Command
January 1, 2022如果你負責撰寫測試用的工具程式,一開始你提供了以下的工具:
public class Assert {
public static void assertEquals(int expected, int result) {
if(expected == result) {
System.out.println("正確!");
}
else {
System.out.printf(
"失敗,預期為 %d,但是傳回 %d!%n", expected, result);
}
}
}
這可以讓同事撰寫以下的測試:
public class CalculatorPlusTest {
public static void main(String[] args) {
var calculator = new Calculator();
var expected = 3;
var result = calculator.plus(1, 2);
Assert.assertEquals(expected, result);
}
}
接著,同事在 Calculator
類別中新增了 minus
方法,並撰寫另一個測試:
public class CalculatorMinusTest {
public static void main(String[] args) {
var calculator = new Calculator();
var expected = 1;
var result = calculator.minus(3, 2);
Assert.assertEquals(expected, result);
}
}
現在要執行測試的話,他得分別運行 CalculatorPlusTest
、CalculatorMinusTest
,同事希望有個執行器,可以收集已建立的測試案例並執行。
定義/執行
同事的測試案例會怎麼寫,你當然一無所知,一無所知表示實作不明,那就定義了抽象的 Test
吧!
public interface Test {
void test();
}
你只相依在抽象的 Test
,現在可以寫個 TestRunner
:
public class TestRunner {
private List<Test> tests = new ArrayList<>();
public void add(Test test) {
tests.add(test);
}
public void run() {
for(Test test : tests) {
test.run();
}
}
}
同事如果要撰寫測試案例,就實作 Test
介面,例如:
public class CalculatorPlusTest implements Test {
@Override
public void run() {
var calculator = new Calculator();
var expected = 3;
var result = calculator.plus(1, 2);
Assert.assertEquals(expected, result);
}
}
不管有幾個測試案例,總之就是使用你的 TestRunner
來收集並執行:
public class CalculatorTest {
public static void main(String[] args) {
TestRunner runner = new TestRunner();
runner.add(new CalculatorPlusTest());
runner.add(new CalculatorMinusTest());
runner.run();
}
}
在這個情境中,同事定義測試案例,你撰寫的 TestRunner
執行測試案例,定義與執行是分離的,Gof 將此概念命名為 Command 模式。
好像很常見?
嗯?怎麼覺得有點熟悉?不!不是熟悉,這不是到處都看得到嗎?例如執行緒程式設計中,不是就定義 Runnable
嗎?
// Runnable 是 Funnctional interface,直接寫 lambda 表示式
Runnable runnable = () -> {
你的流程
};
new Thread(runnable).start();
難道這也實現了 Command?是啊!你定義指令(Runnable
),執行指令(Runnable
)的是 Thread
。那麼事件處理呢?你可以自定義事件處理器,發生事件時元件會執行相對應的方法也是嗎?是啊!…照這樣的說法,那麼不就任何可以自定義指令,由另一個角色執行指令的設計,都算是 Command 的實現?基本上就是如此!
先前的文件中,也早就有過一些例子了,例如,〈Composite〉的範例中,Material
與 Playlist
就組合來看是 Composite,然而就「定義/執行」的行為關係,就是 Command 的實現。
當然,也可以有其他角度的看法,例如,某個設計就「定義/執行」的行為關係是 Command 的實現,然而就「抽換策略」的行為來看,或許就是 Strategy,然後就「XX」的角度來看,可能又是 GGYY 模式…模式有時候想表達的是一種思考角度,有時候甚至想傳達的是某個問題情境。
那那那…Gof 中那個什麼 Invoker、Receiver 就不用理它們了嗎?Gof 中確實是用了一些類別圖之類,來解釋他們使用的範例中具有哪些角色,不過也因此造成很多人誤以為,必須有那些類別圖裡出現的角色,才能稱為是某模式…那些又不是 DIY 組裝說明書…看著圖組裝就能有好的設計…嗯…你是不是搞錯了什麼!?
這也是為何我在漫談模式時,特意不畫圖的原因了,也特意不提供可完整運行的程式碼…有些人容易因此而誤會,也容易因此只看最後的實現成果,而是過程中我聊了些什麼。
你應該擺在觀察、釐清需求,確認想實現什麼目的,在看模式相關的文件或書時,記得!範例只是提供一個情境,讓你可以從中可以怎麼觀察、釐清需求、確認目的,從而知道某語言可以如何實現罷了…