模板方法 (宝,我输液了,输的想你的夜)

14,283 阅读8分钟

本文 GitHub github.com/JavaFamily 已收录,有一线大厂面试完整考点、资料以及我的系列文章。

大家每到一家公司都会发现,每个公司都会有一个规范,比如说请假流程规范,代码规范等等。每个公司都有这个流程,只是里面的具体执行条件不一样而已。

在设计模式中的模版方法模式,也是可以理解为一种规范模版。主要是提升我们代码的复用性,以及扩展等问题。

这样的模板方法在我们当舔狗跟妹妹们聊天的时候也是可以用到的,比如这样一个模板:

“宝,XXXX了,XXXX什么XX?X你的XXX”

当我拿到这样一个模板的时候,我就可以举一反三直接套用了,我们直接填参数就可以了,比如:

“宝,我打疫苗了,打的什么苗 ,爱你的每一秒 ”

“宝,我做核酸了,做的什么酸,得不到你的心酸”

“宝,今天去输液了,输的什么液,想你的夜”

...........

好了言归正传,在框架中模版方法模式也是很常见的。

今天就具体来聊聊设计模式中行为型设计模式中模版方法模式

设计模式系列往期文章:

大纲

还是老规矩从上图五个方面来分别具体和大家聊聊模版方法模式

定义

模版方法模式的定义以及目的?

  • 定义:模板方法模式在一个方法中定义一个算法骨架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤

  • 目的:1.使用模版方法模式的目的是避免编写重复代码,以便开发人员可以专注于核心业务逻辑的实现

    ​ 2.解决接口与接口实现类之间继承矛盾问题

    以上定义来自《设计模式之美》

结构图:

  • AbstractTemplate(抽象模版):定义一系列抽象方法,或者实现的方法,又或者是钩子方法。即:定义流程
  • ConcreteTemplate(具体模版):实现父类抽象方法,基于本身不同的模版业务逻辑,实现不同的业务逻辑代码。即:抽象方法实现相同,内部逻辑不同

整个结构图看起来还是很简单的,但是还是要理解设计模式解决什么问题。

代码实现?还是举例吧。

还是以上面的请假举例吧,假设现在A公司请假需要直属领导审批以及通知HR有人请假了就可以了,B公司需要直属领导,部门负责人审批最后通知HR,方能完成整个请假流程。那作为OA办公流程怎么去处理这个问题嘛?直接看代码实现吧!

public abstract class AskForLeaveFlow {

    // 一级组长直接审批
    protected abstract void firstGroupLeader(String name);

    // 二级组长部门负责人审批
    protected void secondGroupLeader(String name) {
    }

    // 告知HR有人请假了
    private final void notifyHr(String name) {
        System.out.println("当前有人请假了,请假人:" + name);
    }

    // 请假流模版
    public void askForLeave(String name) {
        firstGroupLeader(name);
        secondGroupLeader(name);
        notifyHr(name);
    }

}

首先还是定义一个请假流程,其中:

firstGroupLeader方法为abstract修饰,则作为子类都是必须要实现的

secondGroupLeader 二级领导审批,在子类中可以重写,也可不重写

notifyHr 方法为通知HR,已经内部实现

最后一个askForLeave请假流程方法,把以上模版方法串起来

public class CompanyA extends AskForLeaveFlow {
    
    @Override
    protected void firstGroupLeader(String name) {
        System.out.println("CompanyA 组内有人请假,请假人:" + name);
    }
}

public class CompanyB extends AskForLeaveFlow {
    @Override
    protected void firstGroupLeader(String name) {
        System.out.println("CompanyB 组内有人请假,请假人:" + name);
    }
    @Override
    protected void secondGroupLeader(String name){
        System.out.println("CompanyB 部门有人请假,请假人:" + name);
    }
}

在CompanyA以及CompanyB中,secondGroupLeader二级领导可以选择重写或者不重写,这个类模版方法简称为钩子方法。

public class testTemplate {
    public static void main(String[] args) {
        // 公司A请假流程模版
        AskForLeaveFlow companyA = new CompanyA();
        companyA.askForLeave("敖丙");
        // 结果:CompanyA 组内有人请假,请假人:敖丙
        //       当前有人请假了,请假人:敖丙

        AskForLeaveFlow companyB = new CompanyB();
        companyB.askForLeave("敖丙");
        // 结果:CompanyB 组内有人请假,请假人:敖丙
        //      CompanyB 部门有人请假,请假人:敖丙
        //      当前有人请假了,请假人:敖丙
    }
}

最后就是看测试dome结果了。companyA和companyB分别输出了对应的请假流程。

细心的同学可能已经发现了,做为模版方法中里面除了可以有抽象方法外,还可以有具体的实现方法以及钩子方法。

