设计模式介绍:依赖注入代码示例

设计模式介绍:依赖注入代码示例

依赖注入是一种非常常见和有用的设计模式。让我们深入研究一下,看看它为什么如此有用,又怎么用。

依赖项注入是一种使类独立于其依赖项的编程技术。它可以将对象的创建与使用进行分离。这有助于您遵循SOLID的依赖倒置和单一责任原则。

正如我之前在关于可靠设计原则的文章中所解释的,它们的目标是提高代码的可重用性。还可以减少需要更改类的频率。依赖注入可以通过分离对象的创建和使用。这使您能够在不更改使用它们的类的情况下替换依赖类。当类的依赖项发生变化时,我们不必再承担更改类代码的风险。

依赖注入技术是 service locator pattern服务定位器模式的一种流行的替代方法。许多现代应用程序框架都实现了它。这些框架提供了技术的技术部分,这样您就可以专注于业务逻辑的实现。

常见的例子:

  • 对Java (CDI)规范中的Jakarta EE上下文和依赖注入的引用实现。
  • Spring
  • Guice
  • Play framework
  • Dagger
设计模式介绍:依赖注入代码示例

依赖注入技术

您可以引入接口来打破高级和低级类之间的依赖关系。如果这样做,两个类都依赖于接口,而不再依赖于彼此。我在我的文章中非常详细地解释了依赖倒置原则。

该原则提高了代码的可重用性,并限制了需要更改低级类时的连锁反应。但是即使您完美地实现了它,您仍然保持对底层类的依赖。该接口只是将低级类的使用分离,而不是它的实例化。在代码中的某个地方,您需要实例化接口的实现。这将防止您用另一个接口替换接口的实现。

依赖注入技术的目标是通过将使用与创建对象分离来删除这种依赖关系。这减少了所需的样板代码的数量,并提高了灵活性。

但是在我们看一个示例之前,我想告诉您更多关于依赖注入技术的内容。

依赖注入中的4个角色

如果您想使用这种技术,您需要实现四个基本角色的类。

这些都是:

  • 您想要使用的服务。
  • 使用服务的客户端。
  • 由客户端使用并由服务实现的接口。
  • 注入器创建服务实例并将其注入到客户端。

通过遵循依赖倒置原则,您已经实现了这四个角色中的三个。服务和客户端是依赖倒置原则通过引入接口来删除依赖项的两个类。

您可以跳过接口角色,直接将服务对象注入客户机。但是通过这样做,您就打破了依赖倒置原则,您的客户端对服务类具有显式的依赖关系。在某些情况下,这可能没问题。但最常见的情况是,最好引入一个接口来删除客户端和服务实现之间的依赖关系。

注入器本身是唯一不需要实现依赖倒置的角色。但这不是问题,因为您不需要实现它。本文开头列出的所有框架都提供了它的现成实现。

如您所见,依赖注入非常适合遵循依赖倒置原则的应用程序。您已经实现了所需的大部分角色,依赖项注入技术使您能够消除服务实现的依赖项。

使用依赖注入使CoffeeApp更加灵活

我在我的文章中使用了CoffeeApp示例,它是关于依赖倒置原则的。让我们更改它,以便它使用依赖注入技术。

这个小应用程序允许您控制不同的咖啡机,您可以使用它来煮一杯新鲜的过滤咖啡。它由一个咖啡应用类组成,该类在CoffeeMachine界面上调用brewFilterCoffee方法来煮一杯新鲜的咖啡。类BasicCoffeeMachine实现CoffeeMachine接口。

设计模式介绍:依赖注入代码示例

正如您在图中看到的,这个应用程序已经遵循依赖倒置原则。它还提供依赖倒置技术所需的四种角色中的三种:

CoffeeApp实现客户端角色。

BasicCoffeeMachine类充当服务。

CoffeeMachine接口实现了接口角色。

唯一缺少的就是注入器。我将通过使用Weld框架介绍该角色的实现。它是Jakarta EE的CDI规范的参考实现。自2.0版本以来,您可以在Java SE环境中直接使用它,而无需添加庞大的框架堆栈。

CDI 2.0是所有Jakarta EE 8应用服务器的一部分。如果将应用程序部署到这样的服务器,则不再需要CDI容器。

如果您使用的是不同的应用程序框架,例如Spring,您应该使用该框架提供的依赖注入实现。它使您能够使用我在下面示例中展示给您的相同概念。

