8

我想实例化一个用户帐户。我可以使用这样的工厂:

public class UserFactory {
  public User newUser(UserType type) {
    if (type == UserType.FREE) return new FreeUser();
  }
}

User user = UserFactory.newUser(UserType.FREE);  //UserType.FREE is an enum

现在,如果我使用 Google Guice,我必须编写一个“模块”:

public class UserModule extends AbstractModule {

  public voidSetType(UserType type) {
    this.type = type;
  }

  @Override 
  protected void configure() {
    if (this.type = UserType.FREE)
      bind(User.class).to(FreeUser.class);
    else bind .....
  }
}

然后,这是客户端代码:

Injector injector = Guice.createInjector(new UserModule().setType(UserType.FREE)));

我的问题是:我必须编写一个模块类,而不是编写一个工厂类。从上面的例子中,我没有看到任何优势。Module 类使用反射,它比我在工厂类中的赋值语句要慢。Module 类并不比工厂类简单。我必须维护模块,而不是维护工厂。

无论如何,真正的优势是什么?Google Guice 不就是另一个工厂类吗?

4

3 回答 3

8

您看不到优势,因为 Guice(依赖注入)并非旨在取代工厂。它可以在一定程度上发挥作用(请参阅 AssistedInject 和 Providers),但如果您的工厂中有逻辑,那么您不太可能使用 Guice 作为直接替代品。

对此,Guice 的方式是仍然拥有一个工厂,而 Guice 将注入工厂。现在 User 听起来像一个域对象,很可能不需要 Guice 注入的依赖树。如果是这样,那么工厂应该是 Guice 感知的,并且可以Provider为每个 User 类型获取 s,或者可以直接访问 Inject 以使用不同Key的 s 创建 User 对象。

DI 是关于依赖树的。工厂都是关于生产对象的,而调用者不必关心该对象是如何实现的。它们有交叉,可以一起使用,但它们正在解决不同的问题。

于 2012-08-15T10:07:23.483 回答
2

Guice 绝对不仅仅是一家工厂。它旨在减少系统中构建协作者的样板,并将创建这些协作者的依赖关系的责任分离出来。其原因包括:

  1. 减少样板代码(如果你做对了)
  2. 依赖项是抽象的,因此可以在以下位置进行交换:
    • 测试时间 - 可以交换假/模拟/存根实现
    • 运行时 - 当功能发生变化时,可以交换替代实现。
  3. “接口隔离”和“单一职责”等设计原则考虑

正如另一个答案所说,依赖注入和工厂之间存在交叉——它们都涉及管理对象并让外部系统满足它们的依赖关系,但它们的工作方式不同。在这种情况下,您需要工厂。但是使用依赖注入,如果你有很多需要彼此正常工作的工厂,你可以有一个像 Guice 这样的依赖注入框架来管理工厂的相互依赖,而不需要大量的手动接线代码和其他样板。

我倾向于将存在的依赖关系分解为几种不同的类型,并仅考虑对某些类型进行注入。

+=======================+========================== ===+=====================+
| 类型 | 注意 | 注入?|
+=======================+========================== ===+=====================+
| 静态依赖 | 继承类型 | 不适用 |
+---------------------+-------------- ---+--------------------+
| 合作者 | 服务、工厂等 | 绝对 |
+---------------------+-------------- ---+--------------------+
| 配置 | 上下文 | 也许- 小心|
+---------------------+-------------- ---+--------------------+
| 耗材/数据 | 值类型 | 不使用工厂|
+=======================+========================== ===+=====================+

当然,没有规则是一成不变的,但一般来说,我尽量不注入值类型,除非它们被不可变地用作整个依赖对象范围的上下文。

因此,使用 User 类型作为上下文(例如,对于请求)是可以的,但前提是它是不可变的并且作为“全局上下文”提供给范围为该请求的所有对象。但如果我打算对该用户进行操作,我将使用工厂管理它,可能在另一个单独的工作流/请求中。

自动依赖注入非常适合管理协作者、服务、实用程序类——应用程序的重要组成部分。当然,您可以将其降低到微观层面,但您需要非常小心,不要对此过于疯狂,或者要意识到复杂性的权衡。是的,您的代码将变得更通用、更可重用和抽象——但效果的原因也不太明显(现在错误可能是在运行系统中更早初始化的结果),您的流程将不那么线性,并且您的代码看起来更神奇。

如果您发现自己编写的模块代码比手动接线节省的模块代码多,那么请重新考虑您的设计。

旁注: 1. 你可以通过重构工厂和将代码连接到极致来派生 Guice 和依赖注入框架,但工厂专门用于管理特定类 - Guice 根据定义是通用的,因此不适合替换某些类型的工厂。也许是纯样板工厂。2. 你可以使用注入来注入值类型,但你会遇到困难——值类型(域对象)可能需要合法的依赖循环,尽管 Guice 和其他人并不真正打算这样做,尽管他们支持它。你是在玩火,但在一个每个人都了解风格和方法的系统中,它可能很强大。使用时要格外小心。

于 2012-08-20T14:00:01.373 回答
0

你真的需要“UserType”吗?

考虑一下:

class FreeUser extends User {...}
class NonFreeUser extends User {...}

void configure() {
  // if Type=Free
  bind(User.class).annotatedWith(Free.class).to(FreeUser.class);
  // else
  bind(User.class).to(NonFreeUser.class)
}

进而

class SomeClassThatNeedsAFreeUser {
   @Inject
   @Free
   private User user;

}

因此,当您使用 injector.getInstance(SomeClassThatNeedsAFreeUser.class) 时,您将获得一个自动注入 FreeSUer 的实例……我认为这是一种比通过特定工厂进行紧密耦合更好的方法。

于 2012-08-15T20:07:19.220 回答