我已经阅读了一些关于使Square成为Rectangle类的继承类的做法的一些文章,这说明它违反了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
有人能告诉我它有什么问题吗?
答案 0 :(得分:4)
我并不总是热衷Liskov,因为它似乎限制了你可以根据行为而不是“本质”继承的方法。在我看来,继承总是意味着一种“是一种”关系,而不是一种“完全像”的关系。
话虽如此,the wikipedia article详细说明为什么一些人认为这很糟糕,使用您的确切示例:
违反LSP的典型示例是一个Square类,它派生自Rectangle类,假设宽度和高度都存在getter和setter方法。
Square类总是假设宽度等于高度。如果在期望Rectangle的上下文中使用Square对象,则可能会发生意外行为,因为Square的维度不能(或者不应该)独立修改。
这个问题不容易修复:如果我们可以修改Square类中的setter方法以保持Square不变量(即保持尺寸相等),那么这些方法将削弱(违反)Rectangle的后置条件setters,声明可以独立修改尺寸。
所以,查看代码和等效的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的类的代码实际预期的后置条件或不变量。
换句话说,如果代码使用,那么类就会理解行为,那就没有问题。
底线,正方形是矩形的适当子集,对于矩形的宽松定义: - )
答案 1 :(得分:2)
从Liskov替换原则(LSP)的角度来看,它的错误是你的Rectangle
和Square
是可变的。这意味着您必须显式重新实现子类中的setter,并失去继承的好处。如果你使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
的可见性,对于一个方格,它们将始终相同,并且区域的概念是一致的。
答案 2 :(得分:0)
考虑抽象基类或接口(无论是接口还是抽象类都是与LSP无关的实现细节)ReadableRectangle
;它具有只读属性Width
和Height
。可以从类型ReadableSquare
派生,它具有相同的属性,但在合同上保证Width
和Height
始终相等。
从ReadableRectangle
开始,可以定义具体类型ImmutableRectangle
(在构造函数中使用高度和宽度,并保证Height
和Width
属性始终返回相同的值)和MutableRectangle
。也可以定义具体类型MutableRectangle
,它允许随时设置高度和宽度。
在“方块”方面,ImmutableSquare
应该可以替代ImmutableRectangle
和ReadableSquare
。但是,MutableSquare
只能替代ReadableSquare
[它可以替代ReadableRectangle
。]此外,ImmutableSquare
的行为可以替代ImmutableRectangle
。 ImmutableRectangle
,继承具体ImmutableRectangle
类型所获得的价值将受到限制。如果ImmutableSquare
是抽象类型或接口,ImmutableRectangle
类只需要使用一个字段而不是两个字段来保存其维度(对于具有两个字段的类,保存一个没什么大不了的,但是不难想象有更多领域的课程,节省的费用可能很大)。但是,如果{{1}}是具体类型,那么任何派生类型都必须包含其基数的所有字段。
某些类型的正方形可以替代相应类型的矩形,但是可变正方形不能替代可变矩形。