所以大家在应用的过程可以多考虑考虑在内部定义模版方法时,应该定义成抽象方法还是其它的。

框架中的应用

模版方法模式在我们常见的Java的框架中也是非常常见的,只是可能我们平时没有注意到这一点而已。

第一个:首先我们学SpringMVC的时候,最开始都会写一些Servlet来作为处理一些post或者get请求等。

这里直接看这个源码大家就可以发现这也是直接使用模版方法模式的思想,期间在HttpServlet 继承GenericServlet中也还是模版方法的体现,这说明了可以多次抽象构建模版。

第二个:常见问的文件流中,Java IO 类中的InputStream、OutputStream、Reader、Writer等都能看到模版方法模式的身影。

上面是我贴出的部分InputStream的源码,主要看这个read模版方法,也就是模版方法模式的体现。

当然IO类中还有很多其他的,我就不一一贴源码出来了,感情兴趣的同学,可以自己打开源码了解了解。

业务举例

在业务中怎么使用模版方法?

首先需要理解模版方法它是为了增加代码的复用性,以及扩展性而存在的,所以本着这个思想我还是给大家举一个例子吧。

之前写责任链模式最后给大家举例商品详情,这次还是用商品详情,但是用模版方法模式来实现这个问题,理解为商详2.0版本。

商品详情展示我们可以是分模块展示的,比如头图,商品信息,sku信息,配送地址,分期付费等等。

那么怎么进行组装到商品详情的展示呢?

流程图:

可以看到一个请求过来,可以有模块组装器选择组装返回结果。

提一个点,在第二步请求的模块的时候为了减少整个链路的请求时间可以考虑是串行,或者并行(开线程池处理)。

接下来直接看代码吧

public abstract class AbstractTemplateBlock<T> {
    // 组装结果
    public T template(ModelContainer modelContainer) {
        T block = initBlock();
        try {
            this.doWork(modelContainer, block);
        } catch (Exception e) {
            // 可以选择捕获异常,是中断流程,还是只打印日志,不中断流程
        }
        return block;
    }
    // 初始化构建返回结果模型
    protected abstract T initBlock();
    // 定义抽象模版
    protected abstract void doWork(ModelContainer modelContainer, T block) throws Exception;
}

还是先创建模版Block

@Component
public class ItemInfoBlock extends AbstractTemplateBlock<ItemInfoBlock.ItemInfo> {
    @Override
    protected ItemInfoBlock.ItemInfo initBlock() {
        return new ItemInfoBlock.ItemInfo();
    }

    // 模拟业务逻辑,组装返回商品信息模块数据
    @Override
    protected void doWork(ModelContainer modelContainer, ItemInfo block) throws Exception {
        block.setItemId(123L);
        block.setItemName("测试");
    }
    @Data
    public static class ItemInfo {
        private Long itemId;
        private String itemName;
    }
}

这里只写了一个ItemInfoBlock,其他的模块也是这一样的写法,所以就不全写出来了。

    public static void main(String[] args) {
        // 1.模拟获取SpringBean
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
        ItemInfoBlock itemInfoBlock = (ItemInfoBlock) applicationContext.getBean("itemInfoBlock");

      // 2. ModelContainer可以理解为贯穿上下文中的请求参数,或者一些组装数据需要的预加载数据
       ModelContainer modelContainer  = new ModelContainer();
       // 3. 获取返回结果
       ItemInfoBlock.ItemInfo itemInfo = itemInfoBlock.template(modelContainer);
       System.out.println(JSON.toJSONString(itemInfo));
       // 结果:{"itemId":123,"itemName":"测试"}
    }

最后就是看测试demo了,可以看到再每一个模块中都是有一个AbstractTemplateBlock,内部包含doWork抽象方法,由子类去实现当前自己的业务逻辑。

同时第三步获取返回结果时,我只是单独列出来,大家可以根据实际情况还能做改造。比如说返回map结构等 mapKey 是模块名称,value是数据。

当前这种组装商品详情的模式也是比较常见的一种方式。代码的复用性高,同时扩展性也有一定的体现,符合模版方法模式的思想。

总结

模版方法模式的特点大家应该也能体会到了,适用场景还是为了增加代码的复用性,以及扩展性。

还是那句话存在即合理,不要因设计模式而在写代码时强行嵌套。合理的学习每种设计模式适合场景,解决什么问题。

宝,明天我可能无法正常更新了,我生病了在输液。

我是敖丙,你知道的越多,你不知道的越多,感谢各位人才的:点赞收藏评论,我们下期见!


文章持续更新,可以微信搜一搜「 三太子敖丙 」第一时间阅读,回复【资料】有我准备的一线大厂面试资料和简历模板,本文 GitHub github.com/JavaFamily 已经收录,有大厂面试完整考点,欢迎Star。