如何实现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());
这已经是依赖注入的最简单形式。但这仍然很糟糕,因为一切都必须手动完成(而且,因为调用者可以保留对我们内部对象的引用,从而使我们的状态无效)。
我们可以使用工厂来引入更多抽象:
一种选择是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,尽管该实现对于不可变对象完全不可用(该幼稚的实现不能为构造函数使用任何参数)。