2012年5月16日水曜日

DIを擬人化して理解してみる

Dependency Injection(DI)とか依存性の注入などと言われてもピンとこない。
仕方がないので、実験的に作っているAmidaMVCというPHPフレームワークでDIコンテナを自作してみました。

まぁDIとはこういうものかな?
というのを擬人化して説明を試みた結果です。

◆ DI以前のオブジェクトとは

あるオブジェクトが動作するため、別のオブジェクトに依存している状況を「依存性がある」と言います。依存性があること自体は問題はありませんが、
依存性が固定されている
のが問題と考えられます。

これを擬人化すると
「俺は、こいつと永遠に一緒だぜ」
となるのではないでしょうか?

コードで書くと

class foo {
  protected $love = NULL;
  function __construct() {
    $this->love = new \My\Love();
  }
}

というところでしょう。

一途というのはいいものだけれど、融通がきかない。
これを何とかするのがDIです。

◆ DI(依存性を注入する)

ようは
「誰とでもOK、注入してして」。
という軽さを売りにするのがDI。

コードにすると、

class foo {
  protected $love = NULL;
  function injectLove( $love ) {
    $this->love = $love;
  }
}

こういうのを汎用性が高いと言うのでしょう。
テストしやすい、というのがDIを正当化するのによく言われます。

ただ実際には使いづらい面があります。

◆ IoC(Inversion of Control)

何しろオブジェクトを作るには何かを注入してやらなければいけない。
独り立ちではなく、逆に誰かに依存してしまいます。

これを擬人化すると、
「一人だと何も出来ないんです。誰か注入してください」
と言う「ひ弱な僕」というのがピッタリな表現でしょう。

このように、使う方から使われる側へとオブジェクトの実装を変更することを
制御の反転(Inversion of Control)
と呼びます(多分)。

◆ Container(コンテナ)

こんなひ弱なオブジェクトたちにはママが必要です。
「あ~、坊やにはこれを注入してあげるからね」
これをコンテナと呼びます。
いろんな作り方があるけど、例えばこんな感じ?

class DiMom {
  function loveFoo() {
    $foo = new foo();
    $foo->injectLove( new \I\Love\You() );
    return $foo;
  }
}
オブジェクトの生成を一手に担い、適切なオブジェクトを注入して依存性を解決してあげる。そんなBig Motherのようなクラスをコンテナと呼ぶのでしょう。

実際には、コードでゴリゴリ書くのではなく設定できるようにすると思いますが。


◆ Service Locator(SL)

以上、色々書きましたが、未だにSLとコンテナの違いがよく分かってません。似ているようで違う、というか、多分自分の書いたコンテナはSLの事な気がしています。いや、何が違うのだろう?

思うに、オブジェクトがコンテナに依存している状態をSLと呼ぶのではないでしょうか?

自分が開発しているフレームワークはChain of Responsibilityを使っていて、必要に応じてモジュール(オブジェクトのことです)を呼び出します。
正確に言うと、ある処理を行うのに必要なモジュールを判断するモジュールがあって、そのモジュールがチェーンに必要なモジュールを追加します。
フレームワークは順番にオブジェクトを生成・実行するだけですが、生成部分にコンテナを使いますが、これだと多分SLという区分になる気がします。

また理解できたと思ったら書いてみます。


◆ 参考文献

色々読んだけど、今ブックマークしてあるもののみリスト。


2012年5月7日月曜日

Dependency Injection とリソース指向について

GW最後の夜、ストレッチをしながらDependency Injection(DI)について考えてみた。DIについて調べ始めてまだ3ヶ月の新人なので、未だによく分からないことだらけだが。

DIってリソース指向と似ているのでは?

と思った気がした。
生成するオブジェクトをリソースとして捉えると、
  1. リソース(オブジェクト)の生成
  2. リソースの操作(依存性の注入)
という手順が成立する。

リソースの操作っていうのは、要するに設定の代入と依存性の注入になる。

そこで、さらに手順を細分化してみると

  1. 生成するオブジェクトを特定する
    new、Singleton、staticなど生成方法を含めて一意に特定する
  2. 設定の注入
    オブジェクトにconfigを代入する
  3. 依存性の注入
    任意のオブジェクトを注入可能とする

という手順になるのかな。

◆ オブジェクトを特定するDIN表記

まずはオブジェクトを特定する記述について考えてみる。
こんな感じかな。
['DIN' => [ $className, $type, $idName] ]
ここで
$classNameは生成するクラス名。
$typeはnew、get、static、のどれか。意味は明白だよね。
$idNameは前に生成したオブジェクトを指定するID名。省略可。英数字ならOKとか。

ちなみにDINはDependency Injection Notationの略(笑
で、DIN表記と呼んでみよう。

◆ 設定の注入

さっき作ったオブジェクトに対して、設定を注入する。

['DIN' => [ $className, $type, $idName],
'config' => [ 'A' => 'a', 'B' => 'b', ] ]

と表現できるでしょう。

◆ 依存性の注入

他のオブジェクトをDIN使って表しておいて、依存性を注入。


['DIN' => [ $className, $type, $idName],
'config' => [ 'A' => 'a', 'B' => 'b', ]
'inject' => [
    'name1' => [ 'class1', 'get', 'test' ],
    'name2' => [ 'class2', 'new' ],
  ]
]
これで、DIN表記を導入したメリットが見えるのでは。

必ずしもnewしたオブジェクトを代入する必要はなくて、get(singleton)で作ったオブジェクトを使うことができる。あるいは、複数のオブジェクトで共通したオブジェクトに依存する、という場合もあろう。


◆ PHPでの実装を想像してみる

DIN表記は、こうだよね。
$di->din( 'className', 'new' );
$di->din( 'className', 'get' );
あるいは
$di->din( [ 'className', 'get', 'test' ] );
$di->din( [ 'className', 'static' ] );
設定を注入してみよう。
( Di::start() )->din( 'className', 'new' )->config( $config );
とか格好良くない?

で、依存性を注入。
( Di::start() )->din( 'className', 'get', 'test' )
->config( $config )
->inject( 'loader', [ 'myLoader', 'new', '1' ] )
->inject( 'loader2', [ 'yourLoader', 'get' ] );
さらに
( Di::continue() )->din( 'className', 'get', 'main' )
->config( $config )
->inject( 'loader', [ 'myLoader', 'new', '1' ] )
->inject( 'loader2', [ 'myLoader', 'new', '1' ] );
と別のオブジェクトも続けて生成できたら、便利かなぁ?

あ、まだオブジェクトを入手してない。
( Di::continue() )->forge()
->obtain( $obj1, [ 'className', 'get', 'test' ] )
->obtain( $obj2, [ 'className', 'get', 'main'' ] )
->saveAs( 'myTest' );
設定を保存できたりして。
で、いつでも呼び出せる。
( Di::start() )->forge( 'myTest' )
->...


う~ん。
全部を実装すると大変そうだ。

特に最後の設定の保存あたり。多分このAPIでは使えない気がする。
たとえば、注入されるオブジェクトを保存した設定で生成したい、という場合に対応できない。また生成されるオブジェクトの性質が一定になってしまう。常にnewとか。ちょっと使い勝手が悪そうだ。

それとデフォルトの依存性を誰が管理するか、決めないと。
コンテナ側でデフォルトは持たない、設定の保存などはコンテナの責任、として
どういうAPIで実装すると分かりやすいのか?


まぁGWの夜の夢ということですが、
実際に実装しないとDIとは何かすら分からない状態なので、
もう少し調べてみてから実装してみよう。