Bootstrapping CDI

在使用CDI的依赖项注入特性之前,需要bootstrapping CDI容器。不过别担心,Weld把它做得非常简单。它提供了一个内建的主方法,为您bootstrapping一个容器。

您可以执行以下命令来运行它。

java org.jboss.weld.environment.se.StartMain

但是,在没有应用程序的情况下bootstrapping CDI容器并没有多大意义。您可以在两个步骤中添加应用程序,

几乎不需要任何代码。

Weld-SE Dependency

您需要向应用程序添加对weld-se的依赖项。

<dependency> <groupId>org.jboss.weld.se</groupId> <artifactId>weld-se-core</artifactId> <version>3.0.4.Final</version></dependency>

编写并运行CoffeeAppStart类

下一步是在CDI容器中运行应用程序所需的惟一实现任务。您需要编写一个方法来观察ContainerInitializedevent。我在CoffeeAppStarter课堂上做的。类似于我的文章中关于依赖倒置原则和服务定位器模式的示例,这个类启动CoffeeApp。

@Singleton
public class CoffeeAppStarter {
 private CoffeeApp app; 
@Inject 
public CoffeeAppStarter(CoffeeApp app) 
{ this.app = app; 
public void startCoffeeMachine(@Observes ContainerInitialized event) { 
try { 
app.prepareCoffee(CoffeeSelection.FILTER_COFFEE); 
} catch (CoffeeException e)
 { e.printStackTrace(); }
 }}

在CDI中观察事件是简单而有力的。您只需要用一个方法参数上添加@ observer注解。一旦带注解的参数的类型触发事件时,容器将调用此方法。Weld在启动CDI容器之后触发容器初始化事件。因此,这个方法将在应用程序启动时调用。

使用CDI的依赖注入

您可能已经在前面的代码片段中识别了@Inject注解。它告诉CDI容器在调用CoffeeAppStarter类的构造函数时注入CoffeeApp对象。因此,您可以使用startCoffeeMachine方法中的CoffeeApp应用属性来煮一杯过滤咖啡。

这种方法显然不遵循依赖倒置原则,因为缺少接口。但我认为这是可以接受直接注入服务实现的罕见情况之一。CoffeeAppStarter类的惟一任务是通过调用注入的CoffeeApp对象上的prepareCoffeemethod来启动咖啡机。我认为没有必要添加另一个抽象来使CoffeeApp可替换。

但是coffeeMachine属性在CoffeeApp类中并不存在这种情况。将来,这个应用程序将需要控制不同类型的咖啡机。我想让替换它们变得尽可能简单。这就是为什么我在以前的文章中介绍了CoffeeMachine接口。正如您在以下代码片段中看到的,CoffeeApp类仅依赖于CoffeeMachine接口。它不依赖于任何接口实现。

public class CoffeeApp { 
private CoffeeMachine coffeeMachine;
 @Inject 
public CoffeeApp(CoffeeMachine coffeeMachine) { 
this.coffeeMachine = coffeeMachine; 
public Coffee prepareCoffee(CoffeeSelection selection) throws CoffeeException { 
Coffee coffee = this.coffeeMachine.brewFilterCoffee(); 
System.out.println("Coffee is ready!"); 
return coffee; }
}

在前面的文章中,CoffeeAppStarter类必须实例化CoffeeMachine接口的特定实现。它在实例化CoffeeApp对象时将该对象作为构造函数参数提供。

构造函数注入现在允许您将编译时对特定实现类的依赖项替换为对任何实现类的运行时依赖项。这使得替换CoffeeMachine实现非常容易。您只需要在启动应用程序时将CoffeeMachine接口的不同实现添加到您的类路径中。

总结

SOLID的依赖倒置原则引入了高层次类与其依赖项之间的接口。它将高级类与它的依赖关系分离开来,这样您就可以在不改变使用它的代码的情况下更改低级类的代码。唯一直接使用依赖项的代码是实例化实现接口的特定类的对象的代码。

依赖注入技术使您能够进一步改进这一点。它提供了一种将对象的创建与使用分离的方法。通过这样做,您可以在不更改任何代码的情况下替换依赖项,而且还可以减少业务逻辑中的重复的样板代码。

猜你喜欢

转载自blog.csdn.net/flysnowjava/article/details/80777002