L — Liskov Substitution Principle (Барбара Лисков): код, рассчитанный на базовый тип, должен корректно работать и с подтипом. Это вопрос не синтаксиса, а контракта: если у подкласса те же методы, но поведение нарушает ожидания вызывающего, принцип нарушен.
Классический пример: Rectangle и Square
В математике квадрат — прямоугольник, поэтому Square(Rectangle) выглядит естественно. Но Square после set_width меняет и высоту, а контракт Rectangle («set_width не трогает высоту») нарушается.
class Rectangle:
def __init__(self, w, h):
self.w, self.h = w, h
def set_width(self, w):
self.w = w
def set_height(self, h):
self.h = h
def area(self):
return self.w * self.h
class Square(Rectangle):
def __init__(self, side):
super().__init__(side, side)
def set_width(self, w): # a square keeps its sides equal
self.w = self.h = w
def set_height(self, h):
self.w = self.h = h
def stretch(rect):
rect.set_width(5)
rect.set_height(4)
return rect.area() # the caller expects 5 * 4
print(stretch(Rectangle(1, 1)))
print(stretch(Square(1)))20
16Решение: не делайте квадрат подклассом прямоугольника. Оба — братья под общим контрактом Shape и по отдельности реализуют area().
class Shape:
def area(self):
raise NotImplementedError
class Rectangle(Shape):
def __init__(self, w, h):
self.w, self.h = w, h