如何正确地进行手动 DI,处理深层对象图和多个依赖项。

8

我相信这个问题已经以某种方式被问过了,但我还没有理解。

我们正在进行一个 GWT 项目,我的项目负责人禁止使用 GIN/Guice 作为 DI 框架(他认为新程序员不会理解它),所以我尝试手动进行 DI。

现在我遇到了深层对象图的问题。从 UI 开始,对象层次结构如下:

AppPresenter->DashboardPresenter->GadgetPresenter->GadgetConfigPresenter

GadgetConfigPresenter 在对象层次树中往下走有一些依赖项,例如 CustomerRepository、ProjectRepository、MandatorRepository 等。

因此,创建 GadgetConfigPresenter 的 GadgetPresenter 也具有这些依赖关系,以此类推,直到创建 AppPresenter 的入口点。

  • 这是手动 DI 应该工作的方式吗?
  • 这是否意味着我在启动时创建了所有依赖项,即使我不需要它们?
  • 像 GIN/Guice 这样的 DI 框架能帮助我解决这个问题吗?

2
听起来问题在于你们的项目领导,而不是技术本身。难道因为新程序员不理解就不能使用某项技术吗?那培训和记录他们需要理解的内容呢? - matt b
@matt: 他对新技术非常不情愿,并认为新程序员需要在这个项目中学很多东西,比如Java到PHP的桥接,然后DI只是一些背后的魔法,他们不会理解。我真的不这么认为...如果DI框架是唯一解决方案,我会再次尝试说服他,但我对手动实现DI感兴趣,想知道它是如何做到的。 - Fabian
1
我建议以“看看我们在代码中需要做的所有手动布线工作,并将其与使用IoC容器更轻松的方式进行比较”的方式来处理这个讨论。 - matt b
2个回答

11

您写道

创建GadgetConfigPresenter的GadgetPresenter[.]

与其直接创建GadgetConfigPresenter实例,GadgetPresenter应该依赖于抽象工厂,以便为其创建GadgetConfigPresenter实例。这将GadgetConfigPresenter的内部依赖项推向工厂。

使用构造函数注入,您的穷人版DI装配应该如下所示(对C#语法表示歉意):

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);

请注意,我们经常打破依赖链,确保每个使用者的依赖关系数量永远不会太大。
原则上,这意味着您必须在启动时创建所有依赖项,除非您实现了一种延迟加载策略
管理生命周期之类的事情正是 DI 容器可以极大帮助的地方,但仅遵循 DI 模式和原则就可以编写整个应用程序。
总的来说,如果有可能的话,我仍然建议使用 DI 容器。

1

你可以使用上下文接口来进行依赖注入。这并不难,而且相当直观。

上下文接口是一个类,它公开了guice模块配置中的所有绑定。

以下是一个示例,假设AppPresenter+DashboardPresenter在一个包中,并需要一个“上下文”,而GadgetPresenter和GadgetConfigPresenter在另一个包中,并需要另一个“上下文”。上下文的数量以及如何处理它们完全取决于用户。

/**
 * 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();
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接