liskov替代原则违规

时间:2016-02-23 16:34:02

标签: oop solid-principles liskov-substitution-principle

我正在研究liskov substitution principle。它说sub classes should be proper replacement for the base classes

我读了一个我在互联网上不同地方发现的例子。包含Rectangle.java及其height, width的班级setter and getter methods。类Square.java,只需要一个属性,即length。如果我们有Square.java extending Rectangle.java,那么这违反了这一原则。这是因为Rectangle.java的用户希望width只有height被修改才会受到影响,反之亦然。

我的怀疑:

  1. 我们看到的情况是用空的open和close括号覆盖方法,以防止执行在基类中编写的默认代码。这种情况是否违反了这一原则?

  2. 这个原则还说inheritance不应仅用于重用代码。如果如下是不好的做法,这违反了这个原则吗?

  3. 如果可以从某个图形库获得类Window.java。假设它具有绘制窗口所需的所有代码。还假设它在使用和绘制时有一个工具栏。如果要求是创建一个没有工具栏的窗口。

      

    简单地创建一个WindowWithoutToolBar.java,扩展Window.java和   覆盖drawToolBarMethod()并将其留空   解决了这个目的。[可能只是创建工具栏而不是绘制它以避免尝试访问工具栏对象的其他方法发生任何异常]这是一种不好的做法吗?

    创建一个没有工具栏的全新Window类需要重写已经在Window.java中编写的所有代码。

    1. 如果是Numbers,如果我们有一个Integer.java类,其中包含代码,用于各种算术运算,可以使用像平方等整数来完成。如果我们后来需要NaturalNumber.java,我们可以很容易地扩展它现有的Integer.java并添加检查以仅将正整数作为输入。
    2.   

      现在如果我们需要AbsoluteNumber.java,那么万一我们扩展它   Integer.java这样做违反了这个原则(如果是Integer.java   有一些方法作为getValueAfterMultiplyByNegativeOne())?

      请提供宝贵的意见。

      此致

      克里希纳库马尔

3 个答案:

答案 0 :(得分:4)

有一个检查清单,用于确定您是否违反了Liskov。

  • 如果您违反以下任何一项 - >你违反了Liskov。
  • 如果你不违反任何 - >无法得出任何结论。

检查清单:

  • 不应在派生类中抛出新的异常:如果您的基类抛出了ArgumentNullException,那么您的子类只允许抛出ArgumentNullException类型的异常或从ArgumentNullException派生的任何异常。抛出IndexOutOfRangeException违反了Liskov。
  • 前提条件无法加强:假设您的基类使用成员int。现在你的子类型要求int为正数。这是前期条件的强化,现在任何在负面整数之前完全正常的代码都会被破坏。
  • 后置条件不能被削弱:假设您的基类需要在返回方法之前关闭与数据库的所有连接。在您的子类中,您覆盖该方法并保持连接打开以便进一步重用。你已经削弱了那种方法的后置条件。
  • 必须保留不变量:要实现的最困难和痛苦的约束。不变量有时隐藏在基类中,揭示它们的唯一方法是读取基类的代码。基本上,您必须确保在重写方法时,在执行重写方法后,任何不可更改的内容都必须保持不变。我能想到的最好的事情是在基类中强制执行这种不变约束,但这并不容易。
  • 历史记录约束:覆盖方法时,不允许修改基类中的不可修改属性。看一下这些代码,您可以看到Name被定义为不可修改(私有集),但SubType引入了允许修改它的新方法(通过反射):

    public class SuperType
    {
        public string Name { get; private set; }
        public SuperType(string name, int age)
        {
            Name = name;
            Age = age;
        }
    }
    public class SubType : SuperType
    {
        public void ChangeName(string newName)
        {
            var propertyType = base.GetType().GetProperty("Name").SetValue(this, newName);
        }
    }
    

另外还有2个项目:方法参数的反演方法返回类型的协方差。但是在C#(我是C#开发人员)中是不可能的,所以我不关心它们。

参考:

答案 1 :(得分:3)

有趣的问题。

  1. 据我了解,如果将方法保留为空,则会not be违规,并不会改变该类型的预期行为。基本上,它不仅响应“一个正方形是一个矩形”,而且它的整个界面产生相同的行为,从而强化了子类型要求。例如,您提到的关于设置矩形宽度的内容不应影响其高度。

  2. 绝对继承不应仅用于代码重用。如果它实际上适用于所有可能的子类型,那么继续,你可能会没事的。如果它仅适用于它们的子集,您将发现自己不断重写方法,并在一天结束时编写更多代码。 您可以将该公共代码封装到有意义的组件中,然后通过组合在您的类中使用。

  3. 在Numbers的情况下,我认为你正在偏离错误的前提。我不会扩展Integer类来实现Naturals。想一想减去两个自然,其中第二个高于第一个自然:即。 3 - 5.在这里,你需要做出选择,既可以抛出异常,也可以返回不再是自然的东西。另一种方法是扩展抽象的Number类,您可以在其中定义一组方法:

    abstract class Number {
        public abstract Number sum(Number other);
        public abstract Number subtract(Number other);
        public abstract Number multiply(Number other);
        public abstract Number divide(Number other);
    }
    

    这种实现方式并不完美,因为它需要做出一些假设,比如在你操作不同类型的情况下要做的隐式转换(强制转换)。但在这种情况下,它允许你更自由地应用Liskov替换原则。

答案 2 :(得分:2)

  1. 我想说这不是违规行为。我认为如果抛出像

    这样的异常会违反规定
    throw new Exception('Not implemented');
    

    在基类/接口中不期望/记录此异常时。

    这意味着如果你用你的现有类替换它,它会抛出一个异常,基类没有告诉,代码可能会破坏,因为有人可能没有实现try catch块。

  2. 在大多数情况下,不应使用继承“应该”来重用代码。构图是要走的路。我会说这是一个不好的做法。只是我的想法。