Java Dependency Injection

首先

這篇主要是翻譯 這篇 的教學, 再用自己的方式解釋。

Email Service

文章開頭以 Email Service 來解釋, UML 應該是長這樣, Application 強依賴 EmailService, Application 在一開始就初始化了 EmailService, 而 Main 也強依賴 Application, 這樣寫並沒有太大的問題, 但不容易對 Application class 做測試, 因為不容易對 EmailService 做 mock.



修正 Application 方便測試

為了讓 Application 能夠被測試, 所以改成 inject EmailService 的建構方式




public class Application {

    private EmailService emailService;

    public Application(EmailService emailService) {
        this.emailService = emailService;
    }

    public void notification(String message, String receiver) {
        this.emailService.sendEmail(message, receiver);
    }
}

簡單的使用 Mockito 來 mock EmailService 測試

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>2.12.0</version>
</dependency>


package dependency.injection;

import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

/**
 * Created by jerry on 2017/11/21.
 */
public class ApplicationTest {

    @Mock
    private EmailService mockEmailService;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void assertApplication() {
        Application app = new Application(mockEmailService);

        // trigger
        app.notification("test-message", "someone");

        // mock
        doNothing()
                .when(mockEmailService)
                .sendEmail(anyString(), anyString());

        // verify emailService will be call once
        verify(mockEmailService, times(1));
    }

}

MessageService Interface

Interface 的好處, 是讓我們方便活用物件導向的多型(Polymorphism)概念, 透過 MessageService Interface, 讓 EmailServiceSmsService 擁有一致性的操作 void sendMessage(), 好處是 Application 不再依賴一個明確的 class instance, 而是依賴一個抽象的 interface, 對 Application 來說, 只要能做 sendMessage 的物件都可以接受, Application 不再 “強烈” 依賴 EmailService 或是 SmsService.

日後擴增 FacebookMessage 只要 implement MessageService, 也一樣可以被 Application 使用.




public interface MessageService {

    void sendMessage(String message, String receiver);
}


public class EmailServiceImpl implements MessageService {

    @Override
    public void sendMessage(String message, String receiver) {
        System.out.println("Email sent to " + receiver + " with Message=" + message);
    }
}


public class SmsServiceImpl implements MessageService {

    @Override
    public void sendMessage(String message, String receiver) {
        System.out.println("SMS sent to " + receiver + " with Message=" + message);
    }
}


public class Application {

    private MessageService messageService;

    public Application(MessageService messageService) {
        this.messageService = messageService;
    }

    public void notification(String message, String receiver) {
        this.messageService.sendMessage(message, receiver);

    }
}


public class Main {

    public static void main(String[] args) {
        Application app = new Application(new EmailServiceImpl());
        app.notification("nice to meet you", "someone");
    }
}

Consumer

文章末端, 用了 Consumer interface 抽象了原本的 Application, 而 consumer 做的也只是 sendMessage 這個動作, 多墊一個 interface 好處並不多, 反而增加多餘的 code 造成維護上的不便.


優點

  1. 關注點分離(Separation of Concerns), 對 Application class 來說只需要關注 sendMessage 的動作.
  2. 方便測試, 對 Application 來說, 只要知道 sendMessage 有被執行, 執行的內容不是重點.
  3. 解耦合, Application 不強烈依賴任何 instance.
  4. 操作反轉(IoC), Application 不在需要在操作的時候決定要用 EmailService 還是 SmsService, 而交由外部 class (Main)決定.

缺點

  1. 維護跟擴增並不一定容易, 假設 FacebookMessage 的 sendMessage 是需要互動的情境, 有回傳值 String sendMessage(), 那應該是寫 2 個 interface ? 還是在相同的 interface 寫 2 個 methods ?
  2. 單純的 Code Review 的階段, 無法了解是被 injection 是哪個 instance ? EmailServiceImpl or SmsServiceImpl ?


沒有留言:

張貼留言