Some time ago I published an entry on Local Factories and what role they play in a clean architecture stack where the Composition Root configures dependencies between abstractions and implementations. The article can be found here.
There are dozens of questions about this article from people and I decided to refresh the concept by simplyfing the code a little bit.
The general idea of the Local Factory pattern is to hide the implementation details of a factory but in the same time make the factory the only legitimate source of instances. The goal the pattern tries to achieve is to create a stable API for creating instances, allow possible DI usage but not to be dependant on any specific implementation (including the DI).
Let’s start with the service:
// service contract
// no ioc here
public interface IService
{
void Foo();
}
Then, the factory:
// the Local Factory
// still no ioc here
public class ServiceFactory
{
private static Func<IService> _provider;
// the factory is the only legal provider of service instances
// but in fact it delegaes the creation yet elsewhere
public IService CreateService()
{
if ( _provider != null )
return _provider();
else
throw new ArgumentException();
}
public static void SetProvider( Func<IService> provider )
{
_provider = provider;
}
}
Note how smart the factory is.
Until an actual provider is configured (and it will be configured in the Composition Root), the factory doesn’t even know how to create instances. This approach is a simplification compared to the one I presented last time – my approach that time was to have a factory provider to create factory to create instances. A redundancy that can be easily eliminated by moving the volatile part of the implementation to the factory and eliminating the factory provider from the big picture.
The client code relies on the factory:
// the client uses the factory
// no ioc here in the client
public class ServiceClient
{
public void ServiceUsageExample()
{
var sf = new ServiceFactory();
var service = sf.CreateService();
service.Foo();
}
}
Note that up to this point there is no DI, just a simple dependency from the client to the factory that returns instances. In a real world application you would probably have multiple local factories, each factory belongs to a specific subsystem in a specific layer and doesn’t interfere with other layers and other subsystems.
This is where such local factory differs from the Service Locator, a God-like factory that creates instances of multiple classes from multiple layers but in the same time it introduces an unwanted association to itself. A local factory on the other hand is a part of its local ecosystem: the factory together with the abstraction (interface) consitute the API for their future clients.
Now it is the time however to configure the factory from the Composition Root. The CR should:
- know what actual implementation of the interface should be used
- configure the factory provider somehow, for example to return an instance of a fixed, known type or maybe to use a DI container to return one
First, a concrete implementation of the service. Note that the factory is unaware of what actual type will be used which of course means that the type can be defined anywhere in the solution stack, for example in a different assembly from the one the interface/factory are defined.
public class ServiceImpl : IService
{
public void Foo()
{
Console.WriteLine( "serviceimpl:foo" );
}
}
And last but not least, the Composition Root:
public class Program
{
public static void Main()
{
CompositionRoot();
var client = new ServiceClient();
client.ServiceUsageExample();
}
public static void CompositionRoot()
{
// this is the only place in the code that is aware of the ioc
// but could be as well configured to not to use ioc at all
// no-ioc:
ServiceFactory.SetProvider( () => new ServiceImpl() );
// ioc:
/*
var container = new UnityContainer();
container.Register<IService, ServiceImpl>();
ServiceFactory.SetProvider( () => container.Resolve<IService>() );
*/
}
}
Note that the Composition Root is the only spot in the code that is aware of the possible DI container. We could say that the CR delegates the implementation down the application stack to the Local Factory which otherwise would not know how to create instances. And whether or not a DI container is used – this fact is known only to the CR.
Note also that the possibility to have a simple provider that returns an instance of a known type is suitable for unit testing.
Hope the idea is clearer this time.
No comments:
Post a Comment