How do I use the Decorator Pattern with Unity without explicitly specifying every parameter in the InjectionConstructor
Another approach, thanks to a suggestion from @DarkSquirrel42, is to use an InjectionFactory
. The downside is that the code still needs updating every time a new constructor parameter is added to something in the chain. The advantages are much easier to understand code, and only a single registration into the container.
Func<IUnityContainer,object> createChain = container =>
new LoggingProductRepository(
new CachingProductRepository(
container.Resolve<ProductRepository>(),
container.Resolve<ICacheProvider>()),
container.Resolve<ILogger>());
c.RegisterType<IProductRepository>(new InjectionFactory(createChain));
Assert.IsInstanceOf<LoggingProductRepository>(c.Resolve<IProductRepository>());
See this article on implementing a decorator container extension. This should get you to where you want to be with regards to not needing to modify your configuration if your constructor signatures change.
Another solution involves adding type parameters to your code base in order to help Unity resolving your decorated types. Luckily Unity is perfectly capable of resolving type parameters and their dependencies on its own, so we don't have to care about constructor parameters when defining the decorator chain.
The registration would look as follows:
unityContainer.RegisterType<IService, Logged<Profiled<Service>>>();
Here is a basic example implementation. Note the templated decorators Logged<TService>
and Profiled<TService>
. Look below for some drawbacks I've noticed so far.
public interface IService { void Do(); }
public class Service : IService { public void Do() { } }
public class Logged<TService> : IService where TService : IService
{
private TService decoratee;
private ILogger logger;
public Logged(ILogger logger, TService decoratee) {
this.decoratee = decoratee;
this.logger = logger;
}
public void Do() {
logger.Debug("Do()");
decoratee.Do();
}
}
public class Profiled<TService> : IService where TService : IService
{
private TService decoratee;
private IProfiler profiler;
public Profiled(IProfiler profiler, TService decoratee) {
this.decoratee = decoratee;
this.profiler = profiler;
}
public void Do() {
profiler.Start();
decoratee.Do();
profiler.Stop();
}
}
Drawbacks
- A faulty registration like
uC.RegisterType<IService, Logged<IService>>();
will result in an infinite recursion that stack-overflows your application. This can be a vulnerability in a plug-in architecture. - It uglyfies your code base to some degree. If you ever give up Unity and switch to a different DI framework those template parameters will make no sense to anyone anymore.