△Hollis, 一个对Coding有着独特追求的人△

这是Hollis的第 371 篇原创分享

作者 l Hollis

来源 l Hollis(ID:hollischuang)

我们在日常开发中,经常会遇到类似的场景:当要做一件事儿的时候,这件事儿的步骤是固定好的,但是每一个步骤的具体实现方式是不一定的。

通常,遇到这种情况,我们会把所有要做的事儿抽象到一个抽象类中,并在该类中定义一个模板方法。这就是所谓的模板方法模式。

以前的模板方法

在我之前的一篇《设计模式——模板方法设计模式》https://www.hollischuang.com/archives/420 文章中举过一个例子:

当我们去银行的营业厅办理业务需要以下步骤:1.取号、2.办业务、3.评价。

三个步骤中取号和评价都是固定的流程,每个人要做的事儿都是一样的。但是办业务这个步骤根据每个人要办的事情不同所以需要有不同的实现。

我们可以将整个办业务这件事儿封装成一个抽象类:

/*** 模板方法设计模式的抽象类* @author hollis*/public abstract class AbstractBusinessHandler {/*** 模板方法*/public final void execute(){getNumber();handle();judge();}/*** 取号* @return*/private void getNumber(){System.out.println("number-00" + RandomUtils.nextInt());}/*** 办理业务*/public abstract void handle(); //抽象的办理业务方法,由子类实现/*** 评价*/private void judge(){System.out.println("give a praised");}}

我们在类中定义了一个execute类,这个类编排了getNumber、handle和judge三个方法。这就是一个模板方法

其中getNumber和judge都有通用的实现,只有handle方法是个抽象的,需要子类根据实际要办的业务的内容去重写。

有了这个抽象类和模板方法,当我们想要实现一个"存钱业务"的时候,只需要继承该AbstractBusinessHandeler并且重写handle方法即可:

public class SaveMoneyHandler extends AbstractBusinessHandeler {@Overridepublic void handle() {System.out.println("save 1000");}}

这样,我们在执行存钱的业务逻辑的时候,只需要调用 SaveMoneyHandler的execute方法即可:

public static void main(String []args){SaveMoneyHandler saveMoneyHandler = new SaveMoneyHandler();saveMoneyHandler.execute();}

输出结果:

number-00958442164save 1000give a praised

以上,就是一个简单的模板方法的实现。通过使用模板方法,可以帮助我们很大程度的复用代码。

因为我们要在银行办理很多业务,所以可能需要定义很多的实现类:

//取钱业务的实现类public class DrawMoneyHandler extends AbstractBusinessHandeler {@Overridepublic void handle() {System.out.println("draw 1000");}}//理财业务的实现类public class MoneyManageHandler extends AbstractBusinessHandeler{@Overridepublic void handle() {System.out.println("money manage");}}

一直以来,开发者们在使用模板方法的时候基本都是像上面这个例子一样:需要准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法来让子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。

但是,有了Java 8以后,模板方法有了另外一种实现方式,不需要定义特别多的实现类了。

Java 8 的函数式编程

2014年,Oracle发布了 Java 8,在Java 8中最大的新特性就是提供了对函数式编程的支持。

Java 8在java.util.function下面增加增加一系列的函数接口。其中主要有Consumer、Supplier、Predicate、Function等。

本文主要想要介绍一下Supplier和Consumer这两个,使用者两个接口,可以帮我们很好的改造模板方法。这里只是简单介绍下他们的用法,并不会深入展开,如果大家想要学习更多用法,可以自行google一下。

Supplier

Supplier是一个供给型的接口,简单点说,这就是一个返回某些值的方法。

最简单的一个Supplier就是下面这段代码:

public List<String> getList() {return new ArrayList();}

使用Supplier表示就是:

Supplier<List<String>> listSupplier = ArrayList::new;

Consumer

Consumer 接口消费型接口,简单点说,这就是一个使用某些值(如方法参数)并对其进行操作的方法。

最简单的一个Consumer就是下面这段代码:

public void sum(String a1) {System.out.println(a1);}

使用Consumer表示就是:

Consumer<String> printConsumer = a1 -> System.out.println(a1);

Consumer的用法,最见的的例子就是是Stream.forEach(Consumer)这样的用法,

它接受一个Consumer,该Consumer消费正在迭代的流中的元素,并对每个元素执行一些操作,比如打印:

Consumer<String> stringConsumer = (s) -> System.out.println(s.length());Arrays.asList("ab", "abc", "a", "abcd").stream().forEach(stringConsumer);

Java 8以后的模板方法

在介绍过了Java 8中的Consumer、Supplier之后,我们来看下怎么改造之前我们介绍过的模板方法。

首先,我们定义一个BankBusinessHandler类,并且重新定义一个execute方法,这个方法有一个入参,是Consumer类型的,然后移除handle方法,重新编排后的模板方法内容如下:

/*** @author Hollis*/public class BankBusinessHandler {private void execute(Consumer<BigDecimal> consumer) {getNumber();consumer.accept(null);judge();}private void getNumber() {System.out.println("number-00" + RandomUtils.nextInt());}private void judge() {System.out.println("give a praised");}}

我们实现的模板方法execute中,编排了getNumber、judge以及consumer.accept,这里面consumer.accept就是具体的业务逻辑,可能是存钱、取钱、理财等。需要由其他方法调用execute的时候传入。

这时候,我们想要实现"存钱"业务的时候,需要BankBusinessHandler类中增加以下方法:

/*** @author Hollis*/public class BankBusinessHandler {public void save(BigDecimal amount) {execute(a -> System.out.println("save " + amount));}}

在save方法中,调用execute方法,并且在入参处传入一个实现了"存钱"的业务逻辑的Comsumer。

这样,我们在执行存钱的业务逻辑的时候,只需要调用 BankBusinessHandler的save方法即可:

public static void main(String[] args) throws {BankBusinessHandler businessHandler = new BankBusinessHandler();businessHandler.save(new BigDecimal("1000"));}

输出结果:

number-001736151440save1000give a praised

如上,当我们想要实现取钱、理财等业务逻辑的时候,和存钱类似:

/*** @author Hollis*/public class BankBusinessHandler {public void save(BigDecimal amount) {execute(a -> System.out.println("save " + amount));}public void draw(BigDecimal amount) {execute(a -> System.out.println("draw " + amount));}public void moneyManage(BigDecimal amount) {execute(a -> System.out.println("draw " + amount));}}

可以看到,通过使用Java 8中的Comsumer,我们把模板方法改造了,改造之后不再需要抽象类、抽象方法,也不再需要为每一个业务都创建一个实现类了。我们可以把所有的业务逻辑内聚在同一个业务类中。这样非常方便这段代码的后期运维。

前面介绍如何使用Consumer进行改造模板方法,那么Supplier有什么用呢?

我们的例子中,在取号、办业务、评价这三个步骤中,办业务是需要根据业务情况进行定制的,所以,我们在模板方法中,把办业务这个作为扩展点开放给外部。

有这样一种情况,那就是现在我们办业务的时候,取号的方式也不一样,可能是到银行网点取号、在网上取号或者银行客户经理预约的无需取号等。

无论取号的方式如何,最终结果都是取一个号;而取到的号的种类不同,可能接收到的具体服务也不同,比如vip号会到VIP柜台办理业务等。

想要实现这样的业务逻辑,就需要使用到Supplier,Supplier是一个"供给者",他可以用来定制"取号逻辑"。

首先,我们需要改造下模板方法:

/*** 模板方法*/protected void execute(Supplier<String> supplier, Consumer<BigDecimal> consumer) {String number = supplier.get();System.out.println(number);if (number.startsWith("vip")) {//Vip号分配到VIP柜台System.out.println("Assign To Vip Counter");}else if (number.startsWith("reservation")) {//预约号分配到专属客户经理System.out.println("Assign To Exclusive Customer Manager");}else{//默认分配到普通柜台System.out.println("Assign To Usual Manager");}consumer.accept(null);judge();}

经过改造,execute的入参增加了一个supplier,这个supplier可以提供一个号码。至于如何取号的,交给调用execute的方法来执行。

之后,我们可以定义多个存钱方法,分别是Vip存钱、预约存钱和普通存钱:

public class BankBusinessHandler extends AbstractBusinessHandler {public void saveVip(BigDecimal amount) {execute(() -> "vipNumber-00" + RandomUtils.nextInt(), a -> System.out.println("save " + amount));}public void save(BigDecimal amount) {execute(() -> "number-00" + RandomUtils.nextInt(), a -> System.out.println("save " + amount));}public void saveReservation(BigDecimal amount) {execute(() -> "reservationNumber-00" + RandomUtils.nextInt(), a -> System.out.println("save " + amount));}}

在多个不同的存钱方法中,实现不同的取号逻辑,把取号逻辑封装在supplier中,然后传入execute方法即可。

测试代码如下:

BankBusinessHandler businessHandler = new BankBusinessHandler();businessHandler.saveVip(new BigDecimal("1000"));

输出结果:

vipNumber-001638110566Assign To Vip Countersave 1000give a praised

以上,我们就是用Comsumer和Supplier改造了模板方法模式。

使用Java 8对模板方法进行改造之后,可以进一步的减少代码量,至少可少创建很多实现类,大大的减少重复代码,提升可维护性。

当然,这种做法也不是十全十美的,有一个小小的缺点,那就是理解成本稍微高一点,对于那些对函数式编程不太熟悉的开发者来说, 上手成本稍微高了一些。。。

总结

以上,我们介绍了什么是模板方法模式,以及如何使用Comsumer和Supplier改造模板方法模式。

这样的做法是我们日常开发中经常会用到的,其实,我觉得本文中的例子并不是完完全全能表达出来我想表达的意思,但是我们的真实业务中的逻辑讲起来又比较复杂。

所以,这就需要大家能够多多理解并且实践一下。如果你代码中用到过模板方法模式,那一定是可以通过本文中的方法进行改造的。

如果你还没用过模板方法模式,那说明你的应用中一定有很多重复代码,那就赶紧用起来。

作为一个开发工程师,我们要尽最大努力的消灭应用中的重复代码,功在当代,利在千秋!

技术交流群

最近有很多人问,有没有读者交流群,想知道怎么加入。

最近我创建了一些群,大家可以加入。交流群都是免费的,只需要大家加入之后不要随便发广告,多多交流技术就好了。

目前创建了多个交流群,全国交流群、北上广杭深等各地区交流群、面试交流群、资源共享群等。

有兴趣入群的同学,可长按扫描下方二维码,一定要备注:全国 Or 城市 Or 面试 Or 资源,根据格式备注,可更快被通过且邀请进群。

▲长按扫描


往期推荐

干掉 Postman?测试接口直接生成API文档,这个工具我爱了

程序员千万不要做舔狗啊!

官方认证:软件及信息技术从业者为新生代农民工

如果你喜欢本文,

请长按二维码,关注 Hollis.

转发至朋友圈,是对我最大的支持。

点个 在看 

喜欢是一种感觉

在看是一种支持

↘↘↘

学妹惊呼:使用Java8改造后的模板方法模式真的是yyds相关推荐

  1. 使用Java8改造出来的模板方法真的是yyds

    GitHub 21.3k Star 的Java工程师成神之路,不来了解一下吗! GitHub 21.3k Star 的Java工程师成神之路,真的不来了解一下吗! 我们在日常开发中,经常会遇到类似的场 ...

  2. 淘系学妹分享阿里的20—60KJava岗位JD,真的太难了

    很多人都在纠结面试要准备什么,当然是要具体岗位具体对待了! 我最近研究了一下各大厂 Java 岗位 JD,薪资范围在 20-60K,具体你能拿多少,就要看你的技术点掌握有多少了- 常考的技术点无非就是 ...

  3. 如何向学妹解释在地址栏中输入网址后发生了什么?

    前几天有个学妹问我为什么在浏览器里面输了网址就会显示出来页面,虽然这个现象很常见,但是要想解释清楚确实有些小困难,当时也只是简单的回答了她,现在想趁着这个机会好好整理下相关知识.整理完才觉得其实就和我 ...

  4. 计算机教育中缺失的一课,劝学弟学妹们一句,一定要趁早补上,工作后会如有神助!

    CSDN 的同学们,大家好,今天我是来补课的. 「上一篇」把命令行.终端.Shell 搞清楚后(还没搞清楚的可以点击蓝字过去看一眼),今天这篇写起来就顺畅多了.哦,极度的舒适! 开门见山地说吧.大学里 ...

  5. 学弟学妹们,学会霍夫曼编码后,再也不用担心网络带宽了!

    CSDN 的学弟学妹们,大家好,我是沉默王二. 今天来给大家普及一下霍夫曼编码(Huffman Coding),一种用于无损数据压缩的熵编码算法,由美国计算机科学家大卫·霍夫曼在 1952 年提出-- ...

  6. python记忆口诀-学妹问我: 如何提高编程能力

    聊天截图 聊天截图 前言 开局两张图,剩下全靠吹了. 上面这两张图便是写这篇文章的原由. 对话框的另一边,是一位大二计算机科班在读的小姐姐,看似平静的文字背后透露着迷茫与困惑,还对未来的焦虑. 透过屏 ...

  7. python记忆口诀-学妹问我:如何提高编程能力

    聊天截图 聊天截图前言 开局两张图,剩下全靠吹了. 上面这两张图便是写这篇文章的原由. 对话框的另一边,是一位大二计算机科班在读的小姐姐,看似平静的文字背后透露着迷茫与困惑,还对未来的焦虑. 透过屏幕 ...

  8. 奉劝那些刚参加工作的学弟学妹们:要想进大厂,这些并发编程知识是你必须要掌握的!完整学习路线!!(建议收藏)

    大家好,我是冰河~~ 今天给大家带来一篇完整的并发编程学习路线,这应该是全网最全的并发编程学习路线了吧,希望能够为各位小伙伴们带来实质性的帮助. 如果这篇文章对大家有点帮助,小伙伴们点赞,收藏,评论, ...

  9. 学妹问H哥:你是如何平衡工作和生活的?

    作者 l Hollis 来源 l Hollis(ID:hollischuang) 这几天被B站的<后浪>刷屏了,不管别人怎么看,我个人觉得还挺不错的.很多人说<后浪>传达出一种 ...

最新文章

  1. EasyWechat中代金券的发放总结
  2. 华为emui10是鸿蒙,昨日,华为EMUI10,鸿蒙系统正式发布!
  3. python 完全面向对象_史上最全的Python面向对象知识点疏理
  4. unitywebrequest本地加载_Unity AudioSource加载本地.mp3文件/UnityWebRequest
  5. 【转】如何更改VS2010的[默认开发语言]默认环境设置 .
  6. 前端学习(3330):闭包的形式6
  7. php判断url参数为空,PHP检查url链接是否已经有参数的简单示例
  8. static变量 java
  9. oracle dg物理和逻辑,Oracle DG介绍(物理无实例)
  10. CTO怒了:再写if-else,逮着罚款1000!
  11. python编写时钟代码_python Tkinter 编写时钟
  12. vue3 element el-tree 默认选中某个节点高亮
  13. mysql客户端字符集_设置MySQL客户端连接使用的字符集
  14. 如何看待B站疑似源码泄漏的问题?
  15. 初学数学建模软件MATLAB的笔记
  16. 使用win10远程控制ubuntu16.04
  17. vb.net打砖块游戏
  18. Monkeys [POI 2003,Bzoj 2610]
  19. 音视频流媒体————基本概念
  20. 量化策略:如何利用自回归模型构建日内高频策略

热门文章

  1. Ubuntu 20.10 安装 fcitx5 输入法
  2. php数据库删除数据,php数据库删除数据的简单示例
  3. 具体的压栈指令,例子
  4. 数据结构之树的存储结构
  5. 计算机网络之应用层:2、DNS域名解析系统
  6. ACM第一次集训 - 动态规划问题
  7. pymongo 的使用实例(超细)
  8. QT三种窗口、调试终端信息打印、新建菜单、设置窗口标题名称、界面初始化、打开文件对话框、保存文件对话框
  9. 汇编编译器以及 DOSBox0.74的安装使用(虚拟)64位
  10. 栈的顺序存储及实现(一)