写点什么

当我们在谈论构造函数注入的时候我们在谈论什么 | 京东物流技术团队

  • 2023-11-01
    北京
  • 本文字数:3361 字

    阅读完需:约 11 分钟

当我们在谈论构造函数注入的时候我们在谈论什么 | 京东物流技术团队

依赖注入

当涉及依赖注入(Dependency Injection,DI)时,首先推荐使用构造函数注入,因为构造函数注入有很多技术优点,而且还与面向对象的设计原则密切相关。在业界,构造函数注入作为依赖注入的一种最佳实践得到了广泛的认可,在 Spring Framework 的作者之一 Rod Johnson 的观点中也得有体现。


下面是 Spring 官方文档中对于依赖注入的描述:


Since you can mix constructor-based and setter-based DI, it is a good rule of thumb to use constructors for mandatory dependencies and setter methods or configuration methods for optional dependencies. Note that use of the @Autowired annotation on a setter method can be used to make the property be a required dependency; however, constructor injection with programmatic validation of arguments is preferable.


The Spring team generally advocates constructor injection, as it lets you implement application components as immutable objects and ensures that required dependencies are not null. Furthermore, constructor-injected components are always returned to the client (calling) code in a fully initialized state. As a side note, a large number of constructor arguments is a bad code smell, implying that the class likely has too many responsibilities and should be refactored to better address proper separation of concerns.


Setter injection should primarily only be used for optional dependencies that can be assigned reasonable default values within the class. Otherwise, not-null checks must be performed everywhere the code uses the dependency. One benefit of setter injection is that setter methods make objects of t hat class amenable to reconfiguration or re-injection later. Management through JMX MBeans is therefore a compelling use case for setter injection. ——Spring 官网原文链接 https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-collaborators.html


在本文中,我们将更深入地探讨为何构造函数注入被认为是最佳实践,并将通过详细的 Java 代码示例来阐明其优点。同时,我们将研究如何将构造函数注入与面向对象的设计理念相结合,特别是如何确保封装、单一责任、不变性和依赖倒置原则得以遵循。

有哪些优势

构造函数注入的重要性

依赖注入是一种关键的技术,可以提高应用程序的可测试性和可维护性。Rod Johnson 在他的书中明确指出,通过将依赖项注入到对象中,可以更轻松地进行单元测试,同时降低了对象之间的耦合度。这正是构造函数注入所实现的。当我们在对象的构造函数中传递依赖项时,我们不仅提供了明确的依赖关系,还提高了代码的清晰度。让我们通过一个示例来看看构造函数注入的工作方式。


