首先
這篇主要是翻譯 這篇 的教學, 再用自己的方式解釋。
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, 讓 EmailService
與 SmsService
擁有一致性的操作 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 造成維護上的不便.
優點
- 關注點分離(Separation of Concerns), 對 Application class 來說只需要關注 sendMessage 的動作.
- 方便測試, 對 Application 來說, 只要知道 sendMessage 有被執行, 執行的內容不是重點.
- 解耦合, Application 不強烈依賴任何 instance.
- 操作反轉(IoC), Application 不在需要在操作的時候決定要用 EmailService 還是 SmsService, 而交由外部 class (Main)決定.
缺點
- 維護跟擴增並不一定容易, 假設
FacebookMessage
的 sendMessage 是需要互動的情境, 有回傳值String sendMessage()
, 那應該是寫 2 個 interface ? 還是在相同的 interface 寫 2 個 methods ? - 單純的 Code Review 的階段, 無法了解是被 injection 是哪個 instance ? EmailServiceImpl or SmsServiceImpl ?
沒有留言:
張貼留言