Enabling DI for Lazy Components by Mark Seemann
My previous post led to this comment by Phil Haack:
Your LazyOrderShipper directly instantiates an OrderShipper. What about the dependencies that OrderShipper might require? What if those dependencies are costly?
I didn't want to make my original example more complex than necessary to get the point across, so I admit that I made it a bit simpler than I might have liked. However, the issue is easily solved by enabling DI for the LazyOrderShipper itself.
As always, when the dependency's lifetime may be shorter than the consumer, the solution is to inject (via the constructor!) an Abstract Factory, as this modification of LazyOrderShipper shows:
public class LazyOrderShipper2 : IOrderShipper { private readonly IOrderShipperFactory factory; private IOrderShipper shipper; public LazyOrderShipper2(IOrderShipperFactory factory) { if (factory == null) { throw new ArgumentNullException("factory"); } this.factory = factory; } #region IOrderShipper Members public void Ship(Order order) { if (this.shipper == null) { this.shipper = this.factory.Create(); } this.shipper.Ship(order); } #endregion }
But, doesn't that reintroduce the OrderShipperFactory that I earlier claimed was a bad design?
No, it doesn't, because this IOrderShipperFactory doesn't rely on static configuration. The other point is that while we do have an IOrderShipperFactory, the original design of OrderProcessor is unchanged (and thus blissfully unaware of the existence of this Abstract Factory).
The lifetime of the various dependencies is completely decoupled from the components themselves, and this is as it should be with DI.
This version of LazyOrderShipper is more reusable because it doesn't rely on any particular implementation of OrderShipper - it can Lazily create any IOrderShipper.
Comments
This way the lazy/optional nature of the parameter is obvious to clients of the class, and there is no need to generate lazy implementation classes manually.
Note: I haven't had any practical experience using DI frameworks, so the above might not be possible at all :)
I had a period where I used a lot of delegates as injected dependencies, but I have more or less abandonded that approach again. While it technically works fine, it makes unit testing a bit harder because it's harder to test that a given object contains a specific type of Strategy if it's just a Func<T> or similar.
In any case, I'm mostly familiar with Castle Windsor at the moment. Although I have yet to try it out, I think the new Typed Factory Facility fits the bill very well - with that, we would never have to code a real implementation of IOrderShipperFactory because Windsor would be able to dynamically emit one for us.
What I meant to propose is that we change Jeffrey Palermo's original example like the below:
- private readonly IOrderShipper _shipper;
+ private readonly Func< IOrderShipper > _shipperFactory;
- public OrderProcessor(IOrderValidator validator, IOrderShipper shipper)
+ public OrderProcessor(IOrderValidator validator, Func< IOrderShipper > shipperFactory)
- _shipper = shipper;
+ _shipperFactory = shipperFactory;
- _shipper.Ship(order);
+ _shipperFactory().Ship(order);
The change to the tests should be straightforward as well,
- new OrderProcessor(validator, mockShipper)
+ new OrderProcessor(validator, () => mockShipper)
To communicate intent, it's clearer than Func<T>:
public UsesOrderShipper(Lazy<IOrderShipper> orderShipper)
There's a more complete example using Lazy<T> with Autofac.
Cheers,
Nick
That would definitely be an option as well, but I rather wanted to show the route involving absolutely no redesign of the original OrderProcess, and I couldn't do that purely with Lazy<IOrderShipper>. The most important point I wanted to make is that you can solve this problem using basic tools available since .NET 1.0.
It would, however, make a lot of sense to implement LazyOrderShipper by injecting Lazy<IOrderShipper> into it instead of inventing a new IOrderShipperFactory interface.
While it's easy to get it work with Typed Factory Facility and Castle, how do you implement the factory :
- without static configuration ?
- without passing the container in ?
Or I missed something ?
Thanks,
Thomas
I was refering to your first comment. If I have no problem with the pattern I would like to know how you would do from the implementation point of view.
Thanks,
Thomas
With Windsor there is no problem as TypedFactoryFacility provides implementation on the fly. However if you take another container you have to provide the implementation of IOrderShipperFactory on your own. Now the question is. How my implementation of the factory will pull the IOrderShipper implementation from the container ? I see two choices :
- configure staticaly (like Jeffrey did in his post)
- pass the container into the factory that it could resolve IOrderShipper.
- third choice that I don't know :)
I hope it's clearer now. Let me know if it doesn't make sense.
Thanks,
Thomas
HTH