メインコンテンツまでスキップ

Composition Over Inheritance

Dive Into Design Patterns という本をしばらく前に買って、読んでみてはいるもののいまいち理解できずにいる。その理由の1つが、言語によって各コンセプトの実装方法が異なり、場合によっては実装できないことがあるということだと思う。デザインパターン総本山である GoF による本は C++ を想定して書かれており、今回の本でも Python では馴染みの薄い interface や concrete class といった単語が突然出てくる。このため、文章を読んでいても何が言いたいのかがわからず前に進まないのだ。

そんな中で Favor Composition Over Inheritance が出てきて、これは以前にも何度か遭遇したことがあった概念であったためこれだけはここで理解したいと思った。 Inheritance は日頃使っているのでわかるが、 Composition がどのようなものを指すのかわからない。本の中で Composition は次のように説明されている。

Composition is a specific kind of aggregation, where one object is composed of one or more instances of the other. The distinction between this relation and others is that the component can only exist as a part of the container.

Aggregation の説明がこの文の前にあるけどよくわからないし、 Component と Container の説明は無いに等しい。

Real Python だと、 Composition は以下のような説明になっている。

Composition is an object oriented design concept that models a has a relationship. In composition, a class known as composite contains an object of another class known to as component. In other words, a composite class has a component of another class.

Composition allows composite classes to reuse the implementation of the components it contains. The composite class doesn’t inherit the component class interface, but it can leverage its implementation.

Real Python を読んだ感じだと、Composition はインスタンス作成後にそのインスタンスのアトリビュートを代入することっぽい?そして interface class を継承することでクラスにメソッドを与えたものを Mixin Classes と呼ぶらしい?

例えば Inheritance を使って正三角形と正方形のオブジェクトを作り、面積を出してみる。

import math

class EquilateralPolygon:
def __init__(self, side_length):
self.number_of_sides = None
self.side_length = side_length

class EquilateralTriangle(EquilateralPolygon):
def __init__(self, side_length):
super().__init__(side_length)
self.number_of_sides = 3
self.area = math.sqrt(3) * (self.side_length ** 2) / 4

class Square(EquilateralPolygon):
def __init__(self, side_length):
super().__init__(side_length)
self.number_of_sides = 4
self.area = self.side_length ** 2

my_triangle = EquilateralTriangle(10)
print(my_triangle.area) # 43.30127018922193

my_square = Square(10)
print(my_square.area) # 100

これを Composition で書くと、

import math

class AreaCalculations:
def equilateral_polygon_area(number_of_sides, side_length):
if number_of_sides == 3:
return math.sqrt(3) * (side_length ** 2) / 4
elif number_of_sides == 4:
return side_length ** 2

def circle_area(radius): # 今回は使わないが、円の面積も出せる。
return math.pi * (radius ** 2)

class EquilateralPolygon:
def __init__(self, number_of_sides, side_length):
self.number_of_sides = number_of_sides
self.side_length = side_length
self.area = AreaCalculations.equilateral_polygon_area(self.number_of_sides , self.side_length)

my_triangle = EquilateralPolygon(3, 10)
print(my_triangle.area) # 43.30127018922193

my_square = EquilateralPolygon(4, 10)
print(my_square.area) # 100

ということ...なのか?

でも1個目の例でも、 AreaCalculations クラスを同様に定義しておいて

class EquilateralTriangle(EquilateralPolygon):
def __init__(self, side_length):
super().__init__(side_length)
self.number_of_sides = 3
self.area = AreaCalculations.equilateral_polygon_area(self.number_of_sides , self.side_length)

という書き方ができる。 __init__ がボイラープレートになってしまうもののこれはこれで Composition と呼べそう...?

リンク集