依赖注入和战略模式 [英] Dependency Injection and the Strategy Pattern

查看:132
本文介绍了依赖注入和战略模式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



我还想添加一个假设,它不是一个必须选择的策略。相反,我可能需要检索在图形的整个节点中发现的几种策略的对象图。



我将首先快速概述两个常用的解决方案,那么我将介绍我想看到IOC容器支持的明显的替代方案。我将使用Unity作为示例语法,尽管我的问题不是Unity所特有的。



命名绑定



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

  Container.RegisterType< IDataAccess,DefaultAccessor>(); 
Container.RegisterType< IDataAccess,AlphaAccessor>(Alpha);
Container.RegisterType< IDataAccess,BetaAccessor>(Beta);

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

  var strategy = Container.Resolve< IDataAccess>(Alpha); 




  • 优点:简单,由所有IOC容器支持

  • 缺点:$ b​​ $ b

    • 通常将调用者绑定到IOC容器,并且肯定需要调用者了解策略的一些内容(如名称Alpha )。

    • 每个新策略都必须手动添加到绑定列表中。

    • 此方法不适合处理对象中的多个策略图形。简单来说,它不符合要求。




抽象工厂



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

  public class DataAccessFactory {
public IDataAccess Create(string strategy){
return //在此插入适当的创建逻辑。
}
public IDataAccess Create(){
return //通过环境上下文(如线程本地存储)选择策略。
}
}
public class Consumer
{
public Consumer(DataAccessFactory datafactory)
{
//变体#1。不足以满足要求。
var myDataStrategy = datafactory.Create(Alpha);
//变体#2。这对于要求是足够的。
var myDataStrategy = datafactory.Create();
}
}

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

  Container.RegisterType< DataAccessFactory>(); 




  • 优点:$ b​​ $ b

    • IOC容器从消费者隐藏

    • 环境上下文更接近所需的结果,但...


  • 缺点:$ b​​ $ b

    • 每个策略的构造函数可能有不同的需求。但现在,建造者注入的责任已经从容器转移到抽象工厂。换句话说,每当添加新的策略时,可能需要修改相应的抽象工厂。

    • 大量使用策略意味着大量创建抽象工厂。如果IOC容器只是给了一个小的更多的帮助,那将是很好的。

    • 如果这是一个多线程的应用程序,并且确实提供了环境上下文通过线程本地存储,那么当一个对象正在使用注入的抽象工厂来创建所需的类型时,它可能在不再具有访问必要的线程本地存储值的不同线程上操作。




类型切换/动态绑定



这是我想要使用的方法,而不是上述两种方法。它涉及提供代表作为IOC容器绑定的一部分。大多数所有IOC容器都已经具备了这种能力,但是这种具体方法有着重要的微妙差别。



语法将是这样的:

  Container.RegisterType (typeof(IDataAccess),
new InjectionStrategy((c)=>
{
//访问环境上下文(pehaps thread-local-storage)来确定
//类型的策略...
类型selectedStrategy = ...;
返回selectedStrategy;
})
);

请注意, InjectionFactory 返回 IDataAccess 的实例。而是返回一个实现 IDataAccess 的类型描述。然后,IOC容器将执行该类型的通常创建和建立,这可能包括选择的其他策略。



这与标准类型 - 在Unity的情况下,这个代码绑定是这样编码的:

  Container.RegisterType(typeof(IDataAccess) 
new InjectionFactory((c)=>
{
//访问环境上下文(pehaps thread-local-storage)来确定
//策略的类型..
IDataAccess instanceOfelectedStrategy = ...;
return instanceOfelectedStrategy;
})
);

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



