Python PR

【Python】合成と委譲の使い方について解説

記事内に商品プロモーションを含む場合があります

この記事では、Python の合成と委譲について解説します。

合成することで継承と同じように使用したいオブジェクトの機能をクラスに実装することができますが、元のオブジェクトとは全くの別物となります。

それでは、合成について見ていきましょう!

合成とは?

合成(Composition: コンポジション)とは、クラスに持たせたい機能を持つオブジェクトを属性として代入すること を言います。

例えば、リストの機能を持たせたいと思ったら以下のように属性として持たせます。

class MyClass:

    def __init__(self):
        self._list = []  # 属性としてリストを持たせる

合成で持たせたオブジェクトは外部から直接呼び出さずにメソッドから呼び出して使います。これを 委譲 と言います。

class MyClass:

    def __init__(self):
        self._list = []  

    # 委譲の実装
    def apeend(self, val):
        self._list.append(val)

合成で持たせたオブジェクトが元々持っている機能だとしてもわざわざメソッドから呼び出して実行します。

めんどくさいですがこのように実装することで何か変更があってもメソッドを書き換えるだけで済み、即座に対応可能です。

合成のメリット

合成には継承と比べて以下のようなメリットがある。

責任と影響の明確化

継承すると親の影響をダイレクトに受けてしまう。つまり、親は親のための機能を追加していくが、子はその影響を受けてどんどん責任が不明瞭になってしまう。

責任というのは
あるクラスが持つ役割のこと、
影響というのは
あるクラスと関わりのあるクラスのことです。

引用先: プログラミング 継承とかインターフェースとかコンポジションとか - はかせのラボ

継承は影響を広げてしまうので変更があった場合にどうなるかが見通しづらくなってしまう。特に、親の機能を使いたいだけの場合は別物として扱った方が都合が良い。

合成を使って影響を小さくする(名前空間を分ける)ことで責任と影響を明確にできる。

属性が最小

合成したクラスでは当然親の属性は表示されない。なので、実際に使用するメソッドしか表示されないので分かりやすい。

例えば、継承の場合は以下のような感じ。

合成では以下のような感じ。

合成のデメリット

合成には継承と比べて以下のようなデメリットがある。

実装が面倒

合成は継承と比べると実装が面倒です。

何をするにもメソッドを通して実装する必要があります。それがたとえ合成で持たせたオブジェクトに元々実装されている機能でもメソッドを通して実装します。

isinstance関数で判別できない

合成したオブジェクトは、もちろん isinstance()関数 でどんな機能を持っているか判別することができません。

コード内からはリストとして扱えるのか文字列として扱えるのかわからない。つまり、継承のように親と同じメソッドが実装されていることを保証できません。

まあ、元々別のオブジェクトとして扱うために合成を使っているのでデメリットというよりは違いですね。

合成と継承の使い所

合成と継承を使い分ける基準として is a の関係と has a の関係 がある。

継承を使ったほうが良い場合とコンポジションを使ったほうが良い場合があるが、具体的に何を基準にして使い分ければ良いだろうか。
この問題を解決するために、一般的な物の考え方の一つに「分類」と「分割」がある。

分類とは、A is a B.のことであり、日本語だと「AはBである」と表現できる。これをis-aの関係と呼ぶ。
例えば、「犬は動物である」「猫は動物である」「人間は動物である」・・・など、これらは分類で表現することが出来る。
is-aの関係で表せるものは、継承で表現するのが都合がいい。

分割とは、A has a B.のことであり、日本語だと「AはBを含んでいる」と表現できる。これをhas-aの関係と呼ぶ。
例えば、「パソコンはCPUを含んでいる」「学校は先生を含んでいる」「自転車はサドルを含んでいる」・・・など、これらは分割で表現することが出来る。
has-aの関係で表せるものは、コンポジションで表現するのが都合がいい。

引用先: 継承とコンポジションをどう使い分けるか

上記のような基準で使い分けてもいいがハッキリ言って難しい。なので、基本的に合成を使ってしまうのも手である。