Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dependency Injection and the Strategy Pattern

Tags:

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 (perhaps thread-local-storage) to determine         //the type of the strategy...         Type selectedStrategy = ...;         return selectedStrategy;     }) ); 

Notice that the InjectionStrategy 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 (perhaps thread-local-storage) to determine         //the type of the strategy...         IDataAccess instanceOfSelectedStrategy = ...;         return instanceOfSelectedStrategy;     }) ); 

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?

like image 435
Brent Arias Avatar asked Mar 27 '14 23:03

Brent Arias


People also ask

Is dependency injection a strategy pattern?

Dependency injection is a refinement of the strategy pattern which I will briefly explain. It is often necessary to choose between several alternative modules at runtime. These modules all implement a common interface so that they can be used interchangeably.

What type of design pattern is dependency injection?

In software engineering, dependency injection is a design pattern in which an object or function receives other objects or functions that it depends on. A form of inversion of control, dependency injection aims to separate the concerns of constructing objects and using them, leading to loosely coupled programs.

What is the difference between dependency injection and factory pattern?

Dependency Injection is more of a architectural pattern for loosely coupling software components. Factory pattern is just one way to separate the responsibility of creating objects of other classes to another entity. Factory pattern can be called as a tool to implement DI.

How does dependency injection help in the software design pattern?

Dependency injection supports these goals by decoupling the creation of the usage of an object. That enables you to replace dependencies without changing the class that uses them. It also reduces the risk that you have to change a class just because one of its dependencies changed.


2 Answers

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:

  • Use a Metadata Role Hint
  • Use a Role Interface Role Hint
  • Use a Partial Type Name Role Hint

My personal preference is the Partial Type Name Role Hint.

like image 118
Mark Seemann Avatar answered Oct 09 '22 08:10

Mark Seemann


I have achieved this requirement in many forms over the last couple of years. Firstly let's pull the main points I can see in your post

assume run-time selection of strategies and the use of an IOC container ... 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 ... [must not] binds the caller to the IOC Container ... Every new strategy must [not need to] be manually added to the list of bindings ... It would be nice if the IOC container simply gave a little more help.

I have used Simple Injector as my container of choice for some time now and one of the drivers for this decision is that it has extensive support for generics. It is through this feature that we will implement your requirements.

I'm a firm believer that the code should speak for itself so I'll jump right in ...

  • I have defined an extra class ContainerResolvedClass<T> to demonstrate that Simple Injector finds the right implementation(s) and successfully injects them into a constructor. That is the only reason for the class ContainerResolvedClass<T>. (This class exposes the handlers that are injected into it for test purposes via result.Handlers.)

This first test requires that we get one implementation back for the fictional class Type1:

[Test] public void CompositeHandlerForType1_Resolves_WithAlphaHandler() {     var container = this.ContainerFactory();      var result = container.GetInstance<ContainerResolvedClass<Type1>>();     var handlers = result.Handlers.Select(x => x.GetType());      Assert.That(handlers.Count(), Is.EqualTo(1));     Assert.That(handlers.Contains(typeof(AlphaHandler<Type1>)), Is.True); } 

This second test requires that we get one implementation back for the fictional class Type2:

[Test] public void CompositeHandlerForType2_Resolves_WithAlphaHandler() {     var container = this.ContainerFactory();      var result = container.GetInstance<ContainerResolvedClass<Type2>>();     var handlers = result.Handlers.Select(x => x.GetType());      Assert.That(handlers.Count(), Is.EqualTo(1));     Assert.That(handlers.Contains(typeof(BetaHandler<Type2>)), Is.True); } 

This third test requires that we get two implementations back for the fictional class Type3:

[Test] public void CompositeHandlerForType3_Resolves_WithAlphaAndBetaHandlers() {     var container = this.ContainerFactory();      var result = container.GetInstance<ContainerResolvedClass<Type3>>();     var handlers = result.Handlers.Select(x => x.GetType());      Assert.That(handlers.Count(), Is.EqualTo(2));     Assert.That(handlers.Contains(typeof(AlphaHandler<Type3>)), Is.True);     Assert.That(handlers.Contains(typeof(BetaHandler<Type3>)), Is.True); } 

These tests seems to meet your requirements and best of all no containers are harmed in the solution.


The trick is to use a combination of parameter objects and marker interfaces. The parameter objects contain the data for the behaviour (i.e. the IHandler's) and the marker interfaces govern which behaviours act on which parameter objects.

Here are the marker interfaces and parameter objects - you'll note that Type3 is marked with both marker interfaces:

private interface IAlpha { } private interface IBeta { }  private class Type1 : IAlpha { } private class Type2 : IBeta { } private class Type3 : IAlpha, IBeta { } 

Here are the behaviours (IHandler<T>'s):

private interface IHandler<T> { }  private class AlphaHandler<TAlpha> : IHandler<TAlpha> where TAlpha : IAlpha { } private class BetaHandler<TBeta> : IHandler<TBeta> where TBeta : IBeta { } 

This is the single method that will find all implementations of an open generic:

public IEnumerable<Type> GetLoadedOpenGenericImplementations(Type type) {     var types =         from assembly in AppDomain.CurrentDomain.GetAssemblies()         from t in assembly.GetTypes()         where !t.IsAbstract         from i in t.GetInterfaces()         where i.IsGenericType         where i.GetGenericTypeDefinition() == type         select t;      return types; } 

And this is the code that configures the container for our tests:

private Container ContainerFactory() {     var container = new Container();      var types = this.GetLoadedOpenGenericImplementations(typeof(IHandler<>));      container.RegisterAllOpenGeneric(typeof(IHandler<>), types);      container.RegisterOpenGeneric(         typeof(ContainerResolvedClass<>),         typeof(ContainerResolvedClass<>));      return container; } 

And finally, the test class ContainerResolvedClass<>

private class ContainerResolvedClass<T> {     public readonly IEnumerable<IHandler<T>> Handlers;      public ContainerResolvedClass(IEnumerable<IHandler<T>> handlers)     {         this.Handlers = handlers;     } } 

I realise this post is a quite long, but I hope it clearly demonstrates a possible solution to your problem ...

like image 25
qujck Avatar answered Oct 09 '22 10:10

qujck