摘自Robert Martin的Agile Principles, Patterns, and Practices in C#,
清单10-1。违反LSP导致违反OCP
struct Point {double x, y;} public enum ShapeType {square, circle}; public class Shape { private ShapeType type; public Shape(ShapeType t){type = t;} public static void DrawShape(Shape s) { if(s.type == ShapeType.square) (s as Square).Draw(); else if(s.type == ShapeType.circle) (s as Circle).Draw(); } } public class Circle : Shape { private Point center; private double radius; public Circle() : base(ShapeType.circle) {} public void Draw() {/* draws the circle */} } public class Square : Shape { private Point topLeft; private double side; public Square() : base(ShapeType.square) {} public void Draw() {/* draws the square */} }
DrawShape()
违反了OCP。它 必须了解Shape类的每个可能的派生类, 并且只要Shape的新导数为 已创建。
Square
和Circle
不能代替Shape
的事实是 违反LSP。此违规行为迫使OCP违反了OCP DrawShape。因此,违反LSP是对OCP的潜在违反。
它如何违反LSP?
(尤其是为什么不能用Square
和Circle
代替Shape
?)
违反LSP如何导致违反OCP? (我可以看到它直接违反了OCP,但我不明白如何违反LSP会导致违反OCP。)
答案 0 :(得分:4)
这不是明显或典型的LSP违规,并且可能会认为它根本不是LSP违规,但这是我的解释:
期望Shape
由其type
字段描述。当DrawShape
收到Shape
对象时,可能会发生以下几种情况之一。根据{{1}}字段的值,它可以尝试将对象强制转换为type
并调用其Square
函数,或尝试将其强制转换为Draw
相同的目的。但是,这不能保证对任意Circle
都能正常工作。具体来说,只有在对象的动态类型实际上与它的Shape
字段的语义含义匹配时,它才起作用。如果type
字段与其动态类型不匹配,则在尝试执行动态强制转换时会发生异常。给定type
对象,这就是DrawShape
的行为。
但是,给定Shape
或Square
,会有不同的期望。具体来说,由于Circle
字段的语义含义将始终与对象的动态类型匹配,因此该函数应始终无一例外地执行一条路径或另一条路径。
换句话说,您可以认为type
函数具有DrawShape
对象的四个有趣的执行路径:动态Shape
强制转换时发生异常,动态Circle
强制转换,或成功执行Square
或Square
绘制函数。
当替换一个孩子时,前面提到的两个路径不再可能,并且对于给定的孩子,只有一个路径是可能的。
或者,可以说没有LSP违反;该功能对于子代的替换仍然与对父代的“作用”相同。 Circle
和Squares
只是有一个额外的含义,就是Circles
字段一定会匹配对象的动态类型,从而限制了函数在运行时的执行结果。虽然可以认为它改变了功能期望,但也可以简单地认为它是施加先决条件。
修改
我想我忘了回答部分问题了:这种假定的LSP违规“导致” OCP违规的原因是因为导致type
与Shapes
行为不同的函数逻辑而Squares
是对子级的动态转换,是迫使Circles
类依赖其子级的逻辑。因此,通过使用关于子类的条件逻辑违反LSP,反过来又违反了OCP。
我不知道我自己是否真的将这种特殊情况称为“因果关系”,就像简单的事件交集一样,但这也许是作者的意图。