Skip to content

Instantly share code, notes, and snippets.

@asaokamei
Last active December 27, 2015 22:09
Show Gist options
  • Save asaokamei/7396720 to your computer and use it in GitHub Desktop.
Save asaokamei/7396720 to your computer and use it in GitHub Desktop.
ロボットの足問題とDI+名前空間

ロボットの足問題とDI+名前空間

DI(依存注入、Dependency Injection)でのロボットの足問題の解決方法としてDIに名前空間を導入してみました。 参考:「Diコンテナの名前空間に関するメモ」

ロボットの足問題については、こちらの英語ページあるいはkoriymさんのRay.DIのIssue#36に詳しくあります。

GuiceやRay.DIを理解できたとは言える段階にないので、他の技術での対応方法について比較できないのですが、DIに名前空間を導入することで、かなりエレガントに解決できると思っています。

実際の実装コードはWScore.DiContainerのrobots.1タグにあります。

ロボットの足問題

ロボットの足問題は次の3つに分解できると考えてます。

  1. 同じクラスだが設定が異なるオブジェクトを注入する方法。
  2. 同じクラスだが設定が異なるオブジェクトを生成する方法。
  3. 上に関連して、少し異なるオブジェクトを管理する方法。

DIコンテナに名前空間を導入すると、上記の2)と3)に対して効率的に対応できます。

なお、今回のサンプルでは3)についてはありませんので、最初の1)と2)について詳説します。

設定の異なる同じクラスのオブジェクトを注入する

この段階では、名前空間は使わなず、普通にDIしてます。

自作DIですが、束縛名は単なる文字列(リテラル)であり、たまたまクラス名の場合はオブジェクトを返す、という仕様になっています。

具体的に、Body.phpを見てみます。

<?php
namespace Robots;

use Robots\Leg as LeftLeg;
use Robots\Leg as RightLeg;

class Body
{
    /**
     * @Inject
     * @var LeftLeg
     */
    public $leftLeg;

    /**
     * @Inject
     * @var RightLeg
     */
    public $rightLeg;

    /**
     * @param int $steps
     */
    public function walk( $steps )
    {
        for( $i = 0; $i < $steps; $i++ ) {
            if( $this->leftLeg ) $this->leftLeg->step();
            if( $this->rightLeg ) $this->rightLeg->step();
        }
    }
}

leftLegには「LeftLeg」を注入するようにしてます。

実際にBodyオブジェクトを生成する際に、LeftLegを 定義して注入します。

これだとLeftLeg何なのかわからず、IDEで入力補完してくれず 不便なのでuse文でLeftLegにLegクラスを割り当ててます。

コードと説明が逆な気はしますが…

設定の異なるオブジェクトを生成する

この段階でDI/名前空間が生きてきます。

実際のコード(build.php)を抜き出します。

<?php
/** @var Container $di */
$di = include( dirname( dirname( __DIR__ ) ) . '/scripts/container.php' );

// set up left leg. 
$di->set( 'LeftLeg',  '\Robots\Leg' )->inNamespace( 'leftLeg' );
$di->set( 'partName', 'Left Leg'    )->inNamespace( 'leftLeg' );

// set up right leg. 
$di->set( 'RightLeg', '\Robots\Leg' )->inNamespace( 'rightLeg' );
$di->set( 'partName', 'Right Leg'   )->inNamespace( 'rightLeg' );

/** @var \Robots\Body $robot */
$robot = $di->get( '\Robots\Body' );
$robot->walk(5);

var_dump( $robot );

名前空間(leftLeg)内で、

  • LeftLegにたいしてRobots\Legクラスを束縛した後、Legクラスを名前空間(LeftLeg)に設定。
  • 同じくleftLeg名前空間内でpartNameを定義。値は「Left Leg」。

この状態で、LeftLegを実体化します。

実体化するとき、名前空間がLeftLegなので、以降はLeftLeg内でDI定義を検索します。 検索は、最初に名前空間(LeftLeg)内で検索し、名前空間内に定義がない場合は、名前空間なしの定義を使います。

これにより、既存の束縛関係を使い回しながら、必要な箇所だけ関係を上書きできます。

実行結果です。

step Left Leg 
step Right Leg 
step Left Leg 
step Right Leg 
step Left Leg 
step Right Leg 
step Left Leg 
step Right Leg 
step Left Leg 
step Right Leg 
class Robots\Body#16 (2) {
  public $leftLeg =>
  class Robots\Leg#18 (1) {
    public $controller =>
    class Robots\Controller#22 (1) {
      public $partName =>
      string(8) "Left Leg"
    }
  }
  public $rightLeg =>
  class Robots\Leg#19 (1) {
    public $controller =>
    class Robots\Controller#24 (1) {
      public $partName =>
      string(9) "Right Leg"
    }
  }
}

ちゃんと左→右、と足を出してます。

###DIにおける名前空間とはなにか?

もう少しDI+名前空間について考えてみます。

つまり、同じクラスでも、設定や依存の少し異なるオブジェクトを「効率的に生成する」方法です。その方法は、

  1. アノテーションからクラスの依存グラフを生成する。これが名前空間なしのデフォルトの依存グラフとなる。
  2. 名前空間を導入し、一部のみ名前空間内に定義を行い、デフォルトの定義を上書きする。
  3. オブジェクトの生成時、まず名前空間内の定義を参照し、定義がない場合は、デフォルトの依存グラフを利用する。

こうやって効率的に「ちょっと異なるオブジェクト」を生成します。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment