前言
最近在设计一个对某个中间件的测试计划,这个测试计划须要蕴含不同的测试逻辑,但雷同的是须要对各个环节进行记录;比方统计耗时、调用告诉 API 等雷同的逻辑。
如果每个测试都独自写这些逻辑那无疑是做了许多反复工作了。
基于以上的特色很容易能想到模板办法这个设计模式。
这是一种有下层定义框架,上层提供不同实现的设计模式。
比方装修房子的时候业主能够依照本人的爱好对不同的房间进行装修,然而整体的户型图不能做批改,比方承重墙是必定不能打的。
而这些固定好的条条框框就是下层框架给的束缚,上层不同的实现就有业主本人决定;所以对于整栋楼来说框架都是固定好的,让业主在无限的范畴内自由发挥也不便物业的治理。
具体实现
以我这个案例的背景为例,首先须要定义出下层框架:
Java
Event
接口:
public interface Event { /** * 新增一个工作 */ void addJob(); /** * 单个工作执行结束 * * @param jobName 工作名称 * @param finishCost 工作实现耗时 */ void finishOne(String jobName, String finishCost); /**单个工作执行异样 * @param jobDefine 工作 * @param e 异样 */ void oneException(AbstractJobDefine jobDefine, Exception e); /** * 所有工作执行结束 */ void finishAll();}
public void start() { event.addJob(); try { CompletableFuture.runAsync(() -> { StopWatch watch = new StopWatch(); try { watch.start(jobName); // 不同的子业务实现 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 函数,它是一个形象函数,具体实现交由子类实现;这样不同的测试用例之间也互不烦扰,同时整体的流程完全相同:
- 记录工作数量
- 统计耗时
- 异样记录
等流程。
接下来看看如何应用:
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
同时实现了其中的 run
函数,应用的时候只须要创立不同的实例期待他们都执行实现即可。
以前在 Java 中也有不同的利用:
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 天然也能够实现:
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 相似,创立不同的实例;最初期待所有的工作执行结束。
总结
设计模式往往是对某些共性能力的形象,但也没有一个设计模式能够实用于所有的场景;须要对不同的需要抉择不同的设计模式。
至于在工作中如何进行正确的抉择,那就须要本人日常的积攒了;比方多去理解不同的设计模式对于的场景,或者多去浏览优良的代码,Java 中的 InputStream/Reader/Writer
这类 IO 相干的类都有具体的利用。