面试的时候别再说你不会设计模式了

前言

最近在设计一个对某个中间件的测试方案,这个测试方案需要包含不同的测试逻辑,但相同的是需要对各个环节进行记录;比如统计耗时、调用通知 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 相关的类都有具体的应用。


crossoverJie专栏
不定期分享互联网技术和个人经验

会crossover的程序猿

5.2k 声望
3.9k 粉丝
0 条评论
推荐阅读
从源码彻底理解 Prometheus/VictoriaMetrics 中的 relabel/metric_configs 配置
背景最近接手维护了公司的指标监控系统,之后踩到坑就没站起来过。。本次问题的起因是我们配置了一些指标的删除策略没有生效: {代码...} 与这两个容易引起误解的配置relabel_configs/metric_relabel_configs有关...

crossoverJie阅读 201

封面图
一文搞懂秒杀系统,欢迎参与开源,提交PR,提高竞争力。早日上岸,升职加薪。
前言秒杀和高并发是面试的高频考点,也是我们做电商项目必知必会的场景。欢迎大家参与我们的开源项目,提交PR,提高竞争力。早日上岸,升职加薪。知识点详解秒杀系统架构图秒杀流程图秒杀系统设计这篇文章一万多...

王中阳Go33阅读 2.5k评论 1

封面图
前端如何入门 Go 语言
类比法是一种学习方法,它是通过将新知识与已知知识进行比较,从而加深对新知识的理解。在学习 Go 语言的过程中,我发现,通过类比已有的前端知识,可以更好地理解 Go 语言的特性。

robin23阅读 3.2k评论 6

封面图
Golang 中 []byte 与 string 转换
string 类型和 []byte 类型是我们编程时最常使用到的数据结构。本文将探讨两者之间的转换方式,通过分析它们之间的内在联系来拨开迷雾。

机器铃砍菜刀24阅读 58k评论 2

年度最佳【golang】map详解
这篇文章主要讲 map 的赋值、删除、查询、扩容的具体执行过程,仍然是从底层的角度展开。结合源码,看完本文一定会彻底明白 map 底层原理。

去去100216阅读 11.5k评论 2

年度最佳【golang】GMP调度详解
Golang最大的特色可以说是协程(goroutine)了, 协程让本来很复杂的异步编程变得简单, 让程序员不再需要面对回调地狱, 虽然现在引入了协程的语言越来越多, 但go中的协程仍然是实现的是最彻底的. 这篇文章将通过分析...

去去100215阅读 11.9k评论 4

计算机网络连环炮40问
本文已经收录到Github仓库,该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点,欢迎star~

程序员大彬14阅读 1.8k

会crossover的程序猿

5.2k 声望
3.9k 粉丝
宣传栏