オブジェクト指向の奥義!DI(依存性注入)を習得する

ソフトウェア

 

DI(依存性注入)を習得できれば、

  • 処理が複雑な制御クラスの単体テストができる
  • 依存クラスに左右されなくなるため、ソースコードが読みやすくなる

ようになります。

 

DIをマスターして、あなたのソースコードをよりオブジェクティブにしましょう!

 

 

DI(依存性注入)とは?

 

DIとはなんぞや。以下、Wikipedia参照です。

コンポーネント間の依存関係をプログラムのソースコードから排除するために、
外部の設定ファイルなどでオブジェクトを注入できるようにするソフトウェアパターンである。
英語の頭文字からDIと略される。

 

「コンポーネント間の依存関係を排除するためのソフトウェアパターン」なので、

「依存性注入」という直訳と少しイメージが合いませんね。。。

 

DIできていない実装例

 

DIができていない実装例を示します。

 

例えば、何かしらのデバイスを制御しながら動作するシステムを考えます。

クラス構成としては、

  • システムを制御する「Controller」
  • デバイスと通信する「DeviceCommunicator」

としましょう。

 

このシステムを、何も考えずに実装すると以下のような感じになるでしょうか。

// システムのコントローラ
class Controller:
    def __init__(self):
        self.device_com = DeviceCommunicator()

    def get_data(self):
        val = self.device_com.get_data()
        if vel < 0:
            raise Exception("ValueError")
        return val

// 何かしらのデバイスとの通信用クラス
class DeviceCommunicator:
    def __init__(self):
        // デバイスとの通信確立

    def get_data(self):
        return // 何かしらのデータを返す

Controllerはデバイスと通信してデータを取得する必要があるので、コンストラクタでDeviceCommunicatorを初期化しています。

しかし、Controllerの中でDeviceCommunicatorを初期化してしまうと、ControllerがDeviceCommunicatorに依存してしまいます。

 

DeviceCommunicatorはコンストラクタで制御するデバイスと通信確立しますので、デバイスがないと動きません。

なので、Controllerもデバイスが接続されていないと動作しないことになります。

 

そうなると困るのが、以下のような点です。

  • Controllerの単体テストができない
  • デバイスなしでシステムを動作できない
  • デバイスの入れ替えが容易にできない

 

デバイスに依存しないControllerにするために、DIを活用していきます。

 

DIを活用した実装例

 

デバイスなしでControllerを動作させるためには、Controllerがデバイスを意識することなく動作することが不可欠です。

そのために使用するのが、Interfaceなどの抽象クラスです。

※以下に乗せるのはpythonのコードです。pythonは型を厳密に定義しないので、抽象クラスを使わなくともDIを実現可能ですが、わかりやすさのため以下のようなコードになっています。

// システムのコントローラ
class Controller:
    // i_device_comを初期化時に引数でもらう
    def __init__(self, i_device_com):
        self.i_device_com = i_device_com

    def get_data(self):
        val = self.i_device_com.get_data()
        if val < 0:
            raise Exception("ValueError")
        return val

from abc import ABCMeta, abstractmethod

// デバイスとの通信を抽象化する抽象クラス(インターフェース)
class IDeviceCommnicator(metaclass=ABCMeta):
    @abstractmethod
    def get_date(self):
        pass

// ダミーデバイスとの通信用クラス
class DammyDeviceCommunicator(IDeviceCommunicator):
    def __init__(self):
        // ダミーなので、何もしない
        pass

    def get_data(self):
        return 0 // ダミーなので、適当な値を返す

// デバイスとの通信用クラス
class DeviceCommunicator(IDeviceCommunicator):
    def __init__(self):
        // デバイス1との通信確立

    def get_data(self):
        return // 何かしらのデータを返す

// 単体テスト用クラス
class StubCommunicator(IDeviceCommunicator):
    def __init__(self):
        pass

    def get_data(self):
        return -1 // controllerのエラー処理を確かめるための値を返す

 

Controllerをインスタンス化する際にIDeviceCommunicatorを引数に渡します。

IDeviceCommunicatorは抽象クラスなので、実態を持ちません。

代わりにIDeviceCommunicatorを継承したクラス(DeviceCommunicatorなど)のインスタンスを渡してやればよいです。

 

ここで重要なのが、ControllerはIDeviceCommunicatorの実態が何であるか意識しないということです。

 

引数として渡すIDeviceCommunicatorは目的によって変えることができます。

  • デバイスと通信したいなら、DeviceCommunicatorのインスタンス
  • デバイス未接続で動かしたいなら、DammyDeviceCommunicatorのインスタンス
  • Controllerのテストをしたいなら、StubDeviceCommunicatorのインスタンス

しかしどのインスタンスを渡されてもControllerはそれを意識することはありません。

 

この様に、1つのコードで複数の振舞いが行えることを、多態性があると言ったりします。

 

Controllerはデバイスの種類を気にすることなく、制御の実装のみになるので、自ずと読みやすくなります。

Controllerがデバイスの種類を気にしだすと、デバイスを切り替えるためのif文を書かなきゃいけなかったりして、Controllerの実装が複雑になりますね。

 

まとめ

 

DIについて紹介しました。

依存性注入という日本語訳からは想像しにくい、各コンポーネント間の依存性を排除するための仕組みになります。

DIを使いこなせれば、単体テストが書けるようになり、ソースコードも読みやすくなるので、是非ともマスターしたいスキルです。

 

DIをマスターして、オブジェクティブなコーディングを目指しましょう!

 

コメント