动态

详情 返回 返回

當我們在談論構造函數注入的時候我們在談論什麼 | 京東物流技術團隊 - 动态 详情

依賴注入

當涉及依賴注入(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容器)協同工作得很好。您可以使用構造函數注入來定義組件的依賴關係,並讓容器負責創建和管理對象的生命週期。

@Component
public 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 轉載請註明來源

user avatar greatsql 头像 front_yue 头像 linx 头像 anchen_5c17815319fb5 头像 haijun_5e7e16c909f52 头像 aaaaaajie 头像 an_653b347d1d3da 头像 xiaofeixiang_63ec941cad48a 头像 syfssb 头像 gaiya_68f1e7ba5b140 头像 yifu 头像 shishui_63822321355cb 头像
点赞 12 用户, 点赞了这篇动态!
点赞

Add a new 评论

Some HTML is okay.