设计模式-工厂模式(CDI示例)

By | 2022年3月17日

工厂模式是一种创建型设计模式,其目的是为创建相关或依赖对象系列提供一个接口,而无需指定它们的具体类。 创建逻辑封装在工厂中,该工厂要么提供创建方法,要么将对象的创建委托给子类。 客户端不知道接口或类的不同实现。 客户端只需要知道要使用的工厂来获取接口实现之一的实例。 客户端与对象的创建是分离的。

工厂模式通常被实现为单例或静态类,因为只需要一个工厂实例。 这集中了对象的创建。

CDI框架

在 Java EE 中,我们可以利用 CDI 框架来创建对象,而无需了解它们的创建细节。 Java EE 实现控制反转的方式会导致解耦。 这传达的最重要的好处是高级类与低级类的解耦。 这种解耦允许在不影响客户端的情况下更改具体类的实现:减少耦合并增加灵活性。

您可以认为 CDI 框架本身就是工厂模式的实现。 容器在应用程序启动期间创建限定对象并将其注入到任何匹配注入标准的注入点。 客户不需要知道对象的具体实现,甚至客户也不知道具体类的名称。

public class CoffeeMachine implements DrinksMachine {
 // Implementation code
}

@Inject
DrinksMachine drinksMachine;

在这里,容器创建了 CoffeeMachine 具体类的实例,继承了接口 DrinksMachine ,并在容器找到符合条件的注入点的任何地方注入。 这是使用工厂模式的 CDI 实现的最简单方法。 然而,它并不是最灵活的。

如果我们有多个 DrinksMachine 接口的具体实现会发生什么? 应该注入哪个实现? SoftDrinksMachine 还是 CoffeeMachine? 容器不知道,因此部署将失败并出现“模糊依赖关系”错误。

public class CoffeeMachine implements DrinksMachine {
 // Implementation code
}

public class SoftDrinksMachine implements DrinksMachine {
 // Implementation code
}

@Inject
DrinksMachine drinksMachine;

那么容器如何区分具体实现呢? Java EE 为我们提供了一个新工具:限定符。 限定符是自定义注释,用于标记具体类以及您希望容器注入对象的位置。

回到我们的 Drinks 机器和同类型 CoffeeMachine 和 SoftDrinksMachine 的两个具体类,我们将通过使用两个限定符注释来区分它们。

我们创建一个限定词名称 SoftDrink。 这将注释 SoftDrinksMachine 具体类,Coffee 将注释 CoffeeMachine 类。

@Target 注释限制了我们可以在哪里使用这些限定符来标记注入点,在本例中是方法和字段注入点。 具有保留策略 RUNTIME 的注解确保该注解可通过运行时供 JVM 使用。

Target 的可能值有:TYPE、METHOD、FIELD、PARAMETER。

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD})
public @interface SoftDrink

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD})
public @interface Coffee

DrinksMachine 接口的两个具体实现可以用适当的注释来区分。 CoffeeMachine 类被注释为@Coffee,而 SoftDrinksMachine 类被注释为@SoftDrink。

@Coffee
public class CoffeeMachine implements DrinksMachine {
 // Implementation code
}

@SoftDrink
public class SoftDrinksMachine implements DrinksMachine {
 // Implementation code
}

现在您注释注入点。 使用限定符 @SoftDrink 来表示您希望容器在哪里注入 SoftDrinksMachine 实例,使用限定符 @Coffee 来表示您希望容器在哪里注入 CoffeeDrinkMachine 实例。 现在我们已经明确了应该在哪里注入我们的具体实现并且部署将成功的容器。

@Inject @SoftDrink
DrinksMachine softDrinksMachine;

@Inject @Coffee
DrinksMachine coffeeDrinksMachine;

我们已经看到了 Java EE 的 CDI 框架如何允许创建一个具体类与其使用点分离。 我们已经看到了如何使用限定符来选择所需的实现,而无需了解有关对象创建的任何信息。

重要的是要记住,CDI 框架只会实例化满足托管 bean 规范 JSR 299 的所有条件的 POJO。但是如果您要注入的对象没有,这是否意味着我们不能利用 CDI 框架对不符合要求的类的注入功能。 Java EE 为我们提供了一个解决方案。 让我们深入研究一下如何使用 CDI 框架将 ANY 类型的 ANY 类注入到注入点。

生产者方法

Java EE 有一个称为生产者方法的特性。 这些方法提供了一种实例化方法,因此可用于不符合托管 bean 规范的注入对象,例如需要构造函数参数才能正确实例化的对象。 值可能在运行时更改的对象和创建需要一些自定义初始化的对象也可以通过生产者方法生成准备注入。

让我们看一下生产者方法,它产生一个填充了 Book 对象的 List。

Book 对象的列表将被注入注解为@Library 的注入点。

生产者方法的一个重要特征是它的范围。 这将确定何时调用该方法以及它生成的对象将存在多长时间。

默认情况下,生产者方法范围是@DependentScoped。 这意味着它继承了其客户端的范围。

我们可以通过给它一个更广泛的范围来进一步扩展这个例子。 如果我们注释生产者方法@RequestScoped,它将只为它参与的每个HTTP请求调用一次,并在请求期间持续。

@RequestScoped
@History
@Produces
public List<Book> getLibrary(){
 // Generate a List of books called 'library'
 return library;
}

可能的范围是:

  • RequestScoped – HTTP 请求范围
  • SessionScoped – HTTP 会话范围
  • ApplicationScoped – 跨用户共享
  • ConversationScoped – 与 JSF 的交互
  • DependentScoped – 默认,从客户端继承

好的,坏的和丑陋的

好的

它变得非常容易实现,因为几乎没有样板代码。 创建对象的注入是由容器完成的,并且可以神奇地工作。 任何对象,无论其是否符合 JSR299 都可以实例化并使其可注入。 限定符有助于消除歧义并提供一种机制来过滤注入的具体实现。

坏的

如果您使用 @Named 注释,则必须处理其固有的缺乏类型安全性,例如 @Named(“历史”) -> @History。 但是,如果可能,您应该使用自定义限定符。

丑陋的

对象创建是隐藏的,这使得执行流程难以遵循,但是,您的 IDE 应该提供有关生产者方法或具体实现的位置的提示。