How to do manual DI with deep object graphs and many dependencies properly
I believe this questions has been asked in some or the other way but i'm not getting it yet.
We do a GWT project and my project leader disallowed to use GIN/Guice as an DI framework (new programmers are not going to understand it, he argued) so I try to do the DI manually.
Now I have a problem with deep object graphs. The object hierarchy from the UI looks like this:
AppPresenter->DashboardPresenter->GadgetPresenter->GadgetConfigPresenter
The GadgetConfigPresenter way down the开发者_StackOverflow社区 object hierarchy tree has a few dependencies like CustomerRepository, ProjectRepository, MandatorRepository, etc.
So the GadgetPresenter which creates the GadgetConfigPresenter also has these dependencies and so on, up to the entry point of the app which creates the AppPresenter.
- Is this the way manual DI is supposed to work?
- doesn't this mean that I create all dependencies at boot time even I don't need them?
- would a DI framework like GIN/Guice help me here?
You write that
the GadgetPresenter which creates the GadgetConfigPresenter[.]
Instead of directly creating GadgetConfigPresenter
instances, GadgetPresenter
should take a dependency on an Abstract Factory that can create GadgetConfigPresenter
instances for it. This pushes the inner dependencies of GadgetConfigPresenter
to the factory.
Using Constructor Injection all the way, your Poor Man's DI wiring should look something like this (apologies for the C# syntax):
var customerRepository = new CustomerRepository(/*...*/);
var projectRepository = new ProjectRepository(/*...*/);
var mandatorRepository = new MandatorRepository(/*...*/);
var gadgetConfigPresenterFactory =
new GadgetConfigPresenterFactory(
customerRepository,
projectRepository,
mandatorRepository);
var gadgetPresenter = new GadgetPresenter(gadgetConfigPresenterFactory);
var dashboardPresenter = new DashboardPresenter(gadgetPresenter);
var appPresenter = new AppPresenter(dashboardPresenter);
Notice how we break the dependency chain often, ensuring that the number of dependencies for each consumer never becomes too big.
In principle, this means that you must create all the dependencies at boot time, unless you implement a lazy loading strategy.
Such things as managing lifetimes are exactly the sort of thing where a DI Container can be enormously helpful, but it's entirely possible to write an entire application by just following DI patterns and principles.
All in all, though, I would still recommend a DI Container if at all possible.
You can do DI using Context interfaces. It is not hard, and fairly straight forward.
A Context interface is a class that exposes all the bindings from the guice module configuration.
This is an example of this where I'm assuming that AppPresenter+DashboardPresenter is in one package and needs one "context", while GadgetPresenter and GadgetConfigPresenter is in another package and needs another "context". The number of contexts, and how to handle them is entirely up to the user.
/**
* The dependencies that need to be injected for package1
*/
public interface SomePackageContext {
GadgetPresenter getGadgetPresenter();
GadgetConfigPresenter getGadgetConfigPresenter();
}
/**
* The dependencies that need to be injected for package2
*/
public interface OtherPackageContext {
// These methods can take arguments..
AppPresenter getAppPresenter(Args..);
DashboardPresenter getDashboardPresenter(Args..);
}
/**
* All of the DI needed in our project.
*
* <p>We don't need the two interfaces above, we can put
* everything in this interface if we have a small
* project where layering is not a big issue.
*/
public interface PresenterContext
extends SomePackageContext, OtherPackageContext {
}
public class MockPresenterContext implements PresenterContext {
...
}
public class RealPresenterContext implements PresenterContext {
// This is similar to bind(...) in guice
public AppPresenter getAppPresenter(Args..) {
return new AppPresenter(this, otherargs...);
}
public DashboardPresenter getDashboardPresenter(Args..) {
return new DashboardPresenter(this, otherargs...);
}
public GadgetPresenter getGadgetPresenter() {
return new GadgetPresenter(this);
}
public GadgetConfigPresenter getGadgetConfigPresenter() {
return new GadgetConfigPresenter();
}
}
public class DashboardPresenter {
// @Inject
private final GadgetPresenter gadgetPresenter;
/*
* We inject everything using the SomePackageContext.
*/
public DashboardPresenter(SomePackageContext ctxt) {
this.gadgetPresenter = ctxt.getGadgetPresenter();
}
}
精彩评论