1 动机
2 定义
在阎宏博士的《Java与模式》一书中开头是这样描述模板方法(Template Method)模式的:
模板方法模式是类的行为模式。准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现。这就是模板方法模式的用意。
- 模板方法模式是基于继承的代码复用基本思想,模板方法模式的结构和用法也是面向对象设计的核心之一。在模板方法模式中,可以将相同的代码放在父类中,而将不同的方法实现放在不同的子类中。
- 在模板方法模式中,我们需要准备一个抽象类,将部分逻辑以具体方法以及具体构造函数的形式实现,然后声明一些抽象方法来让子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些抽象方法,从而对剩余的逻辑有不同的实现,这就是模板方法模式的用意。模板方法模式体现了面向对象的诸多重要思想,是一种使用频率较高的模式。
3 构成
UML图
构成
- 抽象模板类(AbstractClass):
- 定义了抽象的原语方法(primitive method) ,以便让子类具体来实现这些原语方法;
- 具体实现一个模板方法(template method),在这个模板方法中定义了对应算法的骨架。该模板方法调用所有的原语方法;
- 具体模板类 (ConcreteClass): 实现父类中定义的抽象原语方法,以完成算法中与特定子类相关的步骤。
示范
抽象模板角色类
抽象模板角色类,abstractMethod()
、hookMethod()
等基本方法是顶级逻辑的组成步骤,这个顶级逻辑由 templateMethod()
方法代表。
public abstract class AbstractTemplate {
/**
* 模板方法
*/
public void templateMethod(){
//调用基本方法
abstractMethod();
hookMethod();
concreteMethod();
}
/**
* 基本方法的声明(由子类实现)
*/
protected abstract void abstractMethod();
/**
* 基本方法(空方法)
*/
protected void hookMethod(){}
/**
* 基本方法(已经实现)
*/
private final void concreteMethod(){
//业务相关的代码
}
}
具体模板角色类
具体模板角色类,实现了父类所声明的基本方法,abstractMethod()方法所代表的就是强制子类实现的剩余逻辑,而hookMethod()方法是可选择实现的逻辑,不是必须实现的。
public class ConcreteTemplate extends AbstractTemplate{
//基本方法的实现
@Override
public void abstractMethod() {
//业务相关的代码
}
//重写父类的方法
@Override
public void hookMethod() {
//业务相关的代码
}
}
4 示例
背景
假设我们作为一个建筑公司,需要设计一个方案来实现建造不同的房子,我们可能会建造木房子或玻璃房子。
我们可以定义一个抽象类HouseTemplate
表示建造任何类型房子,木房子建造类WoodenHouse
和玻璃房子建造类GlassHouse
均实现了HouseTemplate
抽象类。
建造房子的步骤包括:
- 修建地基(
buildFoundation()
) - 修建柱子(
buildPillars()
) - 修建墙面(
buildWalls()
) - 安装窗(
buildWindows()
)
Note:以上每个步骤都对应一个特定的方法。
显然,我们不能任意颠倒以上步骤的顺序(比如,在修建地基之前就安装窗)。因此,我们通过定义一个buildHouse()
模板方法(template method)来指定建造步骤的执行顺序。
Note:buildHouse()
方法不能被子类改写(因为,即使是建造不同类型的房子,建房的四大核心步骤的顺序也是相同的)
同时,无论建造什么房子(木房子或玻璃房子),我们打地基的方式可能都是一样的。所以,我们可以在抽象类HouseTemplate
中,用最常用的打地基方式来实现buildFoundation()
。如果在建造一个特殊类型的房子时,需要用一个特殊的方法来打地基。则可以在房子子类中重写(override)buildFoundation()
方法。
假设我们认为对于不同类型的房子,修建柱子和修建墙面是没有统一的方法的。我们将buildPillars()
和buildWalls()
定义为抽象方法(abstract),以让子类显式地去实现。
实现
HouseTemplate
房子建造抽象类
public abstract class HouseTemplate {
//template method defining the order of execution for performing several steps, final so subclasses can't override
public final void buildHouse(){
buildFoundation();
buildPillars();
buildWalls();
buildWindows();
System.out.println("House is built.");
}
public void buildWindows() {
System.out.println("Building Glass Windows");
}
//default implementation
public void buildFoundation() {
System.out.println("Building foundation with cement,iron rods and sand");
}
//methods to be implemented by subclasses
public abstract void buildWalls();
public abstract void buildPillars();
}
WoodenHouse
木房子建造类
public class WoodenHouse extends HouseTemplate {
@Override
public void buildWalls() {
System.out.println("Building Wooden Walls");
}
@Override
public void buildPillars() {
System.out.println("Building Pillars with Wood coating");
}
}
GlassHouse
玻璃房子建造类
public class GlassHouse extends HouseTemplate {
@Override
public void buildWalls() {
System.out.println("Building Glass Walls");
}
@Override
public void buildPillars() {
System.out.println("Building Pillars with glass coating");
}
}
调用
public class HousingClient {
public static void main(String[] args) {
HouseTemplate houseType = new WoodenHouse();
//using template method
houseType.buildHouse();
System.out.println("************");
houseType = new GlassHouse();
houseType.buildHouse();
}
}
问题引入
如果有一天,有一个客户希望他的房子是没有窗的,那怎么办呢?
我们可以从容地在HouseTemplate
抽象类中,添加一个hook 方法hasWindows()
,且默认返回true
。且简单修改一下模板方法:
public boolean hasWindows(){
return true;
}
//template method
public final void buildHouse(){
buildFoundation();
buildPillars();
buildWalls();
if (hasWindows()){
buildWindows();
}
System.out.println("House is built.");
}
当不需要窗时,只需要在子类中重写hasWindows()
方法,并将返回值修改为false
即可。
5 适用场景
- 模板方法中定义一个操作或行为的内部逻辑执行顺序,这个操作无论在什么样的执行环境中执行,其内部执行顺序均不会改变
- 抽象类中定义了一些操作的默认实现(显然,子类可以在需要的时候重写这些操作)
6 模式应用
模板方法模式在Servlet中的应用
使用过Servlet的人都清楚,除了要在web.xml做相应的配置外,还需继承一个叫HttpServlet的抽象类。
HttpService类提供了一个service()方法,这个方法调用七个do方法中的一个或几个,完成对客户端调用的响应。
这些do方法需要由HttpServlet的具体子类提供,这是典型的模板方法模式。
下面是service()方法的源代码:
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
// servlet doesn't support if-modified-since, no reason
// to go through further expensive logic
doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
if (ifModifiedSince < (lastModified / 1000 * 1000)) {
// If the servlet mod time is later, call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
//
// Note that this means NO servlet supports whatever
// method was requested, anywhere on this server.
//
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
当然,这个service()方法也可以被子类置换掉。
下面给出一个简单的Servlet例子:
从上面的类图可以看出,TestServlet类是HttpServlet类的子类,并且置换掉了父类的两个方法:doGet()和doPost()。
public class TestServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println("using the GET method");
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println("using the POST method");
}
}
分析
从上面的例子可以看出这是一个典型的模板方法模式。
-
HttpServlet担任抽象模板角色
- 模板方法:由service()方法担任。
- 基本方法:由doPost()、doGet()等方法担任。
-
TestServlet担任具体模板角色
TestServlet置换掉了父类HttpServlet中七个基本方法中的其中两个,分别是doGet()和doPost()。
Reference
- 《Java与模式》
- Journaldev - Template Method - https://www.journaldev.com/1763/template-method-design-pattern-in-java
- 模板方法模式(Template Method) - 最易懂的设计模式解析 - https://blog.csdn.net/carson_ho/article/details/54910518
- 《JAVA与模式》之模板方法模式 - https://www.cnblogs.com/java-my-life/archive/2012/05/14/2495235.html