How to do manual DI with deep object graphs and many dependencies properly

血红的双手。 提交于 2019-12-05 05:46:38
Mark Seemann

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();
  }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!