Что случилось с наследование квадрата и прямоугольника?
Я читал некоторые статьи о практике, что делать квадрат классом наследования класса 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 ответов
Я не всегда увлечен Лисков, так как он, кажется, ограничивает то, что вы можете сделать с наследованием, основанным на поведении, а не на "сущности". На мой взгляд, наследование всегда должно было быть отношением "есть", а не "действует точно так же".
сказав, что Википедия переходит в деталь как почему это считается плохим некоторыми, используя Ваш точный пример:
типичным примером нарушения LSP является класс Square это происходит от класса Rectangle, предполагая, что методы getter и setter существуют как для ширины, так и для высоты.
класс Square всегда предполагает, что ширина равна высоте. Если объект Square используется в контексте, где ожидается прямоугольник, может произойти непредвиденное поведение, поскольку размеры квадрата не могут (или, скорее, не должны) изменяться независимо.
эта проблема не может быть легко исправлена: если мы можем изменить методы сеттера в Квадратный класс, чтобы они сохраняли квадратный инвариант (т. е. сохраняли размеры равными), тогда эти методы ослабят (нарушат) постусловия для задатчиков прямоугольника, которые утверждают, что размеры могут быть изменены независимо.
Итак, глядя на ваш код рядом с эквивалентом 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.
другими словами, при условии, что код используя классы понимают поведение, нет никаких проблем.
в нижней строке квадрат является правильным подмножеством прямоугольника для достаточно свободного определения прямоугольника: -)
что с ним не так с точки зрения принципа подстановки Лискова (LSP), так это то, что ваш Rectangle
s и Square
s изменчивы. Это означает, что вы должны явно переопределить сеттеры в подклассе, и потерять преимущества наследования. Если вы сделаете Rectangle
s неизменяемый, т. е., если вы хотите другой 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
дает геттеров, но не сеттеров, отсюда неизменность. С этой реализацией оба Rectangles
и Squares
обеспечить видимость height
и width
, для квадрата они всегда будут одинаковыми, и концепция площади последовательна.
рассмотрим абстрактный базовый класс или интерфейс (является ли что-то интерфейсом или абстрактным классом-это деталь реализации, не относящаяся к LSP)ReadableRectangle
; Он имеет свойства только для чтения Width
и Height
. Из этого можно было бы вывести тип ReadableSquare
, который имеет те же свойства, но по контракту гарантирует, что Width
и Height
всегда будут равны.
С ReadableRectangle
, можно определить конкретный тип ImmutableRectangle
(который принимает высоту и ширина в его конструкторе и гарантирует, что Height
и Width
свойства всегда будут возвращать одни и те же значения) и MutableRectangle
. Можно также определить конкретный тип MutableRectangle
, которое позволяет высоте и ширине быть установленным в любое время.
на" квадратной " стороне вещей, an ImmutableSquare
должен быть заменяемым для обоих ImmutableRectangle
и ReadableSquare
. А MutableSquare
, однако, заменяется только на ReadableSquare
[который, в свою очередь, заменить на ReadableRectangle
.] Дальше, в то время как поведение ImmutableSquare
- это взаимозаменяемый для ImmutableRectangle
, значение, полученное путем наследования конкретного ImmutableRectangle
тип будет ограничено. Если ImmutableRectangle
были абстрактным типом или интерфейсом,ImmutableSquare
классу нужно будет использовать только одно поле, а не два, чтобы удерживать его размеры (для класса с двумя полями сохранение одного не имеет большого значения, но нетрудно представить классы с гораздо большим количеством полей, где экономия может быть значительной). Если, однако, ImmutableRectangle
конкретный тип, то любой производный тип придется все поля своей базы.
некоторые типы квадратов заменяются соответствующими типами прямоугольников, но изменяемый квадрат не заменяется изменяемым прямоугольником.