💉

依存性逆転の原則DIPと依存性注入DIを理解しよう

2023/12/23に公開

こんにちはmocchantaiです🤗
SDB Tech Blog Advent Calendar 2023の 23 日目です。

世に存在する依存性逆転の原則(Dependency Inversion Principle)と依存性注入(Dependency Injection)に関する記事が難解なコードだらけで難しいなと感じたので、同じ境遇の方の助けになれるように現実の具体例を使って解説したいと思いました。

コードを使わず身近な例で見てみましょう!

まず難しい話の前に、依存性逆転の原則と依存性注入が何をしたいのかを身近な例を用いて理解してもらいたいです。

状況説明


あなたの家の電球は白熱電球です。
あなたは自分の家の電球を全て白熱電球からLED電球に取り替えたいと思いました!
しかし自宅に電球はたくさんあって一つ一つ取り替えるのはとても大変です。

このときあなたならどのように取り替えますか?

↓↓全ての白熱電球をLEDに付け替えたい↓↓

普通であれば、
各部屋に行って、
脚立などに登って、
白熱電球を取り外し、
LEDを付け直して、、、
また違う部屋に行って、、、

このように全ての電球を切り替えるのに多くの労力を必要とします。

魔法のライトと魔法のスイッチを取り付けよう!

この変更のめんどくささを解決してくれるのが、、、
💡魔法のライト💡と🕹️魔法のスイッチ🕹️です!

(ごめんなさい、、全然身近な例ではないです🙇‍♂️)

全ての部屋に魔法のライトをつけておけば、

スイッチを切り替えるだけで同時に全ての魔法のライトを白熱電球からLED電球に切り替えることができます!
(そういうものだと思ってください)

ずっと何の話をしてるのかわからないと思いますので、これから依存性逆転の原則DIPと依存性注入DIに紐付けていきます。

コードと図で理解しましょう!

登場人物

少し名前をプログラミングの世界に寄せてみましょう。

電球は低レベルモジュール、各部屋は高レベルモジュールを表します。
魔法の電球はインターフェース、魔法のスイッチはDIコンテナ/サービスプロバイダを表します。

依存性逆転をしていないとき

部屋(高レベルモジュール)が電球(低レベルモジュール)に依存しています。
そのため、電球を白熱電球からLED電球に変更するときに、部屋(高レベルモジュール)のコードを変更する必要があります。

※ 白熱電球=IncandescentBulb
電球クラスはonとoffの機能を持たせましょう。

IncandescentBulb.php
class IncandescentBulb {
    public function turnOn() {/* ... */}
    public function turnOff() {/* ... */}
}

ここではキッチン、リビング、お風呂があると仮定しましょう。
それぞれの部屋で白熱電球をインスタンス化してメソッドを使用しています。

KitchenRoom.php
class Kitchen {
    private $bulb;

    public function __construct() {
        $this->bulb = new IncandescentBulb();
    }

    public function activate() {
        $this->bulb->turnOn();
    }
}
LivingRoom.php
class LivingRoom {
    private $bulb;

    public function __construct() {
        $this->bulb = new IncandescentBulb();
    }

    public function activate() {
        $this->bulb->turnOn();
    }
}
BedRoom.php
class Bedroom {
    private $bulb;

    public function __construct() {
        $this->bulb = new IncandescentBulb();
    }

    public function activate() {
        $this->bulb->turnOn();
    }
}

LED電球に切り替えるとき

まずLED電球を作成しましょう。

LEDBulb.php
class LEDBulb {
    public function turnOn() {/* ... */}
    public function turnOff() {/* ... */}
}

そして全ての部屋のファイルをIncandescentBulbからLEDBulbに修正しないといけません。
一つ一つの部屋を回って切り替えていく感じと似てますね。
この例では3部屋しかありませんが、部屋の数が多くなると大変です。

KitchenRoom.php
- $this->bulb = new IncandescentBulb();
+ $this->bulb = new LEDBulb();
LivingRoom.php
- $this->bulb = new IncandescentBulb();
+ $this->bulb = new LEDBulb();
BedRoom.php
- $this->bulb = new IncandescentBulb();
+ $this->bulb = new LEDBulb();