public class OrderService {    private final OrderRepository orderRepository;
public OrderService(OrderRepository orderRepository) { this.orderRepository = orderRepository; } // ...}
复制代码


在上述示例中,OrderService的构造函数接受一个OrderRepository作为参数,明确指定了其依赖关系。这不仅提高了代码的可读性,还使得单元测试变得更加容易。您可以轻松地创建一个模拟的OrderRepository并将其传递给OrderService的构造函数,以进行单元测试。

构造函数注入与封装特性

面向对象编程强调封装特性,即将数据和行为封装在类的内部,通过公共接口来访问对象。构造函数注入有助于维护封装特性,因为它允许您在对象内部设置依赖项,而不需要向外部暴露 setter 方法。这符合依赖倒置原则和接口隔离原则的思想。


通过将依赖项作为构造函数参数传递,您确保了依赖项在对象内部得到了封装。这意味着外部代码无法直接修改对象的依赖项,从而提高了代码的安全性和稳定性。让我们来看一个例子:


public class CustomerService {    private final CustomerRepository customerRepository;        public CustomerService(CustomerRepository customerRepository) {        this.customerRepository = customerRepository;    }        public Customer getCustomerById(int customerId) {        return customerRepository.findById(customerId);    }    // ...}
复制代码


在上面的示例中,CustomerService依赖于CustomerRepository,并通过构造函数注入的方式获得了该依赖。这确保了customerRepository的封装性,不允许外部代码直接访问或修改它。

构造函数注入与单一责任原则

单一责任原则是面向对象设计的基本原则之一,强调一个类应该只有一个理由去改变。构造函数注入有助于实现这一原则,因为它鼓励每个类专注于执行单一任务,而不负责创建或管理依赖项。通过使用构造函数注入,您可以将依赖项的创建和配置从类中分离出来,使每个类专注于自身的主要职责。这提高了代码的模块化性和可维护性。以下是一个示例:


public class ProductService {    private final ProductRepository productRepository;
public ProductService(ProductRepository productRepository) { this.productRepository = productRepository; }
public List<Product> getAllProducts() { return productRepository.getAll(); }
// ...}
复制代码


在上述示例中,ProductService专注于处理产品相关的业务逻辑,而不需要关心如何创建或配置ProductRepository。这遵循了单一责任原则,使代码更加清晰和可维护。

构造函数注入与不变性

构造函数注入还有助于防止不必要的可变性,因为一旦依赖项被设置,它们通常是不可变的。不可变对象在面向对象设计中具有重要地位,因为它们更容易理解和维护。通过将依赖项注入到对象中并在构造函数中进行初始化,您可以确保依赖项在对象的整个生命周期内保持不变。这有助于减少对象状态的变化,从而提高了代码的可维护性和可预测性。

构造函数注入与依赖注入容器

构造函数注入与依赖注入容器(如 Spring 容器)协同工作得很好。您可以使用构造函数注入来定义组件的依赖关系,并让容器负责创建和管理对象的生命周期。


@Componentpublic class AppConfig {    @Bean    public OrderRepository orderRepository() {        return new JpaOrderRepository();    }
@Bean public OrderService orderService(OrderRepository orderRepository) { return new OrderService(orderRepository); }}
复制代码


在上述示例中,我们使用 Spring 的 Java 配置来定义OrderRepositoryOrderService之间的依赖关系,并通过构造函数注入实现了依赖解析。

构造函数注入与单元测试

构造函数注入使得编写单元测试变得更容易,因为您可以轻松地传递模拟或测试用的依赖项到对象的构造函数中。这样,您可以在不依赖于容器或其他复杂配置的情况下,对类进行单元测试。


public class OrderServiceTest {    @Test    public void testCalculateTotalPrice() {        OrderRepository mockRepository = mock(OrderRepository.class);        when(mockRepository.findOrderById(1)).thenReturn(new Order(1, 100.0));
OrderService orderService = new OrderService(mockRepository);
double totalPrice = orderService.calculateTotalPrice(1); assertEquals(100.0, totalPrice, 0.01); }}
复制代码


上述单元测试中,我们使用构造函数注入创建了一个OrderService的实例,并注入了一个 Mock 的OrderRepository

总结

通过以上示例,阐述了构造函数注入在依赖注入中的价值,以及它如何与面向对象的设计原则协同工作。这不仅提高了代码的可维护性和可测试性,还使其更符合面向对象设计的最佳实践。构造函数注入作为一种强大的工具,有助于构建高质量、可维护和可测试的应用程序。希望通过本文,您能更深入地了解构造函数注入的价值和实践。


参考


https://www.baeldung.com/constructor-injection-in-spring


https://docs.spring.io/spring-framework/reference/core/beans/dependencies/factory-collaborators.html


作者:京东物流 张涛

来源:京东云开发者社区 自猿其说 Tech 转载请注明来源

发布于: 刚刚阅读数: 5
用户头像

拥抱技术,与开发者携手创造未来! 2018-11-20 加入

我们将持续为人工智能、大数据、云计算、物联网等相关领域的开发者,提供技术干货、行业技术内容、技术落地实践等文章内容。京东云开发者社区官方网站【https://developer.jdcloud.com/】,欢迎大家来玩

评论

发布
暂无评论
当我们在谈论构造函数注入的时候我们在谈论什么 | 京东物流技术团队_Java_京东科技开发者_InfoQ写作社区