【问题标题】:Liskov Substitute Principle (LSP) with Code example带有代码示例的 Liskov 替换原则 (LSP)
【发布时间】:2015-09-18 04:47:03
【问题描述】:

里氏替换原则要求

  1. 先决条件不能在子类型中得到加强。
  2. 不能在子类型中削弱后置条件。
  3. 超类型的不变量必须保留在子类型中。
  4. 历史约束(“历史规则”)。对象被认为只能通过它们的方法(封装)进行修改。由于子类型可能会引入超类型中不存在的方法,因此这些方法的引入可能会允许子类型中的状态更改在超类型中是不允许的。历史约束禁止这样做。

任何人都可以发布一个违反这些要点的示例以及解决这些问题的另一个示例吗?

【问题讨论】:

  • 您查看this question 或this question 的答案了吗? ;)。
  • 我检查了Vehicles的例子。我认为第一个和第三个条件解释得很好。但是从上面的例子中,第二个和第四个仍然不清楚。
  • @shA.t - 我个人觉得 Rectangle 和 Square 的例子很蹩脚,因为它确实显示了问题但不是解决方案。
  • @Sam 维基百科lists a couple of possible solutions to the Rectangle-Square Problem。在实践中,删除继承关系(Rectangle is-a Square 和 Square is-a Rectangle)效果很好,不可变对象也是如此:一旦创建,这些对象的属性就不能修改。删除继承对应于允许每个类使用单独的不变量,而不变性是强制执行不变量的最严格的机制。具有不变性,Square is-a Rectangle 是允许的。

标签: solid-principles liskov-substitution-principle


【解决方案1】:

你知道 ICollection 接口吗? 想象一下,您正在编写一个获取 ICollection 并使用其 Add 方法或更好的 Clear 方法来操作它的方法 如果有人传递了一个 ReadOnlyCollection(实现了 ICollection),您将获得使用 Add 的异常。 现在你永远不会想到,因为接口定义没问题,所以 ReadOnlyCollection 违反了 LSP。

【讨论】:

    【解决方案2】:

    问题中的所有四个项目都已在this article 中进行了彻底审查。

    先决条件不能在子类型中得到加强。

    This answer 给出了“真鸭”和“电鸭”的例子,建议你去看看。为简洁起见,我将在本项目中使用它。

    这意味着子类型不能妨碍原始方法在基类中的行为方式。在上面提到的答案代码中,两只鸭子都可以游泳,但ElectricDuck 只有在打开时才会游泳。因此,任何需要鸭子(来自接口IDuck)游泳的代码单元现在都不起作用,除非明确指定鸭子是ElectricDuck(然后打开),这需要实现无处不在。

    后置条件不能在子类型中被削弱。

    对于这个,我们可以从鸭子的类比中退后一步。让我们以this 答案为基础。假设我们有一个只接受正整数的基类。如果在子类型中,在扩展方法时,我们删除了数字必须为正的条件,那么过去认为该数字为正的所有代码单元现在都有被破坏的风险,因为现在无法保证这个数字是正数。这是这个想法的一个表示:

    public class IndexBaseClass
    {
        protected int index;
        public virtual int Index
        {
            get
            {
                //Will return positive integers only
                return index < 0 ? 0 : index;
            }
            set
            {
                index = value;
            }
        }
    }
    
    public class IndexSubClass : IndexBaseClass
    {
        public override int Index
        {
            get
            {
                //Will pay no mind whether the number is positive or negative
                return index;
            }
        }
    }
    
    public class Testing
    {
        public static int GetIndexOfList(IndexBaseClass indexObject)
        {
            var list = new List<int>
            {
                1, 2, 3, 4
            };
    
            return list[indexObject.Index];
        }
    }
    

    如果我们调用 GetIndexOfList 传递一个 IndexSubClass 对象,则不能保证该数字是正数,因此可能会破坏应用程序。想象一下,您已经在整个代码中调用了这个方法。您必须浪费时间检查所有实现中的正值。

    超类型的不变量必须保存在子类型中。

    父类可能有一些不变量,即只要对象存在,一些条件就必须保持为真。任何子类都不应该继承该类并消除这个不变量,因为到目前为止所有实现都有崩溃的风险。在下面的例子中,如果它是负数,父类抛出一个异常然后设置它,但子类只是简单地忽略它,它只是设置不变量。

    以下代码取自here:

    public class ShippingStrategy
    {
        public ShippingStrategy(decimal flatRate)
        {
            if (flatRate <= decimal.Zero)
                throw new ArgumentOutOfRangeException("flatRate", "Flat rate must be positive 
      and non-zero");
    
            this.flatRate = flatRate;
        }
    
        protected decimal flatRate;
    }
    
    public class WorldWideShippingStrategy : ShippingStrategy
    {
        public WorldWideShippingStrategy(decimal flatRate)
            : base(flatRate)
        {
            //The subclass inherits the parent's constructor, but neglects the invariant (the value must be positive)
        }
    
        public decimal FlatRate
        {
            get
            {
                return flatRate;
            }
            set
            {
                flatRate = value;
            }
        }
    }
    

    历史约束(“历史规则”)。

    这条和上一条规则一样。它指出子类型不应引入改变父类中的不可变属性的方法,例如将子类中的新Set方法添加到曾经的属性只能通过构造函数设置。

    一个例子:

    public class Parent
    {
        protected int a;
    
        public Parent(int a)
        {
            this.a = a;
        }
    }
    
    public class Child : Parent
    {
        public Child(int a) : base(a)
        {
            this.a = a;
        }
    
        public void SetA(int a)
        {
            this.a = a;
        }
    }
    

    现在,由于子类,父类中以前不可变的属性现在是可变的。这也违反了 LSP。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-01-18
      • 2010-12-03
      • 1970-01-01
      • 2016-08-20
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多