DIの実装方法は、使用する言語に大きく依存します。
以下に、DI以外の簡単な例を示します。
class Foo {
private Bar bar;
private Qux qux;
public Foo() {
bar = new Bar();
qux = new Qux();
}
}
たとえば、テストのためににモックオブジェクトを使用したいときは、これは面倒ですbar
。したがって、これをより柔軟にし、コンストラクターを介してインスタンスを渡すことができます。
class Foo {
private Bar bar;
private Qux qux;
public Foo(Bar bar, Qux qux) {
this.bar = bar;
this.qux = qux;
}
}
// in production:
new Foo(new Bar(), new Qux());
// in test:
new Foo(new BarMock(), new Qux());
これは、すでに最も単純な形式の依存性注入です。しかし、すべては手動で実行する必要があるため(これは、呼び出し側が内部オブジェクトへの参照を保持し、状態を無効にすることができるため)、これでもいやです。
ファクトリーを使用することで、さらに抽象化を導入できます。
1つのオプションはFoo
、抽象ファクトリーによって生成されます
interface FooFactory {
public Foo makeFoo();
}
class ProductionFooFactory implements FooFactory {
public Foo makeFoo() { return new Foo(new Bar(), new Baz()) }
}
class TestFooFactory implements FooFactory {
public Foo makeFoo() { return new Foo(new BarMock(), new Baz()) }
}
FooFactory fac = ...; // depends on test or production
Foo foo = fac.makeFoo();
別のオプションは、ファクトリーをコンストラクターに渡すことです。
interface DependencyManager {
public Bar makeBar();
public Qux makeQux();
}
class ProductionDM implements DependencyManager {
public Bar makeBar() { return new Bar() }
public Qux makeQux() { return new Qux() }
}
class TestDM implements DependencyManager {
public Bar makeBar() { return new BarMock() }
public Qux makeQux() { return new Qux() }
}
class Foo {
private Bar bar;
private Qux qux;
public Foo(DependencyManager dm) {
bar = dm.makeBar();
qux = dm.makeQux();
}
}
これに関する残りの問題は、DependencyManager
構成ごとに新しいサブクラスを作成する必要があること、および管理できる依存関係の数がかなり制限されていることです(新しい依存関係ごとにインターフェイスに新しいメソッドが必要です)。
リフレクションや動的クラスロードなどの機能を使用すると、これを回避できます。しかし、これは使用する言語に大きく依存します。Perlでは、クラスは名前で参照できます。
package Foo {
use signatures;
sub new($class, $dm) {
return bless {
bar => $dm->{bar}->new,
qux => $dm->{qux}->new,
} => $class;
}
}
my $prod = { bar => 'My::Bar', qux => 'My::Qux' };
my $test = { bar => 'BarMock', qux => 'QuxMock' };
$test->{bar} = 'OtherBarMock'; # change conf at runtime
my $foo = Foo->new(rand > 0.5 ? $prod : $test);
Javaのような言語では、依存関係マネージャーを次のように動作させることができますMap<Class, Object>
。
Bar bar = dm.make(Bar.class);
どの実際のクラスにBar.class
解決されるかは、たとえば、Map<Class, Class>
インターフェイスを実装にマッピングするを維持することにより、実行時に構成できます。
Map<Class, Class> dependencies = ...;
public <T> T make(Class<T> c) throws ... {
// plus a lot more error checking...
return dependencies.get(c).newInstance();
}
コンストラクターの記述には、まだ手動要素が関係しています。しかし、アノテーションを介してDIを駆動するなどして、コンストラクターを完全に不要にすることができます。
class Foo {
@Inject(Bar.class)
private Bar bar;
@Inject(Qux.class)
private Qux qux;
...
}
dm.make(Foo.class); // takes care of initializing "bar" and "qux"
:ここで小さな(そして非常に制限された)DIフレームワークの実装例であるhttp://ideone.com/b2ubuFこの実装は不変オブジェクトの完全に使用不能であるが(このナイーブな実装はコンストラクタのパラメータを取ることができません)。