2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Python】DI、DIコンテナについて

Last updated at Posted at 2022-12-24

メリットは?

  • コンポーネント間の依存関係を取り除ける
    • コードの変更を行いやすい
    • unit test をしやすい

変更に弱いクラスの構成

DogCatクラスはどちらも、引数に与えられた数字を返すだけの関数numberを持っています

Animalクラスではそれぞれのクラスを直接インスタンス化しています

sample.py
class Cat:
    def number(self, num: int) -> int:
        return num


class Dog:
    def number(self, num: int) -> int:
        return num


class Animal:
    animal_1 = Cat() # => ここで直接インスタンス化
    animal_2 = Dog() # => ここで直接インスタンス化

    def total_number(self, num_1: int, num_2: int) -> int:
        return self.animal_1.number(num_1) + self.animal_2.number(num_2)


animal = Animal()
print(animal.total_number(10, 20))
# > 30

しかし、これでは問題があってコードに変更に弱いです

先ほどのコードが仕様の変更などによって、修正する必要がある場合

Dogではなく、Horseクラスに変更しなければならなくなった場合

sample.py
class Cat:
    def number(self, num: int) -> int:
        return num


class Horse: # ※修正1
    def number(self, num: int) -> int:
        return num


class Animal:
    animal_1 = Cat()
    animal_2 = Horse() # ※修正2

    def total_number(self, num_1: int, num_2: int) -> int:
        return self.animal_1.number(num_1) + self.animal_2.number(num_2)


animal = Animal()
print(animal.total_number(10, 20))

この少量のコードに対してでも、変更箇所が多いです。
これが、規模が大きいものだとカオスなことになりかねないです

では、どう対応すれば良いか?

sample.py
class Cat:
    def number(self, num: int) -> int:
        return num


class Horse:
    def number(self, num: int) -> int:
        return num


class Animal:
    def __init__(self, animal_1, animal_2): # コンストラクタでインスタンスを受け取る
        self.animal_1 = animal_1
        self.animal_2 = animal_2

    def total_number(self, num_1: int, num_2: int) -> int:
        return self.animal_1.number(num_1) + self.animal_2.number(num_2)

# クラスの外でインスタンス化
cat = Cat()
horse = Horse()
animal = Animal(cat, horse)
print(animal.total_number(10, 20))

ここでの変更点は以下です

  • コンストラクタでインスタンスを受け取る
  • クラスの外でインスタンス化

しかしこれではまだ、コンストラクタの引数animal_1, animal_2ではオブジェクトも受け取れてしまいます

そこで、型ヒントを使います

sample.py
class Animal:
    def __init__(self, animal_1: Cat, animal_2: Horse):

しかしこれではまた、animal_1にはCatクラス、animal_2にはHorseクラスしか受け取れない(厳密には、mypyなどサードパーティの型チェックが必要)ので汎用性がなくなります

では、どうすれば良いのか?

抽象クラスを使用します

抽象クラス・・・内部的には機能が存在しないクラス

抽象クラス自体は機能を持たないので、抽象クラスを継承したクラスが抽象メソッドをオーバーライドし処理を行えるように実装します

そうすることで、依存関係の解決ができます

実際にコードで確認してみます

抽象クラスの作成

以下が抽象クラスです

abstractAnimal内のメソッドtotal_numberでは何も処理を行っていないことが分かります

sample.py
from abc import ABCMeta, abstractmethod


class AbstractAnimal(metaclass=ABCMeta):
    @abstractmethod
    def total_number(self, num_1: int, num_2: int):
        pass

では、実際に使用してみます

先ほど作成した抽象クラスを使用してDIする

先程追加した抽象クラスAbstractAnimalを継承させます

そして、Animalクラスのコンストラクタでは型ヒントを追加します

saple.py
from abc import ABCMeta, abstractmethod


class AbstractAnimal(metaclass=ABCMeta):
    def total_number(self, num_1: int, num_2: int):
        pass


class Cat(AbstractAnimal):
    def number(self, num: int) -> int:
        return num


class Horse(AbstractAnimal):
    def number(self, num: int) -> int:
        return num


class Animal:
    def __init__(self, animal_1: AbstractAnimal, animal_2: AbstractAnimal):
        self.animal_1 = animal_1
        self.animal_2 = animal_2

    def total_number(self, num_1: int, num_2: int) -> int:
        return self.animal_1.number(num_1) + self.animal_2.number(num_2)


cat = Cat()
horse = Horse()
animal = Animal(cat, horse)
print(animal.total_number(10, 20))

その結果、依存性の逆転という現象が起こります

依存性の逆転

スクリーンショット 2022-09-12 16.37.38.png

これまで、クラス間同士で依存しあっていましたが、間に抽象クラスを噛ませることによって、具象クラスであるCatや,Horseが抽象クラスに対して依存するようになリました

DIコンテナについて

以下、追記しました(2022/12/30)

DIコンテナとは、コンポーネント間の依存関係の管理を行うソフトウェアの事です

DIコンテナとして、injectorというライブラリを使用したものを例に取ります。

It automatically and transitively provides dependencies for you.

これを使用することで、依存関係を自動的かつ過渡的に管理します

実際に使用する

from injector import inject


class AbstractAnimal(metaclass=ABCMeta):
    def total_number(self, num_1: int, num_2: int):
        pass


class Animal:
    @inject
    def __init__(self, animal_1: AbstractAnimal, animal_2: AbstractAnimal):
        if not isinstance(animal_1, AbstractAnimal):
            raise Exception("animal_1 is not AbstractAnimal")
        if not isinstance(animal_2, AbstractAnimal):
            raise Exception("animal_2 is not AbstractAnimal")

        self.animal_1 = animal_1
        self.animal_2 = animal_2

@injectで、コンストラクタに対して依存先オブジェクトを注入するための宣言をデコレータでしています
def __init__(self, animal_1: AbstractAnimal, animal_2: AbstractAnimal):・・・型ヒントは必須です

from injector import inject, Injector, Module

.
.
.

class AnimalDIModule(Module):
    def configure(self, binder):
        binder.bind(Cat, to=Cat)
        binder.bind(Horse, to=Horse)


if __name__ == '__main__':
    di = Injector(AnimalDIModule())
    animal = Animal(di.get(Cat), di.get(Horse))
    print(animal.total_number(10, 20))

続いて、Module クラスを継承したAnimalDIModule クラスを作成し、依存関係の設定をします

di.getをする事で実際に依存関係の解決をし、インスタンスを作成します

参考

2
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?