Java设计模式-模版方法模式(Template Method Pattern)
目录
- 什么是模版方法模式
- 模版方法模式的实现
- JavaSE中模版方法模式的使用
- Struts2模版方法模式的应用
抽象父类约束子类,子类通过钩子方法去影响父类执行的逻辑、顺序
一、什么是模版方法模式
《JAVA与模式》一书中开头是这样描述模板方法(Template Method)模式的:
模板方法模式是类的行为模式。准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。这就是模板方法模式的用意。
《设计模式之禅》书中的定义:
定义一个操作中的算法的框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
两个定义中都出现了父类和子类,所以模版方法是基于继承的设计模式
父类:抽象模版:
- 定义一个或者多个抽象方法
- 定义一个或者多个模版方法,非抽象方法,是一个具体的方法,给出了一个抽象方法的调用顺序和逻辑,但由于在抽象类中,抽象方法只有名称,只可以表达出想要干什么,但具体如何干,就要交给子类实现。
子类:具体模版:
- 实现父类的抽象方法
- 子类就是父类抽象方法的实现,但不同的子类实现方式不同
模版方法的概念是:模版+方法;模版由父类定义,方法由子类实现。从而达到了父类约束子类的目的。模版方法模式的方法有两种:
- 模版方法:定义在抽象类中;可以有多个模版方法(**为了防止恶意的操作,一般模板方法都加上final关键字,不允许被覆写。**并且使用protected修饰,尽量不暴露属性和方法,遵循迪米特法则)
- 基本方法:抽象方法;具体方法;钩子方法
模版方法通过抽象方法强迫子类实现剩余逻辑,是父类约束子类,但子类也可以通过钩子方法去影响父类执行的逻辑、顺序等;
该设计模式有个非常大特点就是核心的模版在父类,如果模版方法修改势必会影响所有的子类,所以这个模版方法一定是非常固定,或者子类完全“遵从”父类的模版方法而不会产生自己的“个性”
二、模版方法模式的实现
实现汽车的驾驶功能(启动、停车、转向等),BMW有自动驾驶功能,但Benz不具备,BMW在驾驶时会自动启用自动驾驶
接下来看代码
package org.templatemethod;
abstract class Car{
protected abstract void start();
protected abstract void turnLeft();
protected abstract void turnRight();
protected abstract void stop();
protected abstract void autoDrive();
final protected void drive(){
start();
turnLeft();
turnRight();
stop();
if (isAI()){
autoDrive();
}
}
protected abstract boolean isAI();
}
class BMW extends Car{
private String name = "BMW";
protected void start() {
System.out.println(name + ":启动");
}
protected void turnLeft() {
System.out.println(name + ":左转");
}
protected void turnRight() {
System.out.println(name + ":右转");
}
protected void stop() {
System.out.println(name + ":停车");
}
protected void autoDrive() {
System.out.println(name + ":自动驾驶");
}
protected boolean isAI(){
return true;
}
}
class Benz extends Car{
private String name = "Benz";
protected void start() {
System.out.println(name + ":启动");
}
protected void turnLeft() {
System.out.println(name + ":左转");
}
protected void turnRight() {
System.out.println(name + ":右转");
}
protected void stop() {
System.out.println(name + ":停车");
}
protected void autoDrive() {
System.out.println(name + ":自动驾驶");
}
protected boolean isAI() {
return false;
}
}
public class Client {
public static void main(String[] args) {
BMW bmw = new BMW();
bmw.drive();
Benz benz = new Benz();
benz.drive();
}
}
// 运行结果
BMW:启动
BMW:左转
BMW:右转
BMW:停车
BMW:自动驾驶
Benz:启动
Benz:左转
Benz:右转
Benz:停车
在整个实现中,Car接口的drive方法定义了一个汽车应该如何驾驶,这就是模版,所有的汽车都是这样行驶的(和现实生活有所差别,理解其中意思即可),但BMW车型做了一点优化,加上了自动驾驶功能。接口Car是允许子类有自动驾驶功能的,但你如何实现并不关心,这就是父类约束子类。子类通过isAI(钩子方法)返回true,表明自己支持自动驾驶,这就是子类影响父类执行的逻辑和顺序。
其中start、stop、turnLeft、turnRight方法是强制子类实现的,而钩子方法是选择性实现,因为部分车辆没有自动驾驶功能。
子类的start、stop、turnLeft、turnRight方法是按照自己的业务实现,是可变的;但子类却不能改变模版方法。
模版方法的优点有:
- 封装不变部分,扩展可变部分
- 提取公共部分代码,便于维护
- 行为由父类控制,子类实现
三、JavaSE中模版方法模式的使用
- java.util.AbstractList#indexOf,lastIndexOf等
- java.util.List#sort
- java.io.InputStream#read
java.util.AbstractList#indexOf
该方法会最终调用get抽象接口,但该接口需要子类实现,所以indexOf是一个模版方法
java.util.List#sort
java.io.InputStream#read
read方法是抽象方法
四、Struts2模版方法模式的应用
tomcat-servlet-api.jar中的javax.servlet.http.HttpServlet抽象类
该抽象类定义了service()模版方法,但该模版方法并未使用final修饰,也就是说子类可以重写模版方法,如下图
最常用的doGet、doPost是经常用于HTTP请求,当我们重写doGet、doPost后Service便会调用重写之后的方法,来实现个性化业务逻辑。
xwork.jar中的com.opensymphony.xwork2.inject.ContainerImpl#callInContext方法
该方法很简单,调用callable参数的call方法,call方法的参数是:将当前的ContainerImple对象封装到InternalContext中,传入call方法中。
说不上是一个标准的模版方法,但它定义了所有容器操作的基础,将容器操作接口进行规范化定义,Container容器的inject、getInstance都使用了InternalContext方法,区别就在于传入的ContextualCallable匿名内部类不同,匿名内部类实现了具体的操作,这就有点像模版方法的样子了。看一下各个inject方法
整个ContainerImple是依赖注入的实现,可以阅读《Mark链接-Struts2-依赖注入的实现原理》