コンストラクタが難しい
コンストラクタが難しい。どこが難しいかというと、オブジェクトを渡すタイミングが難しい。 ざっくり言うと、設計もないし行き当たりばったりでコード書いてると、クソコードの海に沈む。テストがあってもなくても、クソコードの海に沈む。
結局自分の中で結論が出せてない。
たとえば、クラス Demo
をこういう感じで実装する。
<?php namespace Path\To\MyNS;
use Path\To\HogeNS\Fuga;
use Path\To\FooNS\Bar;
/**
* なんかするクラス
*/
class Demo
{
/**
* @var \Path\To\HogeNS\Fuga
*/
private $fuga;
/**
* @param $fuga \Path\To\HogeNS\Fuga なんかするやつ
*/
public function __construct(Fuga $fuga)
{
$this->fuga = $fuga;
}
/**
* なんかする
* @param $bar \Path\To\FooNS\Bar なんかするやつ
*/
public function doStuff(Bar $bar)
{
$this->fuga->doStuff();
$bar->doStuff();
}
}
このクラスは Fuga
と Bar
に依存するわけだけど、必ずしもコンストラクタで渡す必要はなくて、メソッドの呼び出しのタイミングで渡すこともできる。
<?php namespace Path\To\MyNS;
use Path\To\HogeNS\Fuga;
use Path\To\FooNS\Bar;
/**
* なんかするクラス
*/
class Demo
{
/**
* なんかする
* @param $bar \Path\To\FooNS\Bar なんかするやつ
*/
public function doStuff(Bar $bar, Fuga $fuga)
{
$this->fuga->doStuff();
$bar->doStuff();
}
}
で、どっちの実装が正しいんだよ。コンストラクタですべきことって何よと。
DI コンテナ?
最初ちょろっと考えてちげーなと思ったのは、 DI コンテナからサクッと注入できるオブジェクトはコンストラクタで渡すってこと。
Laravel 4.x でおなじみの Illuminate/container
の場合、コンストラクタで指定されている型のオブジェクトがコンテナにバインドされていれば、勝手に依存関係を解決してオブジェクトを生成してくれる。
<?php
$container = $this->container = new \Illuminate\Container\Container();
$container->bind('Path\To\MyNS\Demo');
$container->bind('Path\To\HogeNS\Fuga');
// コンストラクタに Path\To\HogeNS\Fuga が渡されてオブジェクトが生成される
$container->make('Path\To\MyNS\Demo');
故に、 DI コンテナで生成できるオブジェクトのみをコンストラクタで渡すべきっていうは明らかに違う。
イミュータブルなオブジェクトを生成しようと思ったら、生成段階で設定を完了させる必要がある。たぶん。あと、クライアント側で設定を渡したりして、属性の異なるインスタンスを生成したい。 Laravel だと、モデルの基底クラスである Eloquent
クラスがコンストラクタで属性の初期値を受け付けるようになってる。
となると、コンテナで生成できないオブジェクトをコンストラクタで渡すのに問題があるとはあまり思えない。
なんかよくわかんなくなってきたけど、とりあえずケースバイケースだから、設計ちゃんとやれってことなんだろうな。眠い。