當我們在談論建構函式注入的時候我們在談論什麼

京東雲開發者發表於2023-11-01

依賴注入

當涉及依賴注入(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 轉載請註明來源

相關文章