模板方法模式
标签 : Java与设计模式
模板方法模式: 定义一个操作中的算法的骨架, 而将一些步骤延迟到子类中. 模板方法使得子类可以在不改变一个算法的结构的前提下重定义该算法的某些特定步骤.
(图片来源: 设计模式:可复用面向对象软件的基础)
- Tips
处理某个流程的骨架代码已经具备, 但其中某节点的具体实现暂不确定, 此时可采用模板方法, 将该节点的代码实现转移给子类完成. 即: 处理步骤在父类中定义好, 具体实现延迟到子类中定义.
模式实现
到ATM取款机办理业务, 都会经过插卡、输密码、处理业务、取卡 等几个过程, 而且这几个过程一定是顺序执行的, 且除了 处理业务 (如取款、改密、查账) 可能会有所不同之外, 其他的过程完全相同. 因此我们就可以参考模板方法模式把插卡、输密码、取卡 3个过程放到父类中实现, 并定义一个流程骨架, 然后将 处理业务的具体逻辑 放到子类中:
- AbstractClass 抽象模板:
- 定义抽象的原语操作,具体的子类将重定义它们以实现一个算法的各步骤.
- 实现一个模板方法,定义一个算法的骨架. 该模板方法不仅调用原语操作,也调用定义在AbstractClass或其他对象中的操作.
/**
* @author jifang
* @since 16/8/21 上午10:35.
*/
public abstract class AbstractATMBusiness {
public void run() {
System.out.println("-> 插卡");
System.out.println("-> 输入并校验密码");
if (checkPassword()) {
onBusiness();
}
System.out.println("-> 取卡");
}
// 具体业务处理延迟到子类实现
protected abstract void onBusiness();
private boolean checkPassword() {
// TODO Encode Password, Select DB & Comparison
return true;
}
}
AbstractATMBusiness
是一个模板方法, 它定义了ATM操作的一个主要步骤并确定他们的先后顺序, 但允许子类改变这些具体步骤以满足各自的需求.
- ConcreteClass
实现原语操作以完成算法中与特定子类相关的步骤; 每个AbstractClass都可有任意多个ConcreteClass, 而每个ConcreteClass都可以给出这些抽象方法的不同实现, 从而使得*逻辑的功能各不相同:
class CheckOutConcreteATMBusiness extends AbstractATMBusiness {
@Override
protected void onBusiness() {
System.out.println(" ... 取款");
}
}
class ChangePasswordConcreteATMBusiness extends AbstractATMBusiness {
@Override
protected void onBusiness() {
System.out.println(" ... 修改密码");
}
}
- Client
/**
* Created by jifang on 15/12/3.
*/
public class Client {
@Test
public void client() {
AbstractATMBusiness changePassword = new ChangePasswordConcreteATMBusiness();
changePassword.run();
AbstractATMBusiness checkOut = new CheckOutConcreteATMBusiness();
checkOut.run();
}
}
实例
Servlet
HttpServlet
定义了service()
方法固定下来HTTP请求的整体处理流程,使得开发Servlet只需继承HttpServlet
并实现doGet()
/doPost()
等方法完成业务逻辑处理, 并不需要关心具体的HTTP响应流程:
/**
* 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);
}
}
详见: Servlet - 基础.
统一定时调度
将这个示例放在此处可能有些不大合适, 但它也体现了一些模板方法的思想:
1. 实现
- ScheduleTaskMonitor
/**
* @author jifang
* @since 16/8/23 下午3:35.
*/
public class ScheduleTaskMonitor implements InitializingBean, DisposableBean {
private static final Logger LOGGER = LoggerFactory.getLogger(ScheduleTaskMonitor.class);
private static final int _10S = 10_000;
private List<ScheduleTask> tasks = new CopyOnWriteArrayList<>();
private static final Timer timer = new Timer("ScheduleTaskMonitor");
private void start() {
timer.schedule(new TimerTask() {
@Override
public void run() {
for (ScheduleTask task : tasks) {
task.scheduleTask();
}
}
}, 0, _10S);
}
public void register(ScheduleTask task) {
tasks.add(task);
}
@Override
public void afterPropertiesSet() throws Exception {
this.start();
LOGGER.info("Start Monitor {}", this.getClass());
}
@Override
public void destroy() throws Exception {
timer.cancel();
LOGGER.info("Stop Monitor {}", this.getClass());
}
}
- ScheduleTask
public interface ScheduleTask {
void scheduleTask();
}
2. 使用
只需在Spring的配置文件中引入该Bean:
<bean id="monitor" class="com.template.ScheduleTaskMonitor"/>
需要统一定时的类实现ScheduleTask
接口, 并将自己注册到monitor
中:
/**
* @author jifang
* @since 16/3/16 上午9:59.
*/
@Controller
public class LoginController implements ScheduleTask, InitializingBean {
private static final Logger LOGGER = LoggerFactory.getLogger(LoginController.class);
@Autowired
private ScheduleTaskMonitor monitor;
@Override
public void scheduleTask() {
LOGGER.error("O(∩_∩)O 日志记录~");
}
@Override
public void afterPropertiesSet() throws Exception {
monitor.register(this);
}
}
即可完成scheduleTask()
方法的定时调度.
小结
模板方法模式提供了一个很好的代码复用平台, 他通过把不变行为搬移到父类, 去除子类中重复代码来体现它的优势: 有时我们会遇到由一系列步骤构成的过程需要执行, 该过程从高层次上看是相同的, 但有某些细节的实现可能不同, 此时就可以考虑使用用模板方法了.
-
适用
- 一次性实现算法的不变部分, 并将可变的行为留给子类来实现;
- 各子类中公共的行为应该被提取出来并集中到一个公共父类中避免代码重复, 如: Servlet 的
service()
方法. - 控制子类扩展, 模板方法只在特定点调用hook操作, 这样就只允许在这些点进行扩展, 如: Junit测试框架.
-
相关模式
- Factory Method常被模板方法调用.
- Strategy: 模板方法使用继承来改变算法的一部分, Strategy使用委托来改变整个算法.
- 参考 & 扩展
- 设计模式:可复用面向对象软件的基础
- 大话设计模式
- 高淇将设计模式