I recently asked about doing DI properly, and got some links to blog posts about it. I think I have a better understanding now - separate object construction from logic, by putting it in factories. But all of the examples are for things like websites, and say to do all the wiring at startup. Call one large factory which new
s everything and passes in all the dependencies.
But what if I don't want to instantiate everything up front? I have an object which contains a list of other objects which it can delegate to, but they are expensive, and used one at a time, so I construct them when needed and let them get collected when I'm done. I don't want to put new B()
inside the logic of A
because I would rather use DI - but how? Can A
call the factory? That doesn't seem much better, unless the factory is maintaining state including the current dependencies. I just don't want to pass the full list of B
s into A
when it's constructed, since it would be wasteful. If you want, B
doesn't necessarily have to be inside A
, although it makes logical sense (A
is a game level, B
is a single screen), but in any case the logic of A
dictates when B
is created.
So, who calls the factory to get B
, and when?
Clarification: I'm not using framework for DI. I wonder if the term DI implies that?
In Ninject, you can register Func<B>
and request that in the constructor to A
.
Autofac will automagically supply Func<B>
if B
is already registered.
Or, you can take the more straight forward approach and define an explicit factory for B
, and request that factory in the constructor; its just more typing as you'd have to create a factory for every dependency you want to lazily initialize.
Here's another SO answer that shows Ninject style factory methods: How do I handle classes with static methods with Ninject?
@Not Using A Framework: If you can, I'd probably look into using one: a IoC/DI framework usually will handle delayed creation for you out of the box.
If you want to continue to roll your own, then just pass the factory that creates B to your A object. Or, if you just don't like raw Funcs and don't want to have to create explicit factories for all your objects, then you could look into using Lazy<B>
for a more formalized solution.
There are typically two patterns for using rarely needed objects that are expensive to create. The first pattern is using a factory, as David Faivre suggests. The other is by using a proxy.
A proxy is -from a design perspective- probably the cleanest solution, although it might need more code to implement. It is the cleanest, because the application can be totally unaware of this, because you don't need an extra interface (as the factory approach needs).
Here is an simple example with some interface and an expensive implementation:
interface IAmAService
{
void DoSomething();
}
class ExpensiveImpl : IAmAService
{
private object x = [some expensive init];
void IAmAService.DoSomething() { }
}
No you can implement a proxy based on that interface, that can delay the creation of that implementation:
class ServiceProxy : IAmAService
{
private readonly Func<IAmAService> factory;
private IAmAService instance;
public ServiceProxy(Func<IAmAService> factory)
{
this.factory = factory;
}
void IAmAService.DoSomething()
{
this.GetInstance().DoSomething();
}
private IAmAService GetInstance()
{
// TODO: Implement double-checked lock only a single
// instance may exist per ServiceProxy.
if (this.instance == null)
{
this.instance = this.factory();
}
return this.instance;
}
}
This proxy class accepts a factory delegate as dependency, just as David Faivre described in his answer, but this way the application won't have to depend on the Func<IAmAService>
, but can simply depend on IAmAService
.
Now instead of injecting an ExpensiveImpl
, you can inject a ServiceProxy
into other instances:
// Create the proxy
IAmAService service =
new ServiceProxy(() => new ExpensiveImpl());
// Inject it into whatever you wish, such as:
var customerService = new CustomerService(service);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With