我们希望将Unity用于IOC。 我所看到的是有一个全局静态服务(我们称之为IOCService)的实现,它包含对Unity容器的引用,该容器注册所有接口/类组合,每个类都询问该对象:给我一个实现为Ithis或IThat。
我经常看到这种模式不好的响应,因为它导致从ALL类到IOCService的依赖(而不是Unity容器,因为它只在IOCService内部知道)。
但我经常看不到的是:替代方式是什么?
米歇尔
编辑:发现全局静态服务被称为服务定位器,将其添加到标题中。
答案 0 :(得分:11)
另一种方法是在最高应用程序级别只有单个容器实例,然后使用该容器解析您需要在该层中创建的每个对象实例。
例如,大多数可执行文件的主要方法看起来像这样(减去异常处理):
private static void main(string[] args) {
Container container = new Container();
// Configure the container - by hand or via file
IProgramLogic logic = container.Resolve<IProgramLogic>();
logic.Run();
}
你的程序(由IProgramLogic
实例表示)不需要知道你的容器,因为container.Resolve
将创建它的所有依赖项 - 及其依赖项的依赖项,一直到叶子没有自己依赖的类。
ASP.NET是一个更难的案例,因为Web表单不支持构造函数注入。我通常在我的Web表单应用程序中使用Model-View-Presenter,因此我的Page
类实际上只有一个依赖项 - 在他们的演示者上。我没有对它们进行单元测试(所有有趣且可测试的都在我的演示者中,我做测试),我不会替代演示者。所以我不打击框架 - 我只是在我的HttpApplication
类(在global.asax.cs中)公开一个容器属性,并直接从我的Page
文件中使用它:
protected void Page_Load(object sender, EventArgs args) {
ICustomerPresenter presenter = Global.Container.Resolve<ICustomerPresenter>();
presenter.Load();
}
这当然是服务定位器 - 虽然Page
类是唯一与定位器耦合的类:您的演示者及其所有依赖项仍然与IoC容器实现完全分离。
如果您的Page
文件中有很多依赖项(即,如果您不使用Model-View-Presenter),或者您要将Page
类与您的Global
应用程序类,您应该尝试找到一个集成到Web表单请求管道中并使用属性注入的框架(如下面的评论中的Nicholas所建议的那样) - 或编写您自己的IHttpModule
并执行财产注入自己。
答案 1 :(得分:7)
+1 了解服务定位器是一件坏事。
问题是 - Unity不是很复杂,所以我不知道以正确的方式使用它是多么容易/多难。
我最近写了一些博客文章,你可能觉得它很有用。
答案 2 :(得分:5)
而不是显式使用容器,而是通过利用构造函数/属性注入隐式使用它。创建一个依赖于应用程序所有主要部分的核心类(或核心类集)。
大多数容器都允许您将ISomething[]
放入构造函数中,它会将ISomething
的所有实例注入您的类中。
这样,当您引导应用程序时:
答案 3 :(得分:4)
理论上,为了不必担心拥有静态IoC实例,您需要遵循搏击俱乐部规则 - 即不要谈论战斗俱乐部 - 即更不用说IoC容器。
这意味着您的组件应该在很大程度上不了解IoC容器。它只应在注册组件时在最顶层使用。如果一个类需要解决某些问题,那么它应该作为依赖项注入。
这个简单的案例很容易。如果PaymentService
取决于IAccount
,后者应由IoC注入:
interface IAccount {
Deposit(int amount);
}
interface CreditCardAccount : IAccount {
void Deposit(int amount) {/*implementation*/}
int CheckBalance() {/*implementation*/}
}
class PaymentService {
IAccount account;
public PaymentService (IAccount account) {
this.account = account;
}
public void ProcessPayment() {
account.Deposit(5);
}
}
//Registration looks something like this
container.RegisterType<IAccount, CreditCardAccount>();
container.RegisterType<PaymentService>();
不是那么简单的案例就是你要注入多个注册的地方。当您进行任何类型的Converntion Over Configuration并从名称创建对象时,这尤其适用。
对于我们的付款示例,假设您要枚举所有帐户并检查其余额:
class PaymentService {
IEnumerable<IAccount> accounts;
public PaymentService (IEnumerable<IAccount> accounts) {
this.accounts = accounts;
}
public void ProcessPayment() {
foreach(var account in accounts) {
account.Chackbalance();
}
}
}
Unity能够为类映射注册多个接口(它们必须考虑不同的名称)。但是,它不会自动将这些注入到接收这些已注册接口集合的类中。因此,上面的示例将在运行时抛出一个解决方案失败的异常。
如果您不关心这些对象永远存在,您可以以更静态的方式注册PaymentService
:
container.RegisterType<PaymentService>(new InjectionConstructor(container.ResolveAll<IAccount>()));
上述代码将注册PaymentService
,并将使用在注册时解析的IAccount
个实例集合。
或者,您可以将容器本身的实例作为依赖项传递,让PaymentService
执行帐户解析。这并不完全遵循搏击俱乐部规则,但是比静态服务定位器稍微不那么臭。
class PaymentService {
IEnumerable<IAccount> accounts;
public PaymentService (IUnityContainer container) {
this.accounts = container.ResolveAll<IAccount>();
}
public void ProcessPayment() {
foreach(var account in accounts) {
account.Chackbalance();
}
}
}
//Registration is pretty clean in this case
container.RegisterType<IAccount, CreditCardAccount>();
container.RegisterType<PaymentService>();
container.RegisterInstance<IUnityContainer>(container);
答案 4 :(得分:2)
如果您的关注点在整个应用程序中依赖于Unity,则可以将服务定位器与外观相结合以隐藏IOC实现。通过这种方式,您不会在应用程序中创建对Unity的依赖关系,只需要具有可以为您解析类型的某些。
例如:
public interface IContainer
{
void Register<TAbstraction,TImplementation>();
void RegisterThis<T>(T instance);
T Get<T>();
}
public static class Container
{
static readonly IContainer container;
public static InitializeWith(IContainer containerImplementation)
{
container = containerImplementation;
}
public static void Register<TAbstraction, TImplementation>()
{
container.Register<TAbstraction, TImplementation>();
}
public static void RegisterThis<T>(T instance)
{
container.RegisterThis<T>(instance);
}
public static T Get<T>()
{
return container.Get<T>();
}
}
现在您只需要为您选择的IOC容器实现IContainer
。
public class UnityContainerImplementation : IContainer
{
IUnityContainer container;
public UnityContainerImplementation(IUnityContainer container)
{
this.container = container;
}
public void Register<TAbstraction, TImplementation>()
{
container.Register<TAbstraction, TImplementation>();
}
public void RegisterThis<T>(T instance)
{
container.RegisterInstance<T>(instance);
}
public T Get<T>()
{
return container.Resolve<T>();
}
}
现在您有一个服务定位器,它是IOC服务的外观,并且可以将您的服务定位器配置为使用Unity或任何其他IOC容器。应用程序的其余部分不依赖于IOC实现。
配置服务定位器:
IUnityContainer unityContainer = new UnityContainer();
UnityContainerImplementation containerImpl = new UnityContainerImplementation(unityContainer);
Container.InitializeWith(containerImpl);
为了进行测试,您可以创建一个IContainer
的存根,它可以返回您想要的任何内容,并使用它初始化Container
。