代码之家  ›  专栏  ›  技术社区  ›  Brent Arias

非默认构造函数中的ioc容器处理状态参数

  •  4
  • Brent Arias  · 技术社区  · 15 年前

    在本讨论中,对象构造函数可能采用两种参数:状态依赖项或服务依赖项。为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的工作方式类似于接口,但也需要特定的构造函数签名。剩下的作为练习留给学生…

    2 回复  |  直到 15 年前
        1
  •  3
  •   Nicholas Blumhardt    15 年前

    您要找的是“工厂适配器”或func<t,u>。

    需要动态创建另一个组件的参数化实例的组件使用func<t,u>依赖类型来表示:

    class Bar
    {
        Func<string, int, IFoo> _fooCreator;
    
        public Bar(Func<string, int, IFoo> fooCreator) {
            _fooCreator = fooCreator;
        }
    
        public void Go() {
            var foo = _fooCreator("a", 42);
            // ...
        }
    }
    

    例如,当ifoo注册时,autofac将自动提供func。它还将把参数合并到foo构造函数(包括ilogger,)中,并丢弃任何不必要的参数,而不是抛出错误。

    autofac还支持自定义委托,如:

    delegate IFoo FooCreator(string alpha, int omega);
    

    这样可以重写条:

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

    当使用自定义委托时,参数将按名称匹配,而不是像func那样按类型匹配。

    autofac有一些文档您可以查看: http://code.google.com/p/autofac/wiki/DelegateFactories .

    几个ioc容器社区之间正在进行一个名为“公共上下文适配器”的协作项目,以标准化这些和其他高阶依赖类型。项目地点在 http://cca.codeplex.com .

    CCA提出的第一个规范涵盖了工厂适配器,您可以在这里阅读: http://cca.codeplex.com/wikipage?title=FuncFactoryScenarios&referringTitle=Documentation .

    您可能会发现其他一些有用的链接:

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

    我希望你会发现,尽管国际奥委会的集装箱在完全透明之前还有很长的路要走,但我们实际上正在努力实现这一目标:)

    尼克

        2
  •  0
  •   Frank Schwieterman    15 年前

    如果这些参数在应用程序的生存期内是常量,则可以添加iconfigurationservice,其唯一目的是将这些参数返回给需要它们的人。IConfigurationService的实现可能有硬编码的值,请从配置文件中读取它们…无论什么。当然,iconfigurationservice的实现是通过ioc容器检索的。

    如果每个实例的这些参数都不同,那么我不认为它们应该作为ioc容器加载的对象的构造函数参数提供。这使得所有组件都需要查找/依赖ioc容器,这首先会破坏ioc容器的点。

    可以通过公开setter方法(当它们可能在对象的生命周期内更改时,这是适当的)在对象本身上配置它们,也可以将它们设置为返回对象的factory方法的参数(factory对象在ioc容器中注册)。

    你对使用工厂犹豫不决,但我认为这是一种优雅的做法。是的,创建一个工厂是需要努力的,但是由于有一个支持这个行为的需求,所以它不是代码膨胀。这是一个满足需求的简单模式。

    推荐文章