模板方法模式
标签 : Java与设计模式
模板方法模式: 定义一个操作中的算法的骨架, 而将一些步骤延迟到子类中. 模板方法使得子类能够在不改变一个算法的结构的前提下重定义该算法的某些特定步骤.
(图片来源: 设计模式:可复用面向对象软件的基础)
- Tips
处理某个流程的骨架代码已经具备, 但当中某节点的详细实现暂不确定, 此时可採用模板方法, 将该节点的代码实现转移给子类完毕. 即: 处理步骤在父类中定义好, 详细实现延迟到子类中定义.
模式实现
到ATM取款机办理业务, 都会经过插卡、输password、处理业务、取卡 等几个过程, 并且这几个过程一定是顺序运行的, 且除了 处理业务 (如取款、改密、查账) 可能会有所不同之外, 其它的过程全然同样. 因此我们就能够參考模板方法模式把插卡、输password、取卡 3个过程放到父类中实现, 并定义一个流程骨架, 然后将 处理业务的详细逻辑 放到子类中:
- AbstractClass 抽象模板:
- 定义抽象的原语操作,详细的子类将重定义它们以实现一个算法的各步骤.
- 实现一个模板方法,定义一个算法的骨架. 该模板方法不仅调用原语操作,也调用定义在AbstractClass或其它对象中的操作.
/**
* @author jifang
* @since 16/8/21 上午10:35.
*/
public abstract class AbstractATMBusiness {
public void run() {
System.out.println("-> 插卡");
System.out.println("-> 输入并校验password");
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(" ... 改动password");
}
}
- 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使用托付来改变整个算法.
- 參考 & 扩展
- 设计模式:可复用面向对象软件的基础
- 大话设计模式
- 高淇将设计模式