Square和Rectangleinheritance有什么问题?
我已经阅读了一些关于使Square成为Rectangle类的inheritance类的做法的一些文章,这说明它违反了LSP(Liskov替换原则)。 我仍然没有得到它,我在Ruby中做了一个示例代码:
class Rectangle attr_accessor :width, :height def initialize(width, height) @width = width @height = height end end class Square < Rectangle def initialize(length) super(length, length) end def width=(number) super(number) @height = number end def height=(number) super(number) @width = number end end s = Square.new(100) s.width = 50 puts s.height
谁能告诉我它有什么问题?
我并不总是热衷于Liskov,因为它似乎限制了你可以根据行为而不是“本质”来inheritance你的行为。 在我看来,inheritance总是意味着“是一种”关系,而不是“行为完全像”一种关系。
话虽如此, 维基百科的文章详细说明了为什么一些人认为这很糟糕,使用您的确切示例:
违反LSP的典型示例是Square类,它派生自Rectangle类,假设宽度和高度都存在getter和setter方法。
Square类总是假设宽度等于高度。 如果在期望Rectangle的上下文中使用Square对象,则可能会发生意外行为,因为Square的维度不能(或者不应该)独立修改。
这个问题不容易修复:如果我们可以修改Square类中的setter方法以保持Square不变量(即保持尺寸相等),那么这些方法将削弱(违反)Rectangle setter的后置条件,声明尺寸可以独立修改。
因此,查看代码和等效的Rectangle
代码:
s = Square.new(100) r = Rectangle.new(100,100) s.width = 50 r.width = 50 puts s.height puts r.height
输出将在左侧为50,在右侧为100。
但是,在我看来, 这是文章的重点:
像这样的LSP的违反在实践中可能是也可能不是问题,这取决于使用违反LSP的类的代码实际预期的后置条件或不变量。
换句话说,如果使用类的代码理解行为,则没有问题。
底线,正方形是矩形的适当子集,对于矩形的宽松定义:-)
从Liskov替换原则(LSP)的角度来看,它的错误在于你的Rectangle
和Square
s是可变的。 这意味着您必须显式重新实现子类中的setter,并失去inheritance的好处。 如果你使Rectangle
不可变,也就是说,如果你想要一个不同的Rectangle
你创建一个新的而不是改变现有的Rectangle
,那么违反LSP是没有问题的。
class Rectangle attr_reader :width, :height def initialize(width, height) @width = width @height = height end def area @width * @height end end class Square < Rectangle def initialize(length) super(length, length) end end
使用attr_reader
给出了getter而不是setter,因此具有不变性。 通过这种实现, Rectangles
和Squares
提供了height
和width
可见性,对于正方形,它们将始终相同,并且区域的概念是一致的。
考虑抽象基类或接口(无论是接口还是抽象类都是与LSP无关的实现细节) ReadableRectangle
; 它具有只读属性Width
和Height
。 可以从类型ReadableSquare
派生,它具有相同的属性,但在合同上保证Width
和Height
始终相等。
从ReadableRectangle
,可以定义具体类型ImmutableRectangle
(在构造函数中获取高度和宽度,并保证Height
和Width
属性将始终返回相同的值)和MutableRectangle
。 还可以定义具体类型MutableRectangle
,它允许随时设置高度和宽度。
在“方形”方面, ImmutableSquare
应该可以替代ImmutableRectangle
和ReadableSquare
。 但是, MutableSquare
只能替代ReadableSquare
[它又可替代ReadableRectangle
。]此外,虽然ImmutableSquare
的行为可替代ImmutableRectangle
,但inheritance具体的ImmutableRectangle
类型所获得的值将受到限制。 如果ImmutableRectangle
是一个抽象类型或接口, ImmutableSquare
类只需要使用一个字段而不是两个字段来保存它的维度(对于一个有两个字段的类,保存一个没什么大不了的,但是不难想象有一个类更多的领域,节省可能是重要的)。 但是,如果ImmutableRectangle
是具体类型,那么任何派生类型都必须包含其基类的所有字段。
某些类型的正方形可替代相应类型的矩形,但可变正方形不可替代可变矩形。