SOLID学习笔记 - 里氏替换原则
作者:互联网
六大设计原则
里氏替换原则:
“子类型必须可替代其基本类型”。换句话说,给定一个特定的基类,从它继承的任何类都可以替代基类。
错误示例:
class LSPDemo { static void Main(string[] args) { Rectangle shape; shape = new Rectangle(); shape.SetWidth(14); shape.SetHeight(10); Console.WriteLine("Area={0}", shape.Area); // 140 shape = new Square(); shape.SetWidth(14); shape.SetHeight(10); Console.WriteLine("Area={0}", shape.Area); // 100 Console.ReadLine(); } } public class Rectangle { protected int _width; protected int _height; public int Width { get { return _width; } } public int Height { get { return _height; } } public virtual void SetWidth(int width) { _width = width; } public virtual void SetHeight(int height) { _height = height; } public int Area { get { return _height * _width; } } } public class Square : Rectangle { public override void SetWidth(int width) { _width = width; _height = width; } public override void SetHeight(int height) { _width = height; _height = height; } } 运行此代码将导致第一个形状的面积为 140,第二个形状的面积为 100。然后,您会想启动调试器以找出发生了什么。子类的行为已更改。用 Uncle Bob 的话来说,代码违反了 LSP,因为子类型不能替代其基类型。现在我们都知道正方形和矩形是不一样的,但这表明正方形不是矩形的特殊类型。 这是大多数关于LSP的讨论停止的地方。但是 LSP 比这个简短的示例更复杂。LSP 实际上执行了几条规则。这些规则分为两类,合同规则和差异规则。让我们更深入地研究一下这些规则。 合同规则创建类时,协定以正式术语说明如何使用该对象。您需要将方法的名称以及这些参数的参数和数据类型作为输入,然后期望将哪种数据类型作为返回值。LSP 对合同规则施加了三个限制: · 子类型无法加强前置条件 – 前置条件是方法可靠运行所需的条件。这将要求正确实例化类,并将所需的参数传递给方法。通常,保护子句用于强制参数具有正确的值。 · 后置条件不能在子类型中被削弱 – 后置条件验证当方法返回时对象是否处于可靠状态。保护子句再次用于强制执行后置条件。 · 超类型的不变量必须由子类型保留 – 不变量是对象构造完成后,在对象的生存期内必须保持为真的事物。这可能是在构造函数中设置并假定不会更改的字段值。子类型不应将这些类型的字段更改为无效值。例如,可能存在一个业务规则,即最低运费字段必须大于或等于零。子类型不应使其成为负数。只读字段保证遵循此规则。 超差规则在解释方差规则之前,我们需要定义方差。以下是维基百科所说的,“方差是指更复杂类型之间的子类型如何与其组件之间的子类型相关联......C# 接口中的方差由其类型参数上的输入/输出注释确定“https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)”。这相当复杂。可以这样想,你期望不同但相关的亚型如何表现?这就是差异。现在,以下是 LSP 差异规则: ·子类型中必须存在方法参数的逆变 – 子类型反转类型的顺序。 ·子类型中方法的返回类型必须存在协方差 – 子类型使类型保持从特定到最泛型的相同顺序。 ·子类型不应引发新的异常,除非这些异常是基类型引发的异常的子类型 – 此规则简单且不言自明。 如果推断这些规则,则会发现子类型无法接受更具体的类型作为参数,并且不能返回不太具体的类型作为结果。
利斯克的解决方案现在我已经展示了LSP不是什么并解释了它是什么,我想向您展示一个李氏原理的例子。问题是,我们需要用正方形代替矩形,反之亦然,只改变实例化的类。但是,在创建正方形时,如果我们传递两个高度和宽度参数,那么哪个参数是正确的呢?我们可以要求您传递相同的数字两次,但这似乎毫无用处,因为只需要一个。归根结底,上面的代码可以很好地显示Liskov不是什么,但是当您尝试显示Liskov是什么时,它不能很好地工作。所以,我必须转向另一个例子。 我决定使用基本类型的动物。当然,这不是一个完美的例子,因为有些动物跳跃,有些动物滑行,行走或疾驰。有些会飞,有些则不会。有些人有脚,有些人没有。但我认为它仍然会显示如何将一个子类替换为另一个子类。 class Program { static void Main(string[] args) { Animal animal = new Dog(); Console.WriteLine(animal.Walk()); Console.WriteLine(animal.Run()); Console.WriteLine(animal.Fly()); Console.WriteLine(animal.MakeNoise()); Console.ReadLine(); } } public class Animal { public string Walk() { return "Move feet"; } public string Run() { return "Move feet quickly"; } public virtual string Fly() { return null; } public virtual string MakeNoise() { return null; } } public class Dog : Animal { public override string MakeNoise() { return "Bark"; } } public class Bird: Animal { public override string MakeNoise() { return "Chirp"; } public override string Fly() { return "Flag wings"; } }
如果为 Dog 运行此代码,则将获得以下输出。 Move feet Move feet quickly Bark 请注意 Fly( ) 的空行。狗不会飞,所以什么都没回。现在把狗改成鸟,你得到 Move feet Move feet quickly Flag wings Bark 如果您现在将Bird更改为Animal,会发生什么情况? Move feet Move feet quickly 子类(狗或鸟)可以替换为基类(动物),一切仍然有效。代码本身并不关心。我们还可以更进一步,使用 Factory 方法来创建我们想要使用的类,而代码甚至根本不知道该类。但这超出了这个讨论的范围。 |
标签:return,SOLID,里氏,笔记,height,width,shape,类型,public 来源: https://www.cnblogs.com/opts/p/16284547.html