
前言
最近不出意外的陽了,加上剛?cè)肼毿鹿静痪?,所以也沒怎么更新;這兩天好些后分享一篇前段時間的一個案例:
最近在設(shè)計一個對某個中間件的測試方案,這個測試方案需要包含不同的測試邏輯,但相同的是需要對各個環(huán)節(jié)進(jìn)行記錄;比如統(tǒng)計耗時、調(diào)用通知 API 等相同的邏輯。
如果每個測試都單獨(dú)寫這些邏輯那無疑是做了許多重復(fù)工作了。
基于以上的特征很容易能想到模板方法這個設(shè)計模式。
這是一種有上層定義框架,下層提供不同實現(xiàn)的設(shè)計模式。
比如裝修房子的時候業(yè)主可以按照自己的喜好對不同的房間進(jìn)行裝修,但是整體的戶型圖不能做修改,比如承重墻是肯定不能打的。
而這些固定好的條條框框就是上層框架給的約束,下層不同的實現(xiàn)就有業(yè)主自己決定;所以對于整棟樓來說框架都是固定好的,讓業(yè)主在有限的范圍內(nèi)自由發(fā)揮也方便物業(yè)的管理。
具體實現(xiàn)
以我這個案例的背景為例,首先需要定義出上層框架:
Java
Event 接口:
public interface Event {
/**
* 新增一個任務(wù)
*/
void addJob();
/**
* 單個任務(wù)執(zhí)行完畢
*
* @param jobName 任務(wù)名稱
* @param finishCost 任務(wù)完成耗時
*/
void finishOne(String jobName, String finishCost);
/**單個任務(wù)執(zhí)行異常
* @param jobDefine 任務(wù)
* @param e 異常
*/
void oneException(AbstractJobDefine jobDefine, Exception e);
/**
* 所有任務(wù)執(zhí)行完畢
*/
void finishAll();
}
public void start(){
event.addJob();
try {
CompletableFuture.runAsync(() -> {
StopWatch watch = new StopWatch();
try {
watch.start(jobName);
// 不同的子業(yè)務(wù)實現(xiàn)
run(client);
} catch (Exception e) {
event.oneException(this, e);
} finally {
watch.stop();
event.finishOne(jobName, StrUtil.format("cost: {}s", watch.getTotalTimeSeconds()));
}
}, TestCase.EXECUTOR).get(timeout, TimeUnit.SECONDS);
} catch (Exception e) {
event.oneException(this, e);
}
}
/** Run busy code
* @param client
* @throws Exception e
*/
public abstract void run(Client client) throws Exception;
其中最核心的就是 run 函數(shù),它是一個抽象函數(shù),具體實現(xiàn)交由子類完成;這樣不同的測試用例之間也互不干擾,同時整體的流程完全相同:
- 記錄任務(wù)數(shù)量
- 統(tǒng)計耗時
- 異常記錄
等流程。
接下來看看如何使用:
AbstractJobDefine job1 = new Test1(event, "測試1", client, 10);
CompletableFuture<Void> c1 = CompletableFuture.runAsync(job1::start, EXECUTOR);
AbstractJobDefine job2 = new Test2(event, "測試2", client, 10);
CompletableFuture<Void> c2 = CompletableFuture.runAsync(job2::start, EXECUTOR);
AbstractJobDefine job3 = new Test3(event, "測試3", client, 20);
CompletableFuture<Void> c3 = CompletableFuture.runAsync(job3::start, EXECUTOR);
CompletableFuture<Void> all = CompletableFuture.allOf(c1, c2, c3);
all.whenComplete((___, __) -> {
event.finishAll();
client.close();
}).get();
顯而易見 Test1~3 都繼承了 AbstractJobDefine 同時實現(xiàn)了其中的 run 函數(shù),使用的時候只需要創(chuàng)建不同的實例等待他們都執(zhí)行完成即可。
以前在 Java 中也有不同的應(yīng)用:
?https://crossoverjie.top/2019/03/01/algorithm/consistent-hash/?highlight=%E6%A8%A1%E6%9D%BF%E6%96%B9%E6%B3%95#%E6%A8%A1%E6%9D%BF%E6%96%B9%E6%B3%95?。
Go
同樣的示例用 Go 自然也可以實現(xiàn):

func TestJobDefine_start(t *testing.T) {
event := NewEvent()
j1 := &JobDefine{
Event: event,
Run: &run1{},
JobName: "job1",
Param1: "p1",
Param2: "p2",
}
j2 := &JobDefine{
Event: event,
Run: &run2{},
JobName: "job2",
Param1: "p11",
Param2: "p22",
}
j1.Start()
j2.Start()
for _, ch := range event.GetChan() {
<-ch
}
log.Println("finish all")
}
func (r *run2) Run(param1, param2 string) error {
log.Printf("run3 param1:%s, param2:%s", param1, param2)
time.Sleep(time.Second * 3)
return errors.New("test err")
}
func (r *run1) Run(param1, param2 string) error {
log.Printf("run1 param1:%s, param2:%s", param1, param2)
return nil
}
使用起來也與 Java 類似,創(chuàng)建不同的實例;最后等待所有的任務(wù)執(zhí)行完畢。
總結(jié)
設(shè)計模式往往是對某些共性能力的抽象,但也沒有一個設(shè)計模式可以適用于所有的場景;需要對不同的需求選擇不同的設(shè)計模式。
至于在工作中如何進(jìn)行正確的選擇,那就需要自己日常的積累了;比如多去了解不同的設(shè)計模式對于的場景,或者多去閱讀優(yōu)秀的代碼,Java 中的 InputStream/Reader/Writer 這類 IO 相關(guān)的類都有具體的應(yīng)用。