di容器_DI容器是代码污染者

di容器

尽管依赖项注入 (aka,“ DI”)是一种在OOP中组成对象的自然技术(在Martin Fowler引入该术语之前就已知道),但Spring IoCGoogle GuiceJava EE6 CDIDagger和其他DI框架将其转变为反模式。

我将不讨论反对“ setter注入”(例如在Spring IoC中 )和“现场注入”(例如在PicoContainer中 )的明显论点。 这些机制只是违反了面向对象编程的基本原理,并鼓励我们创建不完整的可变对象,这些对象在应用程序执行过程中会塞满数据。 请记住:理想对象必须是不可变的并且不能包含setter


相反,让我们讨论“构造函数注入”(例如Google Guice中的方法 )及其与依赖项注入容器的结合使用 。 我将尝试表明为什么至少将这些容器视为冗余。

什么是依赖注入?

这就是依赖项注入(与普通的旧对象组成没有真正的区别):

public class Budget {
  private final DB db;
  public Budget(DB data) {
    this.db = data;
  }
  public long total() {
    return this.db.cell(
      "SELECT SUM(cost) FROM ledger"
    );
  }
}

对象data称为“依赖关系”。

Budget不知道它正在使用哪种数据库。 它需要从数据库中获得的所有功能,就是能够通过cell()方法使用任意SQL查询来获取单元cell() 。 我们可以使用DB接口的PostgreSQL实现实例化Budget ,例如:

public class App {
  public static void main(String... args) {
    Budget budget = new Budget(
      new Postgres("jdbc:postgresql:5740/main")
    );
    System.out.println("Total is: " + budget.total());
  }
}

换句话说,我们正在将依赖项“注入”到新的对象budget

这种“依赖注入”方法的替代方法是让Budget决定要使用的数据库:

public class Budget {
  private final DB db = new Postgres("jdbc:postgresql:5740/main");
  // class methods
}

这非常脏,导致1)代码重复,2)无法重用和3)无法测试等。无需讨论原因。 很明显。

因此,通过构造函数进行依赖注入是一种了不起的技术。 好吧,甚至没有一种技术。 更像Java和所有其他面向对象语言的功能。 预计几乎所有对象都希望封装一些知识(也称为“状态”)。 这就是构造函数的用途。

什么是DI容器?

到目前为止,一切都很好,但是这里面是阴暗的一面-依赖项注入容器。 它是这样工作的(让我们以Google Guice为例):

import javax.inject.Inject;
public class Budget {
  private final DB db;
  @Inject
  public Budget(DB data) {
    this.db = data;
  }
  // same methods as above
}

注意:构造函数带有@Inject注释。

然后,我们应该在应用程序启动时在某处配置一个容器:

Injector injector = Guice.createInjector(
  new AbstractModule() {
    @Override
    public void configure() {
      this.bind(DB.class).toInstance(
        new Postgres("jdbc:postgresql:5740/main")
      );
    }
  }
);

一些框架甚至允许我们在XML文件中配置注入器。

从现在开始,我们将无法像以前一样通过new运算符实例化Budget 。 相反,我们应该使用刚刚创建的注射器:

public class App {
  public static void main(String... args) {
    Injection injector = // as we just did in the previous snippet
    Budget budget = injector.getInstance(Budget.class);
    System.out.println("Total is: " + budget.total());
  }
}

注入自动发现,要实例化Budget它必须为其构造函数提供一个参数。 它将使用我们在注入器中实例化的Postgres类的实例。

这是使用Guice的正确和推荐的方法。 但是,有一些甚至更暗的模式,这是可能的,但不建议这样做。 例如,您可以使喷射器成为单例,并直接在Budget类中使用它。 这些机制甚至被DI容器制造商都认为是错误的,因此,让我们忽略它们,重点关注推荐的方案。

这个是来做什么的?

让我重申并总结不正确使用依赖项注入容器的场景:

  • 现场注入
  • 二传手注射
  • 将注入器作为依赖项
  • 使注入器成为全局单例

如果我们将它们全部放在一边,剩下的就是上面解释的构造函数注入。 那对我们有什么帮助? 我们为什么需要它? 为什么我们不能在应用程序的主类中使用普通的new

如果使用XML,我们创建的容器只是向代码库添加了更多行,甚至添加了更多文件。 它除了增加了复杂性之外没有增加任何东西。 如果遇到以下问题,我们应该永远记住这一点:“哪个数据库用作预算的论点?”

正确的方式

现在,让我向您展示一个使用new构造应用程序的真实示例。 这是我们在rultor.com中创建“思维引擎”的方式 (完整的类在Agents.java ):

final Agent agent = new Agent.Iterative(
  new Array(
    new Understands(
      this.github,
      new QnSince(
        49092213,
        new QnReferredTo(
          this.github.users().self().login(),
          new QnParametrized(
            new Question.FirstOf(
              new Array(
                new QnIfContains("config", new QnConfig(profile)),
                new QnIfContains("status", new QnStatus(talk)),
                new QnIfContains("version", new QnVersion()),
                new QnIfContains("hello", new QnHello()),
                new QnIfCollaborator(
                  new QnAlone(
                    talk, locks,
                    new Question.FirstOf(
                      new Array(
                        new QnIfContains(
                          "merge",
                          new QnAskedBy(
                            profile,
                            Agents.commanders("merge"),
                            new QnMerge()
                          )
                        ),
                        new QnIfContains(
                          "deploy",
                          new QnAskedBy(
                            profile,
                            Agents.commanders("deploy"),
                            new QnDeploy()
                          )
                        ),
                        new QnIfContains(
                          "release",
                          new QnAskedBy(
                            profile,
                            Agents.commanders("release"),
                            new QnRelease()
                          )
                        )
                      )
                    )
                  )
                )
              )
            )
          )
        )
      )
    ),
    new StartsRequest(profile),
    new RegistersShell(
      "b1.rultor.com", 22,
      "rultor",
      IOUtils.toString(
        this.getClass().getResourceAsStream("rultor.key"),
        CharEncoding.UTF_8
      )
    ),
    new StartsDaemon(profile),
    new KillsDaemon(TimeUnit.HOURS.toMinutes(2L)),
    new EndsDaemon(),
    new EndsRequest(),
    new Tweets(
      this.github,
      new OAuthTwitter(
        Manifests.read("Rultor-TwitterKey"),
        Manifests.read("Rultor-TwitterSecret"),
        Manifests.read("Rultor-TwitterToken"),
        Manifests.read("Rultor-TwitterTokenSecret")
      )
    ),
    new CommentsTag(this.github),
    new Reports(this.github),
    new RemovesShell(),
    new ArchivesDaemon(
      new ReRegion(
        new Region.Simple(
          Manifests.read("Rultor-S3Key"),
          Manifests.read("Rultor-S3Secret")
        )
      ).bucket(Manifests.read("Rultor-S3Bucket"))
    ),
    new Publishes(profile)
  )
);

印象深刻? 这是真正的对象组成。 我相信这是应该实例化正确的面向对象应用程序的方式。

和DI容器? 我认为,它们只会增加不必要的噪音。

相关文章

您可能还会发现以下有趣的帖子:

翻译自: https://www.javacodegeeks.com/2014/10/di-containers-are-code-polluters.html

di容器

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值