关注第一个样本(使用假设的Unity InjectionStrategy


  • 优点:$ b​​ $ b

    • 隐藏容器

    • 无需创建无尽的抽象工厂,或者让消费者解决它们。

    • 每当有新的策略可用时,不需要手动调整IOC容器绑定。 / li>
    • 允许容器保留终身管理控件。

    • 支持纯DI故事,这意味着多线程应用可以创建整个对象 - 在线程上的图形具有正确的线程本地存储设置。


  • 缺点:$ b​​ $ b

    • 因为当创建初始IOC容器绑定时,该策略返回的code>不可用,这意味着第一次返回该类型时可能会发生微小的性能命中。换句话说,容器必须现场反映类型以发现它具有什么构造函数,以便它知道如何注入它。该类型的所有后续事件应该是快速的,因为容器可以缓存从第一次发现的结果。这不是一个值得一提的con,但我正在尝试全面披露。

    • ???




是否有现有的IOC容器可以这样做?任何人都有一个Unity自定义注入类来实现这种效果?

解决方案

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

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





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


There is an enormous amount of discussion on this topic, but everyone seems to miss an obvious answer. I'd like help vetting this "obvious" IOC container solution. The various conversations assume run-time selection of strategies and the use of an IOC container. I will continue with these assumptions.

I also want to add the assumption that it is not a single strategy that must be selected. Rather, I might need to retrieve an object-graph that has several strategies found throughout the nodes of the graph.

I will first quickly outline the two commonly proposed solutions, and then I will present the "obvious" alternative that I'd like to see an IOC container support. I will be using Unity as the example syntax, though my question is not specific to Unity.

Named Bindings

This approach requires that every new strategy has a binding manually added:

Container.RegisterType<IDataAccess, DefaultAccessor>();
Container.RegisterType<IDataAccess, AlphaAccessor>("Alpha");
Container.RegisterType<IDataAccess, BetaAccessor>("Beta");

...and then the correct strategy is explicitly requested:

var strategy = Container.Resolve<IDataAccess>("Alpha");

  • Pros: Simple, and supported by all IOC Containers
  • Cons:
    • Typically binds the caller to the IOC Container, and certainly requires the caller to know something about the strategy (such as the name "Alpha").
    • Every new strategy must be manually added to the list of bindings.
    • This approach is not suitable for handling multiple strategies in an object graph. In short, it does not meet requirements.

Abstract Factory

To illustrate this approach, assume the following classes:

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();
    }
}

The IOC Container then has the following binding:

Container.RegisterType<DataAccessFactory>();

  • Pros:
    • The IOC Container is hidden from consumers
    • The "ambient context" is closer to the desired result but...
  • Cons:
    • The constructors of each strategy might have different needs. But now the responsibility of constructor injection has been transferred to the abstract factory from the container. In other words, every time a new strategy is added it may be necessary to modify the corresponding abstract factory.
    • Heavy use of strategies means heavy amounts of creating abstract factories. It would be nice if the IOC container simply gave a little more help.
    • If this is a multi-threaded application and the "ambient context" is indeed provided by thread-local-storage, then by the time an object is using an injected abstract-factory to create the type it needs, it may be operating on a different thread which no longer has access to the necessary thread-local-storage value.

Type Switching / Dynamic Binding

This is the approach that I want to use instead of the above two approaches. It involves providing a delegate as part of the IOC container binding. Most all IOC Containers already have this ability, but this specific approach has an important subtle difference.

The syntax would be something like this:

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

Notice that the InjectionFactory is not returning an instance of IDataAccess. Instead it is returning a type description that implements IDataAccess. The IOC Container would then perform the usual creation and "build up" of that type, which might include other strategies being selected.

This is in contrast to the standard type-to-delegate binding which, in the case of Unity, is coded like this:

Container.RegisterType(typeof(IDataAccess),
    new InjectionFactory((c) =>
    {
        //Access ambient context (pehaps thread-local-storage) to determine
        //the type of the strategy...
        IDataAccess instanceOfelectedStrategy = ...;
        return instanceOfelectedStrategy;
    })
);

The above actually comes close to satisfying the overall need, but definitely falls short of the hypothetical Unity InjectionStrategy.

Focusing on the first sample (which used a hypothetical Unity InjectionStrategy):

  • Pros:
    • Hides the container
    • No need either to create endless abstract factories, or have consumers fiddle with them.
    • No need to manually adjust IOC container bindings whenever a new strategy is available.
    • Allows the container to retain lifetime management controls.
    • Supports a pure DI story, which means that a multi-threaded app can create the entire object-graph on a thread with the proper thread-local-storage settings.
  • Cons:
    • Because the Type returned by the strategy was not available when the initial IOC container bindings were created, it means there may be a tiny performance hit the first time that type is returned. In other words, the container must on-the-spot reflect the type to discover what constructors it has, so that it knows how to inject it. All subsequent occurrences of that type should be fast, because the container can cache the results it found from the first time. This is hardly a "con" worth mentioning, but I'm trying for full-disclosure.
    • ???

Is there an existing IOC container that can behave this way? Anyone have a Unity custom injection class that achieves this effect?

解决方案

As far as I can tell, this question is about run-time selection or mapping of one of several candidate Strategies.

There's no reason to rely on a DI Container to do this, as there are at least three ways to do this in a container-agnostic way:

My personal preference is the Partial Type Name Role Hint.

这篇关于依赖注入和战略模式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