本文介绍了IOC容器处理非默认构造函数中的状态参数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

为了讨论的目的,对象构造函数可能采用两种参数:状态依赖或服务依赖。为IOC容器提供服务依赖很容易:DI接管。但是相比之下,状态依赖通常只有客户端才知道。也就是说,对象请求者。



事实证明,有一个客户端通过IOC容器提供状态参数是相当痛苦的。我将展示几种不同的方式来做到这一点,所有这些都有很大的问题,并问社区如果有另一个选项,我错过。让我们开始:



在我添加一个IOC容器到我的项目代码之前,我开始使用这样的类:

 类Foobar {
//参数是状态依赖,而不是服务依赖
public Foobar(string alpha,int omega){...}
//...other stuff
}

我决定添加记录器对Foobar类的服务依赖性,也许我将通过DI提供:

 类Foobar {
public Foobar (string alpha,int omega,ILogger log){...};
//...other stuff
}

也告诉我需要让类Foobar本身可交换。也就是说,我需要服务 - 找到一个Foobar实例。我在组合中添加了一个新界面:

 类Foobar:IFoobar {
public Foobar(string alpha,int omega ,ILogger log){...};
//...other stuff
}

定位器调用,它将为我ILogger服务依赖。不幸的是,状态依赖性Alpha和Omega也不是这样。一些容器提供了一种语法来解决这个问题:

  // Unity 2.0 pseudo-ish代码:
myContainer.Resolve< IFoobar>(
new parameterOverride [] {{alpha,one},{omega,2}}
);

我喜欢这个功能,但我不喜欢它是无类型的,什么参数必须传递(通过智能感知等)。所以我看看另一个解决方案:

  //这是一个锅炉板 
class Foobar:IFoobar {
public Foobar(string alpha,int omega){...};
//...stuff
}
类FoobarFactory:IFoobarFactory {
public IFoobar IFoobarFactory.Create(string alpha,int omega){
return new Foobar ,omega);
}
}

// fetch it ...
myContainer.Resolve< IFoobarFactory>()。Create(one,2);以上解决了类型安全和智能感知问题,但它(1)强制类Foobar获取一个ILogger通过服务定位器而不是DI和(2)它要求我做一堆锅炉板(XXXFactory,IXXXFactory)的所有品种的Foobar实现我可能使用。如果我决定使用纯服务定位器方法,它可能不是一个问题。



因此,我尝试另一种容器提供支持的变体:

  //总体来说,这是一个伪纪念模式。 
class Foobar:IFoobar {
public Foobar(FoobarParams parms){
this.alpha = parms.alpha;
this.omega = parms.omega;
};
//...stuff
}

类FoobarParams {
public FoobarParams(string alpha,int omega){...}
}

//获取一个实例:
FoobarParams parms = new FoobarParams(one,2);
// Unity 2.0伪代码...
myContainer.resolve< IFoobar>(new parameterOverride(parms));

使用这种方法,我已经半途恢复了我的智能感知。但我必须等到运行时检测到错误,我可能会忘记提供FoobarParams参数。



因此,让我们尝试一种新方法:

  具体创建者
类Foobar:IFoobar {
public Foobar(string alpha,int omega,ILogger log){...}
static IFoobar Create(string alpha,int omega){
// unity 2.0 pseudo-ish code。假设一个公共的
//服务定位器,或单例包含容器...
return Container.Resolve< IFoobar>(
new parameterOverride [] {{alpha,alpha} omega,omega}}
);
}

//获取我的实例:
Foobar.Create(alpha,2);



我实际上不介意我使用具体的Foobar类创建一个IFoobar 。它代表基本的概念,我不希望在我的代码中改变。我也不介意在静态创建中缺少类型安全,因为它现在被封装。我的intellisense工作也!任何具体实例以这种方式将忽略提供的状态参数,如果他们不适用(Unity 2.0行为)。也许一个不同的具体实现FooFoobar可能有一个正式的参数名称不匹配,但我仍然很高兴。



但这个方法的大问题是,它只能有效地与Unity 2.0(在结构图中的一个不匹配的参数将抛出异常)。所以这是好的,只有如果我留在Unity。问题是,我开始喜欢结构图更多。所以现在我还有一个选项:

 类Foobar:IFoobar,IFoobarInit {
public Foobar(ILogger log) {...};

public IFoobar IFoobarInit.Initialize(string alpha,int omega){
this.alpha = alpha;
this.omega = omega;
return this;
}
}