依存性逆転をしているとき

魔法の電球(電球インターフェース)を導入したことで、高レベルモジュールが低レベルモジュールではなく、抽象化された電球インターフェースに依存するようになりました。

電球のインターフェースLightBulbInterfaceを定義します。これによりどの電球クラスもこのインターフェースに従う必要があります。

LightBulbInterface.php
interface LightBulbInterface {
    public function turnOn();
    public function turnOff();
}

白熱電球の具体的な実装です。LightBulbInterfaceに従っています。

IncandescentBulb.php
class IncandescentBulb implements LightBulbInterface {
    public function turnOn() {/* ... */}
    public function turnOff() {/* ... */}
}

LED電球の具体的な実装です。こちらもLightBulbInterfaceに従います。

LEDBulb.php
class LEDBulb implements LightBulbInterface {
    public function turnOn() {/* ... */}
    public function turnOff() {/* ... */}
}

ここではキッチン、リビング、お風呂に電球を取り付けましょう。
このとき、具体的な電球(白熱電球、LED電球)ではなく、魔法の電球(電球インターフェース)で実装します。

KitchenRoom.php
class Kitchen {
    private $bulb;

    public function __construct(LightBulbInterface $bulb) {
        $this->bulb = $bulb;
    }

    public function activate() {
        $this->bulb->turnOn();
    }
}
LivingRoom.php
class LivingRoom {
    private $bulb;

    public function __construct(LightBulbInterface $bulb) {
        $this->bulb = $bulb;
    }

    public function activate() {
        $this->bulb->turnOn();
    }
}
BedRoom.php
class Bedroom {
    private $bulb;

    public function __construct(LightBulbInterface $bulb) {
        $this->bulb = $bulb;
    }

    public function activate() {
        $this->bulb->turnOn();
    }
}

魔法のスイッチであるDIコンテナ/サービスプロバイダでLightBulbInterfaceIncandescentBulbに対応するようにバインドします。

/app/Providers/AppServiceProvider.php
namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Contracts\LightBulbInterface;
use App\Services\LEDBulb;

class AppServiceProvider extends ServiceProvider {
    public function register() {
        $this->app->bind(LightBulbInterface::class, IncandescentBulb::class);
    }
}

LED電球に切り替えるとき

DIコンテナ/サービスプロバイダでのバインド変更するだけです!

魔法のスイッチを切り替えるように、電球インターフェースに注入する具体的な電球クラスを変更します。

/app/Providers/AppServiceProvider.php
- $this->app->bind(LightBulbInterface::class, IncandescentBulb::class);
+ $this->app->bind(LightBulbInterface::class, LEDBulb::class);

このようにLaravelのサービスプロバイダを使用して依存性逆転を実現し、白熱電球からLED電球への切り替えを容易に行うことができます。

結局、依存性逆転の原則と依存性注入は何?

ここまで読んだ方は何となく便利さがわかったかと思いますが、じゃあ今までの説明の中で依存性逆転の原則と依存性注入はどこに当たるのかをまとめます。

依存性逆転の原則

依存性逆転の原則とは、左の図(高レベルモジュールから低レベルモジュールの方向に依存の矢印が向いていた状態)を右の図(高レベルモジュールが抽象的なインターフェースに依存している状態)にすると依存の方向が逆転して、システムの拡張性、柔軟性、再利用性が向上するよね👍という原則です。

依存性注入

依存性注入とは、依存性逆転の原則を実現するための手段であり、コンポーネント(クラス、モジュールなど)が依存しているものを直接作成や取得をするのではなく、外部から提供(注入)されることを指します。

これで依存性逆転の原則と依存性注入を区別して理解ができたのではないでしょうか?

まとめ

少しでも依存性逆転の原則と依存性注入の理解が深まれば幸いです!
今後も学んだことを"身近な例"を交えて解説していきたい思います🙌

ソーシャルデータバンク テックブログ

Discussion