【问题标题】:How Liskov substitution principle is different from normal inheritance?Liskov 替换原则与普通继承有何不同?
【发布时间】:2019-11-17 05:11:25
【问题描述】:

我正在尝试理解 Liskov 替换原则。但我无法确定 Liskov 替换原则与正常继承有何不同。 下面的代码是关于正常继承的。我应该怎么做下面的代码说我的代码遵循了 Liskov 替换原则

    public class ClassA
    {
        public virtual void MethodA()
        {
            Console.WriteLine("-----------------ClassA.MethodA---------------------");
        }

        public virtual void MethodB()
        {
            Console.WriteLine("-----------------ClassA.MethodB---------------------");
        }
    }

    public class ClassB: ClassA
    {
        public override void MethodA()
        {
            Console.WriteLine("-----------------ClassB override ClassA.MethodA---------------------");
        }
    }

【问题讨论】:

    标签: c# inheritance liskov-substitution-principle


    【解决方案1】:

    这是一个常见的定义:

    https://www.tomdalling.com/blog/software-design/solid-class-design-the-liskov-substitution-principle/

    里氏替换原则 (LSP):使用指针的函数 基类必须能够使用派生类的对象,而无需 知道。

    这里有更严格的解释:

    https://en.wikipedia.org/wiki/Liskov_substitution_principle

    .. Liskov 替换原则 (LSP) 是子类型关系的特定定义,称为(强)行为子类型,最初由 Barbara Liskov 在 1987 年的会议主题演讲中引入,标题为“数据抽象和层次结构”。它是一种语义关系,而不仅仅是句法关系,因为它旨在保证层次结构中类型的语义互操作性,尤其是对象类型。 Barbara Liskov 和 Jeannette Wing 在 1994 年的一篇论文中简洁地描述了该原理如下:

    子类型要求

    Let ϕ ( x ) be a property provable about objects x of type T. Then ϕ ( y ) should be true for objects y of type S where S is a subtype of T.

    Liskov 原则对新的面向对象编程语言中采用的签名提出了一些标准要求(通常在类级别而不是类型级别;请参阅名义子类型与结构子类型的区别):

    • 子类型中方法参数的逆变。
    • 子类型中返回类型的协方差。
    • 不应由子类型的方法引发新的异常,除非这些异常本身是由超类型的方法引发的异常的子类型。

    除了签名要求外,子类型还必须满足许多行为条件。这些在类似于按合约方法设计的术语中进行了详细说明,从而导致对合约如何与继承交互的一些限制:

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

    问:你的例子符合吗?

    A:我不这么认为。因为 A.MethodA() 在语义上与 B.MethodA() 是“不同的”。 ClassB 没有通过The Duck Test

    我提出这个反例:

    public class ClassA {
      public virtual void MethodA() {
        Console.WriteLine("ClassA.MethodA");
      }
    
      public virtual void MethodB(){
        Console.WriteLine("ClassA.MethodB");
      }
    }
    
    public class ClassC: ClassA {
      public void MethodC() {
        Console.WriteLine("ClassC.MethodC");
      }
    }
    

    这也是为什么 LSP “与”继承“相同”的一个很好的例子。

    【讨论】:

    • 嗨,保罗,谢谢你的例子。从定义“使用指向基类的指针的函数必须能够在不知道的情况下使用派生类的对象”。我可以说,当我们关注 LSP 时,我们不应该重写父类吗?并且在您的代码示例中,您在派生类中有 override MethodC,实际上父类中没有 MethodC。你想用 MethodC 扩展 ClassC 吗?
    • 来自不同的 SO 帖子:stackoverflow.com/questions/1735137“如果您更改由基本方法定义的任何行为,则覆盖会破坏 Liskov 替换原则。”
    • hashCode 函数呢? LSP 似乎要求子类返回与基类Object 相同的值,这是反直觉的。
    猜你喜欢
    • 2014-06-05
    • 1970-01-01
    • 1970-01-01
    • 2016-03-12
    • 1970-01-01
    • 2010-12-03
    • 1970-01-01
    • 2014-05-04
    • 2016-08-20
    相关资源
    最近更新 更多