Square和Rectangle继承有什么问题?

时间:2013-05-29 03:06:02

标签: ruby oop liskov-substitution-principle

我已经阅读了一些关于使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

有人能告诉我它有什么问题吗?

3 个答案:

答案 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)的角度来看,它的错误是你的RectangleSquare是可变的。这意味着您必须显式重新实现子类中的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,因此具有不变性。通过此实现,RectanglesSquares都可以提供heightwidth的可见性,对于一个方格,它们将始终相同,并且区域的概念是一致的。

答案 2 :(得分:0)

考虑抽象基类或接口(无论是接口还是抽象类都是与LSP无关的实现细节)ReadableRectangle;它具有只读属性WidthHeight。可以从类型ReadableSquare派生,它具有相同的属性,但在合同上保证WidthHeight始终相等。

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

在“方块”方面,ImmutableSquare应该可以替代ImmutableRectangleReadableSquare。但是,MutableSquare只能替代ReadableSquare [它可以替代ReadableRectangle。]此外,ImmutableSquare的行为可以替代ImmutableRectangleImmutableRectangle,继承具体ImmutableRectangle类型所获得的价值将受到限制。如果ImmutableSquare是抽象类型或接口,ImmutableRectangle类只需要使用一个字段而不是两个字段来保存其维度(对于具有两个字段的类,保存一个没什么大不了的,但是不难想象有更多领域的课程,节省的费用可能很大)。但是,如果{{1}}是具体类型,那么任何派生类型都必须包含其基数的所有字段。

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