OTOBANK Engineering Blog

オトバンクはコンテンツが大好きなエンジニアを募集しています!

SymfonyのService Containerについて(前編)

こんにちは。@mrtry_です。 最近、低温調理機を自作しまして、毎週ハナマサで肉塊を買って、調理する週末を過ごしています。

さて、今回から2回ほどService Containerについて紹介したいと思います。 初回のこの記事では、Service Containerの仕組みの元となるデザインパターンDependency injection(DI)について紹介したいと思います。

以下、今回の記事の目次になります。

  • Dependency injection(DI)とは
  • 実際のコード例
  • DIすると嬉しいこと
  • SymfonyでDIを行うにはSymfonyでDIを行うには

Dependency injection(DI)とは

Dependency injection(DI)とは、デザインパターンの一種です。 よく「依存性の注入」とよく言われています。 和訳だけ見てもどんなものなのか想像しにくいですね...。

英語のwikipediaを見てみると、以下のように説明されています。

In software engineering, dependency injection is a technique whereby one object supplies the dependencies of another object.A dependency is an object that can be used (a service). An injection is the passing of a dependency to a dependent object (a client) that would use it. The service is made part of the client's state.[1] Passing the service to the client, rather than allowing a client to build or find the service, is the fundamental requirement of the pattern. Dependency injection - Wikipedia

意訳すると、だいたい以下のようなことが書いてあります。

  • Dependency injectionは、オブジェクト間の依存関係を解決するデザインパターンのひとつ
  • 「Dependency」は、利用しているオブジェクト(サービス)とのことを指す
  • 「Iinjection」は、「クライアント」に「依存オブジェクト(サービス)」を渡すこと
  • 「依存するオブジェクトをクライアントに渡す」という振る舞いが、DIパターンの基本

また、以下のような説明もあります。

As with other forms of inversion of control, dependency injection supports the dependency inversion principle. The client delegates the responsibility of providing its dependencies to external code (the injector). The client is not allowed to call the injector code.[2] It is the injecting code that constructs the services and calls the client to inject them. This means the client code does not need to know about the injecting code. The client does not need to know how to construct the services. The client does not need to know which actual services it is using. The client only needs to know about the intrinsic interfaces of the services because these define how the client may use the services. This separates the responsibilities of use and construction. Dependency injection - Wikipedia

意訳すると、だいたい以下のようなことが書いてあります。

  • DIには「依存性反転の原理」がある
  • クライアントは、自身に注入される依存オブジェクトの準備を全て「インジェクタ」に任せる
  • クライアントは、依存オブジェクトの生成方法、そのオブジェクトがどんなものであるか知る必要はない
  • クライアントは、注入される依存オブジェクトのInterfaceだけわかれば良い
  • こうすることで、「依存オブジェクトを準備する責任」と「依存オブジェクトを使用する責任」に分けることができる

というわけで、DIをまとめると以下のようになります。

  • 「依存するオブジェクトを外から渡してあげる」という「デザインパターン」のこと
  • 要素として、クライアントとインジェクタというものがある
    • クライアント
      • 依存するオブジェクトを外から受け取り、そのオブジェクトを使用する責任を持つ
      • 渡されるオブジェクトのInterfaceだけ知っている
    • インジェクタ
      • 依存オブジェクトを用意する責任を持つ
      • クライアントが知っているInterfaceを持つクラスを用意する

というものだとわかりました。

実際のコード例

実際のコードでDIしたもの、していないもので比較してみます。 掲載されているソースコードは、やはりあなた方のDependency Injectionはまちがっている。 — A Day in Serenity (Reloaded) — PHP, FuelPHP, Linux or somethingを参考にしています。

ClientServiceというクラスを作成します。 また、ServiceClientの依存オブジェクトとします。

DIしていないコード

Clientのconstruct時に、Serviceもnewし、内部で保持するような実装になっています。

<?php

class Client
{
    private $service;

    public function __construct()
    {
        $this->service = new Service();
    }

    public function doSomething()
    {
        $this->service->doSomething();
    }

    ...
}

class Service
{
    public function doSomething()
    {
        ...
    }

    ...
}

DI しているコード

Clientのconstruct時に、$serviceを外部から受け取るようになっています。 $serviceは、既に準備されたものを受け取るようになっています (この$serviceのオブジェクトの準備は、何かしらで実装されたインジェクタが用意してくれます)。 Clientがどんな$serviceを受け取るかはServiceInterfaceで決定しています。

<?php

class Client
{
    private $service;

    public function __construct(ServiceInterface $service)
    {
        $this->service = $service;
    }

    public function doSomething()
    {
        $this->service->doSomething();
    }

    ...
}

class Service implements ServiceInterface
{
    public function doSomething()
    {
        ...
    }

    ...
}

DIすると嬉しいこと

先程のコード例を見比べて、DIした時にどんな嬉しいことがあるのでしょう?

DIしていないと困ること

  • 結合度が高い
    • 依存オブジェクトの置換や更新をする時、依存オブジェクトを利用しているクラスのソースコードを修正する必要がある
    • 例: ServiceクラスのAPIに変更があったとき、Clientクラスも合わせて修正しなければならない
  • テストがしにくい
    • 依存オブジェクトを直接参照しているので、テスト時にスタブやモックに置換することができない
    • 例: Serviceが本番DBを扱っているオブジェクトとする。Clientのテストをするとき、Serviceに依存しているためモックなどに差し替えができず、DBにテスト用のデータを準備したりする必要がある

DIすると嬉しいこと

  • 結合度の低下
    • 依存オブジェクトを利用しているクラスのソースコードに修正を加えず、依存オブジェクトの置換や更新を行える
    • 例: ServiceクラスのAPIに変更があったとしても、Interfaceで実装を統一しているので、変更する必要はない
  • テストがしやすくなる
    • スタブやモックを準備することで、そのクラスを単体テストすることができる
    • 例: Clientのテストをするとき、Serviceと同様のInterfaceを持つオブジェクトを作成すれば、テスト時は開発サーバに接続したりすることができる

SymfonyでDIを行うには

DIの仕組みを提供するフレームワークのことを、DIコンテナと言います。 Symfonyには、Service ContainerというDIコンテナが初めから導入されて、とても簡単にDIをすることができます。

実際の使い方に関しては、次回説明したいと思います。

おわり

今回は、Dependency injectionについて紹介しました。 次回は、Service Containerの利用方法について紹介したいと思います。

また、この記事は、@mrtry_の勉強の一環で書いていますので、 お気づきの点などがありましたら、コメント等でご指摘いただければ幸いです!

参考