//现在创建...
IFoobar foo = myContainer.resolve< IFoobarInit>()。初始化(one

现在有了这个我有一个不错的妥协与其他方法:是类型安全/智能感知(2)我可以选择通过DI(如上所示)或服务定位器获取ILogger,(3)没有必要使一个或多个单独的具体的FoobarFactory类(与详细的锅炉板示例代码),和(4)它合理地坚持使接口容易正确使用,并且难以使用不正确的原则。



一个接受障碍仍然存在:我也想要应用按合同设计。



我提出的每个示例都是有意地支持构造函数注入(对于状态依赖),因为我想保留不变支持作为最常用的。即,当构造函数完成时建立不变量。



在上面的示例中,当对象构造完成时,不会建立不变量。只要我在做自己的合同设计,我可以告诉开发者不要测试不变量,直到调用Initialize(...)方法。



但更重要的是,当.net 4.0出来时,我想使用它的代码契约支持合同的设计。从我读到的,它不会与最后一种方法兼容。



Curses!



也发生在我,我的整个哲学是关闭。也许我会被告知,通过服务定位器调用一个Foobar:IFoobar意味着它是一个服务 - 服务只有其他服务依赖,它们没有状态依赖(例如这些示例的Alpha和Omega)。我也愿意倾听这样的哲学问题,但我也想知道半阅读的参考资料是什么,这将引导我沿着这条思路走。



现在我把它转到社区。我应该考虑什么方法?我真的相信我已经用尽了我的选择吗?



p.s。这种问题,一起,促使我相信整个IOC容器的想法,在至少在.net中,是过早的。它让我想起了人们将站在他们的头上,使C语言感觉面向对象(添加奇怪的宏等)的日子。我们应该寻找的是CLR和IOC容器的语言支持。例如,想象一种称为启动的新型接口。启动的工作方式类似于接口,但也需要特定的构造函数签名。其余的我作为练习留给学生...

解决方案

您正在寻找的是或Func< T,U>。



需要在运行中创建另一组件的参数化实例的组件使用Func&依赖类型表示:

  class Bar 
{
Func< string,int,IFoo& _fooCreator;

public Bar(Func< string,int,IFoo> fooCreator){
_fooCreator = fooCreator;
}

public void Go(){
var foo = _fooCreator(a,42);
// ...
}
}

Autofac,例如,每当IFoo注册时,将自动提供Func。它还会将参数合并到Foo构造函数(包括ILogger)中,并丢弃任何不必要的而不是引发错误。



Autofac还支持自定义委托, p>

 委托IFoo FooCreator(string alpha,int omega); 

这种方式可以重写:

  class Bar 
{
FooCreator _fooCreator;

public Bar(FooCreator fooCreator){
_fooCreator = fooCreator;
}

public void Go(){
var foo = _fooCreator(a,42);
// ...
}
}


Autofac有一些文档,你可以检查:。



在名为Common Context Adapters的几个IOC容器社区之间有一个协作项目来标准化这些和其他高阶依赖关系类型。项目网站位于。



CCA提出的第一个规范涵盖了工厂适配器,您可以在这里阅读:。



其他一些链接有用:






我希望你会发现,虽然IoC容器承认有很长的路要走,他们将完全透明,我们实际上努力到那里:)



Nick


For the purpose of this discussion, there are two kinds of parameters an object constructor might take: state dependency or service dependency. Supplying a service dependency with an IOC container is easy: DI takes over. But in contrast, state dependencies are usually only known to the client. That is, the object requestor.

It turns out that having a client supply the state params through an IOC Container is quite painful. I will show several different ways to do this, all of which have big problems, and ask the community if there is another option I'm missing. Let's begin:

Before I added an IOC container to my project code, I started with a class like this:

class Foobar {
   //parameters are state dependencies, not service dependencies
   public Foobar(string alpha, int omega){...};
   //...other stuff
}

I decide to add a Logger service depdendency to the Foobar class, which perhaps I'll provide through DI:

class Foobar {
    public Foobar(string alpha, int omega, ILogger log){...};
    //...other stuff
}

But then I'm also told I need to make class Foobar itself "swappable." That is, I'm required to service-locate a Foobar instance. I add a new interface into the mix:

class Foobar : IFoobar {
    public Foobar(string alpha, int omega, ILogger log){...};
    //...other stuff
}

When I make the service locator call, it will DI the ILogger service dependency for me. Unfortunately the same is not true of the state dependencies Alpha and Omega. Some containers offer a syntax to address this:

//Unity 2.0 pseudo-ish code:
myContainer.Resolve<IFoobar>(
   new parameterOverride[] { {"alpha", "one"}, {"omega",2} }
);

I like the feature, but I don't like that it is untyped and not evident to the developer what parameters must be passed (via intellisense, etc). So I look at another solution:

//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);

The above solves the type-safety and intellisense problem, but it (1) forced class Foobar to fetch an ILogger through a service locator rather than DI and (2) it requires me to make a bunch of boiler-plate (XXXFactory, IXXXFactory) for all varieties of Foobar implementations I might use. Should I decide to go with a pure service locator approach, it may not be a problem. But I still can't stand all the boiler-plate needed to make this work.

