依赖注入与策略模式

Bre*_*ias 18 .net c# dependency-injection ioc-container strategy-pattern

关于这个话题的讨论很多,但每个人似乎都错过了一个明显的答案.我想帮助审查这个"明显的"IOC容器解决方案.各种对话假设运行时选择策略和使用IOC容器.我将继续这些假设.

我还想补充一个假设,即它不是必须选择的单一策略.相反,我可能需要检索一个对象图,该对象图在整个图的节点中找到了几个策略.

我将首先快速概述两个常用的解决方案,然后我将展示我希望看到IOC容器支持的"明显"替代方案.我将使用Unity作为示例语法,但我的问题不是Unity特有的.

命名绑定

这种方法要求每个新策略都手动添加绑定:

Container.RegisterType<IDataAccess, DefaultAccessor>();
Container.RegisterType<IDataAccess, AlphaAccessor>("Alpha");
Container.RegisterType<IDataAccess, BetaAccessor>("Beta");
Run Code Online (Sandbox Code Playgroud)

......然后明确要求正确的策略:

var strategy = Container.Resolve<IDataAccess>("Alpha");
Run Code Online (Sandbox Code Playgroud)
  • 优点:简单,并得到所有IOC容器的支持
  • 缺点:
    • 通常将调用者绑定到IOC容器,当然要求调用者知道有关策略的信息(例如名称"Alpha").
    • 必须手动将每个新策略添加到绑定列表中.
    • 此方法不适合处理对象图中的多个策略.简而言之,它不符合要求.

抽象工厂

为了说明这种方法,假设以下类:

public class DataAccessFactory{
    public IDataAccess Create(string strategy){
        return //insert appropriate creation logic here.
    }
    public IDataAccess Create(){
        return //Choose strategy through ambient context, such as thread-local-storage.
    }
}
public class Consumer
{
    public Consumer(DataAccessFactory datafactory)
    {
        //variation #1. Not sufficient to meet requirements.
        var myDataStrategy = datafactory.Create("Alpha");
        //variation #2.  This is sufficient for requirements.
        var myDataStrategy = datafactory.Create();
    }
}
Run Code Online (Sandbox Code Playgroud)

然后IOC容器具有以下绑定:

Container.RegisterType<DataAccessFactory>();
Run Code Online (Sandbox Code Playgroud)
  • 优点:
    • IOC容器对消费者隐藏
    • "环境背景"更接近预期的结果,但......
  • 缺点:
    • 每种策略的构造者可能有不同的需求.但是现在构造函数注入的责任已从容器转移到抽象工厂.换句话说,每次添加新策略时,可能需要修改相应的抽象工厂.
    • 大量使用策略意味着大量创建抽象工厂.如果IOC容器只是给了一点帮助,那就太好了.
    • 如果这是一个多线程应用程序并且"环境上下文"确实由线程本地存储提供,那么当对象使用注入的抽象工厂来创建它需要的类型时,它可能正在运行不同的线程,不再有权访问必要的线程本地存储值.

类型切换/动态绑定

这是我想要使用的方法而不是上述两种方法.它涉及提供委托作为IOC容器绑定的一部分.大多数IOC容器都具备此功能,但这种特定方法具有重要的细微差别.

语法如下:

Container.RegisterType(typeof(IDataAccess),
    new InjectionStrategy((c) =>
    {
        //Access ambient context (perhaps thread-local-storage) to determine
        //the type of the strategy...
        Type selectedStrategy = ...;
        return selectedStrategy;
    })
);
Run Code Online (Sandbox Code Playgroud)

请注意,InjectionStrategy不是返回的一个实例IDataAccess.相反,它返回一个实现的类型描述IDataAccess.然后,IOC容器将执行该类型的常规创建和"构建",其可能包括选择的其他策略.

这与标准的类型到委托绑定形成对比,在Unity的情况下,它的编码如下:

Container.RegisterType(typeof(IDataAccess),
    new InjectionFactory((c) =>
    {
        //Access ambient context (perhaps thread-local-storage) to determine
        //the type of the strategy...
        IDataAccess instanceOfSelectedStrategy = ...;
        return instanceOfSelectedStrategy;
    })
);
Run Code Online (Sandbox Code Playgroud)

上述实际上接近满足整体需求,但绝对不能满足假设的Unity InjectionStrategy.

专注于第一个样本(使用假设的Unity InjectionStrategy):

  • 优点:
    • 隐藏容器
    • 无需创建无穷无尽的抽象工厂,或让消费者摆弄它们.
    • 只要有新策略,就无需手动调整IOC容器绑定.
    • 允许容器保留生命周期管理控件.
    • 支持纯DI故事,这意味着多线程应用程序可以使用正确的线程本地存储设置在线程上创建整个对象图.
  • 缺点:
    • 因为在Type创建初始IOC容器绑定时策略返回的结果不可用,这意味着第一次返回该类型时可能会遇到微小的性能损失.换句话说,容器必须在现场反映类型以发现它具有的构造函数,以便它知道如何注入它.该类型的所有后续出现都应该很快,因为容器可以缓存第一次找到的结果.这不是一个值得一提的"骗局",但我正在努力进行全面披露.
    • ???

是否存在可以以这种方式运行的现有IOC容器?任何人都有Unity自定义注入类来实现这种效果?

Mar*_*ann 16

据我所知,这个问题是关于运行时选择或几个候选策略之一的映射.

没有理由依赖DI容器来执行此操作,因为至少有三种方法可以以容器无关的方式执行此操作:

我个人的偏好是部分类型名称角色提示.