在本讨论中,对象构造函数可能采用两种参数:状态依赖项或服务依赖项。为ioc容器提供服务依赖性很容易:di接管。但与此相反,状态依赖通常只为客户机所知。即对象请求程序。
事实证明,让客户机通过ioc容器提供状态参数是相当痛苦的。我将展示几种不同的方法,所有这些方法都有很大的问题,并询问社区是否有另一个选项我错过了。开始吧:
在我向项目代码中添加ioc容器之前,我先从这样一个类开始:
class Foobar {
//parameters are state dependencies, not service dependencies
public Foobar(string alpha, int omega){...};
//...other stuff
}
我决定在foobar类中添加一个logger服务depdency,也许我可以通过di提供:
class Foobar {
public Foobar(string alpha, int omega, ILogger log){...};
//...other stuff
}
但是我也被告知我需要使类foobar本身“可交换”,也就是说,我需要服务定位一个foobar实例。我在组合中添加了一个新接口:
class Foobar : IFoobar {
public Foobar(string alpha, int omega, ILogger log){...};
//...other stuff
}
当我调用服务定位器时,它将为我定义ilogger服务依赖项。不幸的是,状态依赖项alpha和omega并非如此。一些容器提供了一种语法来解决这个问题:
//Unity 2.0 pseudo-ish code:
myContainer.Resolve<IFoobar>(
new parameterOverride[] { {"alpha", "one"}, {"omega",2} }
);
我喜欢这个特性,但我不喜欢它是非类型化的,开发人员不清楚必须传递哪些参数(通过intellisense等)。所以我看了另一个解决方案:
//This is a "boiler plate" heavy approach!
class Foobar : IFoobar {
public Foobar (string alpha, int omega){...};
//...stuff
}
class FoobarFactory : IFoobarFactory {
public IFoobar IFoobarFactory.Create(string alpha, int omega){
return new Foobar(alpha, omega);
}
}
//fetch it...
myContainer.Resolve<IFoobarFactory>().Create("one", 2);
上面解决了类型安全和智能感知问题,但它(1)强制类foobar通过服务定位器而不是di获取ilogger,(2)它需要我为可能使用的所有foobar实现制作一堆锅炉板(xxxfactory,ixxfactory)。如果我决定使用纯服务定位器方法,这可能不是问题。但我还是受不了做这件事所需的所有锅炉板。
所以我尝试另一种容器提供的支持:
//overall, this is a pseudo-memento pattern.
class Foobar : IFoobar {
public Foobar (FoobarParams parms){
this.alpha = parms.alpha;
this.omega = parms.omega;
};
//...stuff
}
class FoobarParams{
public FoobarParams(string alpha, int omega){...};
}
//fetch an instance:
FoobarParams parms = new FoobarParams("one",2);
//Unity 2.0 pseudo-code...
myContainer.resolve<IFoobar>(new parameterOverride(parms) );
通过这种方法,我已经有一半恢复了我的智力。但我必须等到运行时才能检测到可能忘记提供“foobarparams”参数的错误。
所以让我们尝试一种新方法:
//code named "concrete creator"
class Foobar : IFoobar {
public Foobar(string alpha, int omega, ILogger log){...};
static IFoobar Create(string alpha, int omega){
//unity 2.0 pseudo-ish code. Assume a common
//service locator, or singleton holds the container...
return Container.Resolve<IFoobar>(
new parameterOverride[] {{"alpha", alpha},{"omega", omega} }
);
}
//Get my instance:
Foobar.Create("alpha",2);
实际上,我并不介意使用具体的“foobar”类来创建一个ifoobar。它代表一个基
概念
我不想改变我的代码。我也不介意静态“create”中缺少类型安全性,因为它现在是封装的。我的智能感应也起作用了!如果不应用提供的状态参数(Unity2.0行为),则以这种方式创建的任何具体实例都将忽略这些参数。也许一个不同的具体实现“foofoobar”可能有一个形式上的arg名称不匹配,但我仍然非常满意。
但是这种方法的最大问题是它只在Unity2.0中有效工作(结构映射中不匹配的参数将引发异常)。所以只有团结一致才好。问题是,我开始更喜欢结构图了。所以现在我再来一个选择:
class Foobar : IFoobar, IFoobarInit {
public Foobar(ILogger log){...};
public IFoobar IFoobarInit.Initialize(string alpha, int omega){
this.alpha = alpha;
this.omega = omega;
return this;
}
}
//now create it...
IFoobar foo = myContainer.resolve<IFoobarInit>().Initialize("one", 2)
现在,我有了一个很好的折衷方案:(1)我的参数是类型安全/智能感知的(2)我可以选择通过DI(如上图所示)或服务定位器获取ILogger,(3)不需要创建一个或多个单独的具体FoobarFactory类(contrast使用了前面冗长的“锅炉板”示例代码,并且(4)它合理地坚持了“使接口易于正确使用,并且难以错误使用”的原则,至少可以说它并不比前面讨论的替代方案差。
一个验收障碍仍然存在:我还想申请“按合同设计”。
我提供的每个示例都有意支持构造函数注入(对于状态依赖项),因为我希望保留最常用的“不变”支持。也就是说,不变量是在构造函数完成时建立的。
在上面的示例中,对象构造完成时不建立不变量。只要我在做自己开发的“契约式设计”,我就可以告诉开发人员在调用initialize(…)方法之前不要测试不变量。
但更重要的是,当.net 4.0问世时,我想使用它的“代码契约”支持按契约设计。从我所读到的来看,这与最后一种方法是不相容的。
诅咒!
当然,我也会想到,我的整个人生观都被抛弃了。也许我会被告知,通过服务定位器变戏法foobar:ifoobar意味着它是一个服务——而且服务只有其他服务依赖项,它们没有状态依赖项(比如这些示例中的alpha和omega)。我也愿意倾听这样的哲学问题,但我也想知道什么半权威性的参考阅读会引导我走上这条思路。
所以现在我把话题转向社区。我应该考虑什么方法我还没有呢?我真的必须相信我已经用尽了我的选择吗?
P.S.这类问题,
along with others
,提示我相信整个ioc容器的想法,至少在.net中是不成熟的。它让我想起了人们会站在自己的头上让“C”语言感觉到面向对象(添加奇怪的宏等等)。我们应该寻找的是clr和ioc容器的语言支持。例如,想象一种叫做“initiate”的新接口。initiate的工作方式类似于接口,但也需要特定的构造函数签名。剩下的作为练习留给学生…