So then I try another variation of container-provided support:

//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) );

With this approach, I've half-way recovered my intellisense. But I must wait till run-time to detect the error where I might forget to supply the "FoobarParams" parameter.

So let's try a new approach:

//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);

I actually don't mind that I'm using the concrete "Foobar" class to create an IFoobar. It represents a base concept that I don't expect to change in my code. I also don't mind the lack of type-safety in the static "Create", because it is now encapsulated. My intellisense is working too! Any concrete instance made this way will ignore the supplied state params if they don't apply (a Unity 2.0 behavior). Perhaps a different concrete implementation "FooFoobar" might have a formal arg name mismatch, but I'm still pretty happy with it.

But the big problem with this approach is that it only works effectively with Unity 2.0 (a mismatched parameter in Structure Map will throw an exception). So it is good only if I stay with Unity. The problem is, I'm beginning to like Structure Map a lot more. So now I go onto yet another option:

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)

Now with this I've got a somewhat nice compromise with the other approaches: (1) My arguments are type-safe / intellisense aware (2) I have a choice of fetching the ILogger via DI (shown above) or service locator, (3) there is no need to make one or more seperate concrete FoobarFactory classes (contrast with the verbose "boiler-plate" example code earlier), and (4) it reasonably upholds the principle "make interfaces easy to use correctly, and hard to use incorrectly." At least it arguably is no worse than the alternatives previously discussed.

One acceptance barrier yet remains: I also want to apply "design by contract."

Every sample I presented was intentionally favoring constructor injection (for state dependencies) because I want to preserve "invariant" support as most commonly practiced. Namely, the invariant is established when the constructor completes.

In the sample above, the invarient is not established when object construction completes. As long as I'm doing home-grown "design by contract" I could just tell developers not to test the invariant until the Initialize(...) method is called.

But more to the point, when .net 4.0 comes out I want to use its "code contract" support for design by contract. From what I read, it will not be compatible with this last approach.

Curses!

Of course it also occurs to me that my entire philosophy is off. Perhaps I'd be told that conjuring a Foobar : IFoobar via a service locator implies that it is a service - and services only have other service dependencies, they don't have state dependencies (such as the Alpha and Omega of these examples). I'm open to listening to such philosophical matters as well, but I'd also like to know what semi-authorative reference to read that would steer me down that thought path.

So now I turn it to the community. What approach should I consider that I havn't yet? Must I really believe I've exhausted my options?

p.s. This kind of problem, along with others, prompts me to believe the whole IOC Container idea, at least in .net, is premature. It reminds me of the days when people would stand on their heads to make the "C" language feel object-oriented (adding weird macros and such). What we should be looking for is CLR and language support of IOC Containers. For example, imagine a new kind of interface called an "initiate". An initiate works like an interface, but requires a particular constructor signature as well. The rest I leave as an exercise to the student...

解决方案

What you're looking for is the "factory adapter" or Func<T, U>.

A component that needs to create parameterised instances of another component on the fly uses the Func<T, U> dependency type to represent this:

class Bar
{
    Func<string, int, IFoo> _fooCreator;

    public Bar(Func<string, int, IFoo> fooCreator) {
        _fooCreator = fooCreator;
    }

    public void Go() {
        var foo = _fooCreator("a", 42);
        // ...
    }
}

Autofac, for example, will automatically provide the Func whenever IFoo is registered. It will also merge parameters into the Foo constructor (including ILogger,) and discard any that are unnecessary rather than throwing an error.

Autofac also supports custom delegates like:

delegate IFoo FooCreator(string alpha, int omega);

This way Bar can be rewritten:

class Bar
{
    FooCreator _fooCreator;

    public Bar(FooCreator fooCreator) {
        _fooCreator = fooCreator;
    }

    public void Go() {
        var foo = _fooCreator("a", 42);
        // ...
    }
}

When custom delegates are used, the parameters will be matched by name, rather than by type as for Func.

Autofac has some documentation you might check out: http://code.google.com/p/autofac/wiki/DelegateFactories.

There's a collaborative project in the works between several IOC container communities called "Common Context Adapters" to standardise these and other higher-order dependency types. The project site is at http://cca.codeplex.com.

The first proposed specification from CCA covers the factory adapter, you can read it here: http://cca.codeplex.com/wikipage?title=FuncFactoryScenarios&referringTitle=Documentation.

Some other links you may find helpful:

http://nblumhardt.com/2010/04/introducing-autofac-2-1-rtw/http://nblumhardt.com/2010/01/the-relationship-zoo/

I hope you'll find that while IoC containers admittedly have a long way to go before they'll be completely transparent, we're actually working quite hard to get there :)

Nick

这篇关于IOC容器处理非默认构造函数中的状态参数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-18 13:28