5

我一直在阅读 Misko Hevery 关于依赖注入的经典 文章,基本上是“将对象图创建代码与代码逻辑分离”。

主要思想似乎是“摆脱‘新’操作符”,将它们放入专用对象(“工厂”)并注入你所依赖的一切。”

现在,我似乎无法理解如何使这与由其他几个组件组成的对象一起工作,并且他们的工作是将这些组件与外部世界隔离开来。

蹩脚的例子

一个 View 类,表示几个字段和一个按钮的组合。所有组件都依赖于图形化的 ui 上下文,但您希望将其隐藏在每个子组件的界面后面。

所以像(在伪代码中,我猜语言并不重要):


class CustomView() {

   public CustomView(UIContext ui) {
        this.ui = ui
   }

   public void start() {

      this.field = new Field(this.ui);
      this.button = new Button(this.ui, "ClickMe");

      this.button.addEventListener(function () {
           if (field.getText().isEmtpy()) {
              alert("Field should not be empty");
           } else {
              this.fireValueEntered(this.field.getText());
           }
      });
   }

   // The interface of this component is that callers
   // subscribe to "addValueEnteredListener"..)
   public void addValueEnteredListener(Callback ...) {
   }

   public void fireValueEnteredListener(text) {
     // Would call each listeners in turn
   }
}

来电者会做类似的事情:



// Assuming a UIContext comes from somewhere...
ui = // Wherever you get UI Context from ?
v = new CustomView(ui);
v.addValueEnteredListener(function (text) {
 // whatever...
});

现在,这段代码有三个“新”运算符,我不确定 Misko(和其他 DI 支持者)提倡删除哪一个,或者如何删除。

摆脱 new Field() 和 new Button()

只需注入它

我不认为这里的想法是实际注入Field 和 Button 的实例,可以这样做:


class CustomView() {

   public CustomView(Field field, Button button) {
        this.field = field;
        this.button = button;
   }

   public void start() {

      this.button.addEventListener(function () {
           if (field.getText().isEmtpy()) {
              alert("Field should not be empty");
           } else {
              this.fireValueEntered(this.field.getText());
           }
      });
   }

   // ... etc ... 

这无疑使组件的代码更轻巧,而且它实际上隐藏了 UI 的概念,因此 MetaForm 组件在可读性和可测试性方面明显得到了改进。

但是,创建这些东西的负担现在落在了客户端上:



// Assuming a UIContext comes from somewhere...

ui = // wherever ui gets injected from 

form = new Form(ui);
button = new Button(ui);

v = new CustomView(form, button);
v.addValueEnteredListener(function (text) {
 // whatever...
});

这对我来说听起来真的很麻烦,尤其是因为客户知道班级的所有内部人员,这听起来很傻。

妈妈知道,给她注射

这些文章似乎提倡的是注入一个工厂来创建组件元素。


class CustomView() {

   public CustomView(Factory factory) {
      this.factory = factory;
   }

   public void start() {

      this.field = factory.createField();
      this.button = factory.createButton();

      this.button.addEventListener(function () {
           if (field.getText().isEmtpy()) {
              alert("Field should not be empty");
           } else {
              this.fireValueEntered(this.field.getText());
           }
      });
   }

   // ... etc ... 

然后对调用者来说一切都变得很好,因为它只需要从某个地方获取工厂(并且这个工厂将是唯一知道 UI 上下文的,所以为解耦欢呼吧。)



// Assuming a UIContext comes from somewhere...

factory = // wherever factory gets injected from 

v = new CustomView(factory);
v.addValueEnteredListener(function (text) {
 // whatever...
});

一个可能的缺点是,在测试 MetaForm 时,您通常必须使用“模拟”工厂......创建字段和按钮类的模拟版本。但显然还有另一个缺点......

哟工厂好胖!!

工厂将有多大?如果您严格遵循该模式,那么您想要在运行时在应用程序中创建的每个单独的组件(通常是 UI 的情况,对)都必须在至少一个工厂中获得自己的 createXXXXX 方法。

因为现在你需要:

  • Factory.createField 创建字段
  • Factory.createButton 创建按钮
  • Factory.createMetaForm 用于在客户端创建字段、按钮和 MetaForm(比如说 MetaEditPage 要使用一个)
  • 显然是一个 Factory.createMetaEditPage 为客户端..
  • ......和它的乌龟一路走来。

我可以看到一些简化这一点的策略:

  • 尽可能将在“启动”时创建的图形部分与在运行时创建的部分分开(前者使用像 Spring 这样的 DI 框架,后者使用工厂)
  • 将工厂分层,或在同一个工厂中并置相关对象(UIWidgetFactory 对 Field 和 Button 有意义,但您会将其他的放在哪里?在链接到应用程序的工厂中?到其他逻辑级别?)

我几乎可以听到所有来自 C 人的笑话,即没有 Java 应用程序可以在不调用 ApplicationProcessFactoryCreator.createFactory().createProcess().startApplication() 的废话链的情况下做任何事情......

所以我的问题是:

  • 我在这里完全错过了一点?
  • 如果不是,您会建议采用哪种策略来让事情变得可以忍受?

附录:为什么我不确定依赖注入会有所帮助

假设我决定使用依赖注入,以及类似 guice 的框架。我最终会编写这样的代码:


class CustomView

    @Inject
    private Field fiedl;

    @Inject
    private Button button;

    public void start() {
      this.button.addEventListener(....

// etc...

然后什么 ?

我的“作曲根”将如何利用它?我当然不能为 Field 和 Button 配置“单例”(带有小写的“s”,如“类的单个实例”)(因为我想创建与 MetaForm 实例一样多的实例?

使用提供者没有意义,因为我的问题不是我想创建哪个按钮实例,而只是我最近想创建它,并带有一些只对这种形式有意义的配置(例如它的文本) .

对我来说,DI 不会有帮助,因为我是组件的新部分不是依赖项。而且我想我可以将任何子组件转换为依赖项,并让框架注入它们。只是在这种情况下,注入子组件对我来说看起来真的很不自然,而且很直观……所以再一次,我一定错过了一些东西;)

编辑

特别是,我的问题是我似乎无法理解您将如何测试以下场景:

“当我单击按钮时,如果字段为空,则应该有错误”。

如果我注入按钮,这是可行的,这样我就可以手动调用它的“fireClicked”事件——但感觉有点傻。

另一种方法是只做 view.getButton().fireClicked() ,但这看起来有点难看......

4

2 回答 2

1

好吧,您可以使用一些 DI 框架(Spring 或 Guice)并完全摆脱工厂方法。只需在 field/constructor/set 方法上添加一些注释,DI 框架就会完成这项工作。在单元测试中使用模拟框架。

于 2012-09-23T17:57:33.237 回答
0

不要过分沉迷于“没有新”的教条如何?

我的看法是 DI 适用于 - 你知道的,依赖项或委托。CustomView您的示例是关于组合的,恕我直言,拥有实体(您的)明确创建其组件绝对有意义。毕竟,组合的客户端甚至不必知道这些组件的存在、它们是如何初始化的或您如何使用它们。

于 2013-04-25T13:37:40.907 回答