見出し画像

Python_ミックスインでクラスを拡張する #231日目

Mixin (ミックスイン) はオブジェクト指向プログラミングで用いられる技法で、クラスに機能を注入するため、特別な多重継承関係を実現するメカニズムを意味しています。

実務では、例えばクラスにユーザー認証やバリデーションチェック等の共通機能を持たせたい時など、ベースとなるクラスにそれらの機能を注入しておく等で活用できます。

特徴として、Mixinクラスはサブクラスに継承されることで機能を提供することを前提としているため、それ単体では機能しません。

ブログを参考にさせていただきながら、実際にコードを書いて確認してみたいと思います。


ベースとなるクラスを定義

まずはベースとなるクラスを定義します。AnimalBaseという基幹クラスを作り、それを継承してLionクラスを作成します。ここまではクラスのベーシックな使い方ですね。

[test_mixin.py]

class AnimalBase:
    """ベースになる動物クラス"""
    def __init__(self, name):
        self.name = name

class Lion(AnimalBase):
    """ライオンのクラス"""
    pass
[実行]
>>> from test_mixin import Lion
>>> lion = Lion("タマ")


>>> print(lion) 
<test_mixin.Lion object at 0x000002C272817FA0>
 
>>> print(lion.name)
タマ


ミックスインでクラスに機能を1つ追加

ベースとなるクラスができたので、それをミックスインで拡張していきます。ここではLion独自の機能を注入するための「LionMixinクラス」を作成し、最終的なLionクラスで継承しています。

class AnimalBase:
    """ベースになる動物クラス"""
    def __init__(self, name):
        self.name = name

class LionMixin:
    """ライオンの機能を提供するミックスイン
    このクラス単体ではnameが無いためインスタンス化しても機能しない
    """
    def scratch(self):
        print(self.name + 'がひっかいた!')


class  Lion(AnimalBase, LionMixin):
    """最終的なクラス"""
    pass
[実行]
>>> from test_mixin import Lion
>>> lion = Lion("タマ")
 

>>> lion.name
'タマ'
 
>>> lion.scratch()
タマがひっかいた!


ミックスインでクラスに機能を2つ追加

ミックスインは複数実装することができます。先ほどのLionMixinに加え、鳥の機能を注入するための「BirdMixin」を追加します。

class AnimalBase:
    """ベースになる動物クラス"""
    def __init__(self, name):
        self.name = name


class LionMixin:
    """ライオンの機能を提供するミックスイン
    このクラス単体ではnameが無いためインスタンス化しても機能しない
    """
    def scratch(self):
        print(self.name + 'がひっかいた!')


class BirdMixin:
    """鳥の機能を提供するミックスイン
    このクラス単体ではnameが無いためインスタンス化しても機能しない
    """
    def fly(self):
        print(self.name + 'が飛んだ!')


class  Griffin(AnimalBase, LionMixin, BirdMixin):
    """最終的なクラス"""
    pass
[実行]
>>> from test_mixin import Griffin
>>> griffin = Griffin("タマグレート")


>>> griffin.name
'タマグレート'

>>> griffin.scratch()
タマグレートがひっかいた!

>>> griffin.fly()
タマグレートが飛んだ!

このように、ミックスインというメカニズムを使うと、オブジェクト指向を維持しながらクラスを多機能にしていくことが可能です。


継承ではなく委譲でクラスに機能を追加する場合

Pythonでの実装において、継承と委譲の話はよく論点にあがるみたいです。継承は便利ですが、親クラスの機能を全て引き継いでしまう事が不具合につながるケースもあるみたいで、基本的には「委譲」で機能追加した方がよい、という意見が多かったです。

「委譲」の場合は、単純に親クラスをインスタンス化して使うだけなので、必要な機能のみを持ってきて使うことができます。ただその分コードが多くなり、可読性が落ちるというデメリットもあります。

上記のコードを「委譲」スタイルで記述すると以下のようになります。

class AnimalBase:
    def __init__(self, name) -> None:
        self.name = name

class Lion:
    def scratch(self, name):
        print(name + "がひっかいた!")

class Bird:
    def fly(self, name):
        print(name + "が飛んだ!")


class GriffinTransfer():
    def __init__(self, name) -> None:
        self.animal_base = AnimalBase(name)
        self.lion = Lion()
        self.bird = Bird()
    
    def scratch(self):
        self.lion.scratch(self.animal_base.name)
    
    def fly(self):
        self.bird.fly(self.animal_base.name)
[実行]
>>> from test_mixin import GriffinTranser
>>> griffin = GriffinTransfer("タマトランスファー")
 
>>> griffin.animal_base.name
'タマトランスファー'
>>>
>>> griffin.scratch()
タマトランスファーがひっかいた!
>>>
>>> griffin.fly()
タマトランスファーが飛んだ!

LionとBirdクラスそれぞれでnameを受け取って使っています。普通にクラスを作って使っている形ですね。


余談:@staticmethod

ちなみに上記のLionクラスのscratchメソッドを直接使いたい場合は、一度インスタンス化すればOKです。

[実行]
>>> from test_mixin import Lion
>>> lion = Lion()

>>> lion.scratch("タマ")
タマがひっかいた!

さらに@staticmethodデコレータを使うと、いちいちインスタンス化しなくても良くなります。以下でLionクラスだけ@staticmethodでデコレートしてみました。その場合、selfは不要になります。

class AnimalBase:
    def __init__(self, name) -> None:
        self.name = name

class Lion:
    @staticmethod
    def scratch(name):
        print(name + "がひっかいた!")

class Bird:
    def fly(self, name):
        print(name + "が飛んだ!")


class GriffinTransfer():
    def __init__(self, name) -> None:
        self.animal_base = AnimalBase(name)
        self.lion = Lion()
        self.bird = Bird()
    
    def scratch(self):
        self.lion.scratch(self.animal_base.name)
    
    def fly(self):
        self.bird.fly(self.animal_base.name)
[実行]
>>> from test_transfer import Lion, GriffinTransfer

>>> Lion.scratch("タマ")
タマがひっかいた!
  
# @staticmethodを使ったクラスが混じっていてもGriffinTransferは普通に動く
>>> griffin = GriffinTransfer("タマトランスファー")
>>> griffin.scratch()
タマトランスファーがひっかいた!


ここまでお読みいただきありがとうございました!!

参考


この記事が気に入ったらサポートをしてみませんか?