模板方法模式,顾名思义,就是定义一种模板来承接和完成某种业务或者逻辑的实现。在生活或者我们平时的开发中,经常会不知不觉的使用到这个设计模式,比如我们去银行办理业务,取号->办理业务->评价服务人员就算是一个模板。再比如说我们吃饭,点菜->吃饭->买单也算是一个模板形式的操作逻辑。这种形如某些特定的逻辑和算法不变,只有其中某一个模块或某几个地方需要动态变化的就适合我们使用模板方法模式。
定义:
模板方法模式(TemplateMethod)定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤。
模板方法模式基于继承,在抽象的父类中定义各个算法的模块,算法模块可以是固定的具体实现,例如银行的取号排队,这种不变的业务逻辑完全可以在抽象的父类中声明并写好。同样算法模块也可以是声明一个方法,供不同的子类实现不同的算法或者业务逻辑。除此之外模板方法模式还需要有一个模板方法,它用来将各个算法模块放到一起顺序执行形成一个整体性的业务逻辑。就这样通过使用模板方法模式,可以将我们遇到的一些复杂的业务流程的实现步骤封装在一系列的基本方法中,再通过模板方法定义算法的执行顺序,从而使得不同的子类得到不同的实现。因为模板方法模式里面必须有模板方法的存在,所以只能是抽象类而不能是接口。
这里简单的总结一下Java中抽象类和接口的区别:
- 接口和抽象类都不能被实例化,它们都位于继承树的顶端,用于被其他类实现和继承。
- 接口和抽象类都可以包含抽象方法,实现接口或继承抽象类的普通子类都必须实现这些抽象方法。
- 接口里只能包含抽象方法和默认方法,不能为普通方法提供方法实现;抽象类则可以完全可以包含普通方法。
- 接口里不能定义静态方法;抽象类里可以定义静态方法。
- 接口里只能定义静态常量,不能定义普通成员变量;抽象类里则既可以定义普通成员变量,也可以定义静态常量。
- 接口里不包含构造器;抽象类里可以包含构造器,抽象类里的构造器并不是用于创建对象,而是让其子类调用这些构造器来完成属于抽象类的初始化操作。
- 接口里不能包含初始化块;但是抽象类完全可以包含初始化块。
- 一个类最多只能有一个直接父类,包含抽象类;但一个类可以直接实现多个接口,通过实现多个接口可以弥补Java单继承的不足。
从语法层面上来讲:
-
单继承多接口:子类只能继承一个抽象类实现多个接口;
-
抽象类可以有成员变量,非抽象的方法实现等;而接口只能静态常量,抽象方法
-
抽象类可以实现接口,接口只能实现接口
-
抽象不需要全实现方法,接口必须全实现
从设计层面上来讲:
-
抽象层次不同:抽象类对类抽象,接口对行为抽象;
-
跨域不同:抽象类所跨域的是具有相似特点的类,接口可以跨域不同的类,不过他们有相似的行为,比如飞机和鸟都会飞;
-
设计层次不同。抽象类自下而上,通过提取相似性重构而来;接口是自定义一套规则,所以是自上至下;
抽象类以及接口的一般使用场景:
-
如果要创建一个模型,这个模型将由一些紧密相关的对象采用,就可以使用抽象类。如果要创建将由一些不相关对象采用的功能,就使用接口。
-
如果必须从多个来源继承行为,就使用接口。
-
如果知道所有类都会共享一个公共的行为实现,就使用抽象类,并在其中实现该行为。
好,回到正题,前面说到,模板方法模式的核心就是抽象类的设计,里面包含各个算法的子模块和一个必要的模板方法,结构图如下所示:
模板方法模式包含两个角色:
- AbstractClass(抽象类): 在抽象类中定义了一系列基本操作(PrimitiveOperations),这些基本操作可以是具体的,也可以是抽象的,每一个基本操作对应算法的一个步骤,在其子类中可以重定义或实现这些步骤。同时,在抽象类中实现了一个模板方法(Template Method),用于定义一个算法的框架,模板方法不仅可以调用在抽象类中实现的基本方法,也可以调用在抽象类的子类中实现的基本方法,还可以调用其他对象中的方法。
- ConcreteClass(具体子类): 它是抽象类的子类,用于实现在父类中声明的抽象基本操作以完成子类特定算法的步骤,也可以覆盖在父类中已经实现的具体基本操作。
其中模板方法又可以细分成三类方法:
- 抽象方法(Abstract Method): 一个抽象方法由抽象类声明、由其具体子类实现。
- 具体方法(Concrete Method): 一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承。
- 钩子方法(Hook Method):一个钩子方法由一个抽象类或具体类声明并实现,而其子类可能会加以扩展。通常在父类中给出的实现是一个空实现,并以该空实现作为方法的默认实现,当然钩子方法也可以提供一个非空的默认实现,一般都是返回Boolean类型的值作为判断处理。
首先我们以基本的JDBC为例,数据库连接有着基本的操作,打开连接->打开数据库->使用数据库->关闭连接。其中打开连接可以是数据库连接池,也可以是jdbc-mysql,更可以是oracle,所以连接数据库的操作我们声明成抽象方法供子类继承实现多态,而剩下的数据库基本操作均可以在父类中直接实现。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
package com.glorze.template.method; /** * 数据库操作模板方法抽象类 * @ClassName DbTempalte * @author: glorze.com * @since: 2018年9月8日 下午3:16:21 */ public abstract class BaseDbTempalte { /** * 获得数据库连接,抽象方法供子类继承来进行不同的实现 * @Title: getConnection * @return void * @author: 高老四博客 * @since: 2018年9月8日 下午3:22:03 */ public abstract void getConnection(); public void openConnection() { System.out.println("打开数据库连接"); } public void executeSql() { System.out.println("执行sql"); } public void closeConnection() { System.out.println("关闭数据库连接"); } /** * 声明模板方法,定义以上基本操作的执行顺序 * @Title: handler * @return void * @author: 高老四博客 * @since: 2018年9月8日 下午3:27:56 */ public void handler(){ getConnection(); openConnection(); executeSql(); closeConnection(); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package com.glorze.template.method; /** * 子类继承数据库操作父类 * @ClassName DataBaseOne * @author: glorze.com * @since: 2018年9月8日 下午3:42:43 */ public class DataBaseOne extends BaseDbTempalte { @Override public void getConnection() { System.out.println("使用JDBC-ODBC连接oracle数据库"); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package com.glorze.template.method; /** * 子类继承数据库操作父类 * @ClassName DataBaseTwo * @author: glorze.com * @since: 2018年9月8日 下午3:43:13 */ public class DataBaseTwo extends BaseDbTempalte { @Override public void getConnection() { System.out.println("使用数据库连接池连接数据库"); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
package com.glorze.template.method; /** * 客户端 * @ClassName Client * @author: glorze.com * @since: 2018年9月8日 下午3:43:35 */ public class Client { public static void main(String[] args) { BaseDbTempalte bdt; bdt = new DataBaseOne(); bdt.handler(); System.out.println("----------------"); bdt = new DataBaseTwo(); bdt.handler(); } } |
模板方法模式是通过把不变的行为移动到父类(抽象类),然后去除子类中的重复代码来体现它的优势,从而提供了一个很好的代码复用平台,也帮助子类摆脱重复的不变行为的纠缠。
程序运行结果:
使用JDBC-ODBC连接oracle数据库
打开数据库连接
执行sql
关闭数据库连接
—————-
使用数据库连接池连接数据库
打开数据库连接
执行sql
关闭数据库连接
接下来我们再看一下模板方法中关于钩子方法的使用,之前提到钩子方法一般是两种,一种就是一个虚方法,一个空的实现,具体的业务让子类去重写,还有一种就是返回布尔类型的值从而达到判断是否执行一个基本方法。我们拿图片展示的流程举例,图片需要获取原图->处理图片大小以及质量->向客户端展示图片,其中是否处理图片的大小以及质量是一个通用的算法,可以直接在抽象父类中直接实现,但是我们也要写一个钩子方法,给其一个默认实现,从而来根据不同的情况来判断某些情况下是否需要处理图片的大小和质量。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
package com.glorze.template.method.hook; /** * 图片展示抽象类,使用钩子判断是否处理图像 * @ClassName BaseHookPicture * @author: glorze.com * @since: 2018年9月8日 下午4:19:56 */ public abstract class BaseHookPicture { /** * 获取图片源 * @Title: getPicture * @return void * @author: 高老四博客 * @since: 2018年9月8日 下午4:20:48 */ public abstract void getPicture(); public void handlePic() { System.out.println("压缩图片并改变图片的长宽以适配客户端展示"); } /** * 图片展示 * @Title: displayPic * @return void * @author: 高老四博客 * @since: 2018年9月8日 下午4:55:25 */ public abstract void displayPic(); public void handler() { getPicture(); if(isHandlePic()) { handlePic(); } displayPic(); } public Boolean isHandlePic() { return true; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
package com.glorze.template.method.hook; /** * 钩子方法重写 * @ClassName HookMethod * @author: glorze.com * @since: 2018年9月8日 下午4:59:10 */ public class HookMethod extends BaseHookPicture { @Override public void getPicture() { System.out.println("从pixabay.com获取一张图片"); } @Override public void displayPic() { System.out.println("向PC端展示处理或者未处理的图片"); } @Override public Boolean isHandlePic() { return false; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
package com.glorze.template.method.hook; /** * 客户端 * @ClassName Client * @author: glorze.com * @since: 2018年9月8日 下午4:59:49 */ public class Client { public static void main(String[] args) { BaseHookPicture bhp = new HookMethod(); bhp.handler(); } } |
在上面的代码中,我们引入一个钩子方法,返回Boolean类型,通过它来调控是否处理图片,如果子类不重写钩子方法,则会使用父类默认的实现。所以由于面向对象的多态性,子类可以覆盖父类,从而实现子类对父类的反向控制。
模板方法模式的优点:
- 在父类中形式化地定义一个算法,而由它的子类来实现细节的处理,在子类实现详细的处理算法时并不会改变算法中步骤的执行次序。
- 鼓励我们使用继承实现代码复用
- 通过继承反向控制父类行为,就像之前的钩子方法
- 通过抽象父类,不同的子类可以实现不同的行为,更换和新增子类方便,符合设计模式中的单一职责和开放-封闭原则。关于这两个原则可以参考一下老四的这两篇《浅析设计模式第三章之单一职责原则》、《浅析设计模式第四章之开放-封闭原则》文章。
模板方法模式的缺点:
主要缺点就是如果父类基本方法变得越来越多并且基本方法是多变类型,会导致子类的细节实现也越来越多,从而导致系统更加臃肿,此时也需要更进一步的抽象。
模板方法模式的一般使用场景:
- 复杂算法分割,复杂业务流程分隔
- 多个类都有共同的行为,可以提取抽象父类,在父类中直接声明并实现
- 需要反向控制父类行为,利用钩子方法。
关于模板方法的源码文末注册小站即可下载,除了以上示例代码,老四还附加一个模板方法模式的一个相对具体实现,利用Java反射通过配置文件配置对应的子类从而实现系统的动态调用,当系统需要更换子类,只需要修改配置文件即可实现对应的子类的调用,可以参考一下,代码相对更完善一些。
更博不易,如果觉得文章对你有帮助并且有能力的老铁烦请赞助盒烟钱,点我去赞助。或者扫描文章下面的微信/支付宝二维码打赏任意金额,老四这里抱拳了。赞助时请备注姓名或者昵称,因为您的署名会出现在赞赏列表页面,您的赞赏钱财也会被用于小站的服务器运维上面,再次抱拳。
资源下载
隐藏内容:******,购买后可见!
下载价格:0 G币
您需要先登录后,才能购买资源
欢迎访问高老四博客(glorze.com),本站技术文章代码均为老四亲自编写或者借鉴整合,其余资源多为网络收集,如涉及版权问题请与站长联系。如非特殊说明,本站所有资源解压密码均为:glorze.com。