クラス、モジュール、関数などのソフトウェアの部品は拡張に対しては開いており、修正に対しては閉じていなければならないとする原則です。
スーパークラスの仕様を理解すれば、それを継承したサブクラスは中身を全てを確認しなくても利用することができる。
(拡張性、保守性の向上)
サブクラスとスーパークラスの間で差異(実行できるものとできないもの)があると、
サブクラスを使うために、サブクラスを全て理解する必要がでてしまう。
# liskov_substitution.py
"""
スーパクラスで使えるものは、サブクラスでも使えるようにしておく
"""
class Rectangle(object):
""" 長方形 """
def __init__(self, width, height):
self._width = width
self._height = height
# propertyとgetter, setterメソッドはセットで考える
# https://naruport.com/blog/2019/8/27/python-tutorial-class-property-getter-setter/
@property
def width(self):
return self._width
@width.setter
def width(self, width):
self._width = width
@property
def height(self, height):
self._height = height
@height.setter
def height(self, height):
self._height = height
def calcurate_area(self):
return self._width * self._height
class Square(Rectangle):
def __init__(self, size):
self._width = self._height = size
"""親クラスのセッターをオーバライドする(プロパティの再定義がいらない)"
また、pythonの使用上、getterも定義しなければならなくなるとのこと
"""
@Rectangle.width.setter
def width(self, size):
self._width = self._height = size
@Rectangle.height.setter
def height(self, size):
self._width = self._height = size
def print_area(obj):
change_to_width = 10
change_to_height = 20
# このままではリスコフの置換原則を満たしていない
# そのため、Squareが入ってきているのに、10*20をしてしまう。
obj.width = change_to_width
obj.height = change_to_height
# 修正方法1(Squareの場合、強引に値を修正する)
if isinstance(obj, Square):
# このプログラム内で、squareオブジェクトに合わせる
change_to_width = change_to_height
# 修正方法2(考え方のみ)他のやり方としては、Reactangleの継承ではなく、もっと抽象的なクラスを作成して
# print_areaはSquareで実行できないようにする、という方法も考えられる。
print('Predicted Area = {}, Actual Area = {}'.format(
change_to_height * change_to_width,
obj.calcurate_area()
))
if __name__ == '__main__':
rc = Rectangle(2, 3)
print_area(rc)
sq = Square(5)
print_area(sq)