6

我读过一些关于让 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

谁能告诉我它有什么问题?

4

3 回答 3

4

我并不总是热衷于 Liskov,因为它似乎限制了您可以基于行为而不是“本质”对继承进行的操作。在我看来,继承总是意味着一种“是”的关系,而不是“行为完全像”的关系。

话虽如此,维基百科文章使用您的确切示例详细说明了为什么某些人认为这很糟糕:

违反 LSP 的典型示例是从 Rectangle 类派生的 Square 类,假设宽度和高度都存在 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 的类的代码实际期望的后置条件或不变量。

换句话说,只要使用类的代码理解行为,就没有问题。

底线,正方形是矩形的适当子集,对于矩形的足够松散的定义:-)

于 2013-05-29T03:18:05.970 回答
2

从 Liskov 替换原则 (LSP) 的角度来看,它的问题在于您Rectangle的 s 和Squares 是可变的。这意味着您必须在子类中显式地重新实现 setter,并失去继承的好处。如果您使Rectangles 不可变,即,如果您想要一个不同的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

Usingattr_reader给出了 getter 而不是 setter,因此具有不变性。有了这个实现,Rectangles和都Squares提供了 和 的可见性heightwidth对于正方形,它们总是相同的,并且面积的概念是一致的。

于 2013-05-29T04:17:22.147 回答
0

考虑抽象基类或接口(无论是接口还是抽象类都是与 LSP 无关的实现细节)ReadableRectangle;它具有只读属性WidthHeight. 可以从中派生一个 type ReadableSquare,它具有相同的属性,但通过合同保证Width并且Height将始终相等。

ReadableRectangle,可以定义具体类型ImmutableRectangle(在其构造函数中采用高度和宽度,并保证HeightandWidth属性将始终返回相同的值),并且MutableRectangle. 还可以定义具体类型MutableRectangle,它允许随时设置高度和宽度。

在事物的“正方形”方面, anImmutableSquare应该可以替代 anImmutableRectangle和 a ReadableSquare。但是, AMutableSquare只能替代 a ReadableSquare[而后者又可以替代 a ReadableRectangle。] 此外,虽然 an 的行为ImmutableSquare可以替代 an ImmutableRectangle,但通过继承具体ImmutableRectangle类型获得的值将受到限制。如果ImmutableRectangle是抽象类型或接口,则ImmutableSquare该类只需要使用一个字段而不是两个来保存其维度(对于具有两个字段的类,保存一个没什么大不了的,但不难想象具有更多字段的类可以显着节省成本的领域)。但是,如果ImmutableRectangle是具体类型,则任何派生类型都必须具有其基类的所有字段。

某些类型的正方形可以替代相应类型的矩形,但可变正方形不能替代可变矩形。

于 2013-07-02T22:15:32.983 回答