Work with abstract Factory, which is introduced through the DI container

I got confused in implementing Injection Dependency in one specific example.

Let's say we have a class called SomeClass, which has a dependency on the type of IClassX.

public class SomeClass { public SomeClass(IClassX dependency){...} } 

The creation of specific implementations of the IClassX interface depends on the run-time parameter.

With this constructor, I can’t configure the DI container (used by Unity) because I don’t know which implementation of IClassX will be used at runtime. Mark Semann, in his book, β€œInjecting Dependencies In .Net,” suggests using Abstract Factory as an injection parameter.

Now we have SomeAbstractFactory, which returns an implementation of IClassX based on runtime paramater runTimeParam.

 public class SomeAbstractFactory { public SomeAbstractFactory(){ } public IClassX GetStrategyFor(int runTimeParam) { switch(runTimeParam) { case 1: return new ClassX1(); case 2: return new ClassX2(); default : return new ClassDefault(); } } } 

SomeClass now accepts ISomeAbstractFactory as the injection parameter:

 public class SomeClass { public SomeClass(ISomeAbstractFactory someAbstractfactory){...} } 

And that is wonderful. We have only one root structure, where we create a graph of objects. We configure the Unity container to enter SomeAbstractFactory in SomeClass.

But suppose the classes ClassX1 and ClassX2 have their own dependencies:

 public class ClassX1 : IClassX { public ClassX1(IClassA, IClassB) {...} } public class ClassX2 : IClassX { public ClassX2(IClassA, IClassC, IClassD) {...} } 

How to resolve dependencies of IClassA, IClassB, IClassC, and IClassD?

1. Injection through the SomeAbstractFactory constructor

We can introduce specific implementations of IClassA, IClassB, IClassC, and IClassD in SomeAbstractFactory as follows:

 public class SomeAbstractFactory { public SomeAbstractFactory(IClassA classA, IClassB classB, IClassC classC, IClassD classD) {...} ... } 

The Unity container will be used in the original root of the composition, and then use poor mans DI to return a specific ClassX1 or ClassX2 class based on the runTimeParam parameter

 public class SomeAbstractFactory { public SomeAbstractFactory(IClassA classA, IClassB classB, IClassC classC, IClassD classD){...} public IClassX GetStrategyFor(int runTimeParam) { switch(runTimeParam) { case 1: return new ClassX1(classA, classB); case 2: return new ClassX2(classA, classC, classD); default : return new ClassDefault(); } } } 

Problems with this approach:

  • SomeAbstractFactory is aware of dependencies that do not actually belong to it.
  • For a deeper graph of objects, you need to change the SomeAbstractFactory constructor and class implementation
  • DI container will not be used to resolve dependencies, poor DI should be used.

2. Explicit call of the DI container

Instead of the newbie of class X1 or ClassX2, we will enable them using the DI container.

 public class SomeAbstractFactory { public SomeAbstractFactory(IUnityContainer container){...} public IClassX GetStrategyFor(int runTimeParam) { switch(runTimeParam) { case 1: return container.Resolve<IClassX>("x1"); case 2: return container.Resolve<IClassX>("x2"); default : return container.Resolve<IClassX>("xdefault"); } } } 

Problems with this approach:

  • DI container passed to SomeAbstractFactory
  • The DI Resolve method is not used only in the root of the composition (ServiceLocator anti-pattern)

Is there an even more suitable approach?

+5
source share
1 answer

The following example shows how to do this with Unity. This blog post explains this a bit better using Windsor. The underlying concept is exactly the same for each, slightly different implementation.

I would prefer my abstract factory to access the container. I see abstract factory as a way to prevent container dependency - my class depends only on IFactory , so this is just an implementation of a factory that uses a container. Castle Windsor goes even further - you define the interface for the factory, but Windsor provides a real implementation. But this is a good sign that the same approach works in both cases, and you do not need to change the factory interface.

In the approach below, the factory-dependent class needs to pass some argument that allows the factory to determine which instance will be created. factory is going to convert this to a string, and the container will match it with a named instance. This approach works with both Unity and Windsor.

In doing this, the IFactory dependent class does not know that the factory uses the string value to find the correct type. In the Windsor example, the class passes the Address object to the factory, and the factory uses this object to determine which address verification pattern to use based on the country of the address. No other class except factory knows how to choose the right type. This means that if you switch to another container, the only thing you need to change is the implementation of IFactory . Nothing that depends on IFactory should change.

Here is a sample code using Unity:

 public interface IThingINeed {} public class ThingA : IThingINeed { } public class ThingB : IThingINeed { } public class ThingC : IThingINeed { } public interface IThingINeedFactory { IThingINeed Create(ThingTypes thingType); void Release(IThingINeed created); } public class ThingINeedFactory : IThingINeedFactory { private readonly IUnityContainer _container; public ThingINeedFactory(IUnityContainer container) { _container = container; } public IThingINeed Create(ThingTypes thingType) { string dependencyName = "Thing" + thingType; if(_container.IsRegistered<IThingINeed>(dependencyName)) { return _container.Resolve<IThingINeed>(dependencyName); } return _container.Resolve<IThingINeed>(); } public void Release(IThingINeed created) { _container.Teardown(created); } } public class NeedsThing { private readonly IThingINeedFactory _factory; public NeedsThing(IThingINeedFactory factory) { _factory = factory; } public string PerformSomeFunction(ThingTypes valueThatDeterminesTypeOfThing) { var thingINeed = _factory.Create(valueThatDeterminesTypeOfThing); try { //This is just for demonstration purposes. The method //returns the name of the type created by the factory //so you can tell that the factory worked. return thingINeed.GetType().Name; } finally { _factory.Release(thingINeed); } } } public enum ThingTypes { A, B, C, D } public class ContainerConfiguration { public void Configure(IUnityContainer container) { container.RegisterType<IThingINeedFactory,ThingINeedFactory>(new InjectionConstructor(container)); container.RegisterType<IThingINeed, ThingA>("ThingA"); container.RegisterType<IThingINeed, ThingB>("ThingB"); container.RegisterType<IThingINeed, ThingC>("ThingC"); container.RegisterType<IThingINeed, ThingC>(); } } 

Here are some unit tests. They show that the factory returns the correct type of IThingINeed after checking what has been passed to its Create() function.

In this case (which may or may not be applicable), I also specified one default type. If nothing is registered in a container that exactly matches the requirement, it can return this default value. This default value can also be a null instance without any behavior. But all this choice is in the factory configuration and container.

 [TestClass] public class UnitTest1 { private IUnityContainer _container; [TestInitialize] public void InitializeTest() { _container = new UnityContainer(); var configurer = new ContainerConfiguration(); configurer.Configure(_container); } [TestCleanup] public void CleanupTest() { _container.Dispose(); } [TestMethod] public void ThingINeedFactory_CreatesExpectedType() { var factory = _container.Resolve<IThingINeedFactory>(); var needsThing = new NeedsThing(factory); var output = needsThing.PerformSomeFunction(ThingTypes.B); Assert.AreEqual(output, typeof(ThingB).Name); } [TestMethod] public void ThingINeedFactory_CreatesDefaultyTpe() { var factory = _container.Resolve<IThingINeedFactory>(); var needsThing = new NeedsThing(factory); var output = needsThing.PerformSomeFunction(ThingTypes.D); Assert.AreEqual(output, typeof(ThingC).Name); } } 

The same factory can be implemented using Windsor, and the factory in the Windsor example can be made in Unity.

+1
source

Source: https://habr.com/ru/post/1245703/


All Articles