浅谈设计模式-模板方法模式
书接上回,本篇讲一下行为型模式-模板方法模式
模板方法模式
定义:定义一个算法的骨架,并允许子类为一个或多个步骤提供实现
作用:模板方法使得子类可以在不改变算法结构的情况下,重写定义算法的某些步骤
代码模板
/**
* 模板父类
*/
public abstract class AbstractTemplate {
/**
* 固定算法骨架
* 固定,不能变动,子类无法重写
*/
public final void templateMethod(){
//步骤1
this.operation1();
//步骤2
this.operation2();
//步骤3
this.operation3();
//步骤4
this.operation4();
}
private void operation1() {
System.out.println("步骤1:一种情况:逻辑固定,子类无法继承与重写,使用private修饰");
}
protected final void operation2() {
System.out.println("步骤2:一种情况:逻辑固定,允许子类继承,但不允许重写,使用protected + final修饰");
}
protected void commOperation() {
System.out.println("普通方法,不是具体的算法步骤,允许子类继承与重写,使用protected修饰");
}
//步骤3:一种情况:父类无法确定如何实现,由子类继承并重写,使用protected修饰
protected abstract void operation3();
protected void operation4(){
System.out.println("步骤4:钩子方法,父类提供默认实现,子类可选择性实现,使用protected修饰");
}
}
方法解析
标准的模板模式中,包含几种标准的方法操作
模板方法:templateMethod,定制算法的骨架
具体操作:实现模板算法的某个具体步骤,一般是一种固定实现,不需要怎么变化,所以使用final修饰。此时考虑是否需要子类访问,如果不需要使用private修饰,如果需要使用protected修饰。operation1 operation2 方法就是具体操作。
通用操作:模板类中定义的允许子类自由访问的方法,但不属于算法步骤,而是一些辅助方法。commOperation 方法就是通用的操作。
原语操作:算法步骤,模板方法必须调用的操作,而且父类中无法确定如何实现,需要子类具体实现。operation3 方法就是通用的操作。
钩子操作:算法步骤,但不是核心算法步骤,父类有默认实现,子类可以根据自己情况选择重写或者沿用,一般认为是算法的拓展点,非必须操作。operation4 方法就是通用的操作。
结语:标准的模板模式包含上面的诸多操作,但真实开发中,有模板方法,原语操作,钩子操作就算模板方法了。
案例分析
需求:设计一套工具类,实现统计算法耗时功能
逻辑接口
/**
* 逻辑任务,该doJob逻辑需要统计耗时
*/
@FunctionalInterface
public interface TaskJob {
void doJob();
}
模板类
/**
* 模板类
*/
public abstract class LogicStatisTime {
//模板方法,固定统计时间算法
public final void excute(TaskJob taskJob){
long begin = this.begin();
taskJob.doJob();
long end = this.end();
this.statis(begin, end);
this.endStatis();
}
//开始计时,算法步骤,固定
private Long begin(){
long begin = System.currentTimeMillis();
System.out.println("计时开始T1:" + begin);
return begin;
}
//结算计时,算法步骤,固定
private Long end(){
long end = System.currentTimeMillis();
System.out.println("计时结束T2:" + end);
return end;
}
//算法时间统计,算法步骤,由子类
protected abstract void statis(Long begin, Long end);
//算法步骤,父类默认实现,子类可以选择性拓展
protected void endStatis() {
System.out.println("欢迎关注:下岗码农大飞");
}
}
统计子类-简单统计
public class SimpleLogicStatisTime extends LogicStatisTime{
@Override
protected void statis(Long begin, Long end) {
System.out.println("算法耗时:" + Math.abs((begin - end)/1000 ) + "秒");
}
@Override
protected void endStatis() {
System.out.println("不要关注:下岗码农大飞");
}
}
统计子类-漂亮统计
public class PrettyLogicStatisTime extends LogicStatisTime{
@Override
protected void statis(Long begin, Long end) {
long total = Math.abs((begin - end) );
long hour = (total % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60);
long min = (total % (1000 * 60 * 60)) / (1000 * 60);
long sec = (total % (1000 * 60)) / 1000;
if(hour > 0){
System.out.println(hour + "时" + min + "分" + sec + "秒");
}else if (min > 0){
System.out.println(min + "分" + sec + "秒");
}else if(sec > 0){
System.out.println(sec + "秒");
}else{
System.out.println("耗时太少,无法统计");
}
}
}
测试
public class App {
//要统计耗时的代码
public static void doLoop(){
for (int i = 0; i < 10; i++) {
System.out.println("hello world....");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
new SimpleLogicStatisTime().excute(App::doLoop);
System.out.println("-------------------");
new PrettyLogicStatisTime().excute(App::doLoop);
}
}
解析
都在代码了啦,看看就可以。
适用场景
一次性实现一个算法的不变的部分,并将可变的行为留个子类来实现
各子类中的公共的行为被提取出来并集中到一个公共父类中,从而避免代码重复
优缺点
优点
提高复用性,
提高扩展性
缺点
类数目增加
增加了系统实现的复杂度
继承关系自身缺点,如果父类添加新的抽象方法,所有子类都要该一遍
开发案例
java web 中HttpServlet类
public abstract class HttpServlet extends GenericServlet {
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;
try {
ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
} catch (IllegalArgumentException iae) {
// Invalid date header - proceed as if none was set
ifModifiedSince = -1;
}
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方法就是模板方法,里面的doXxx方法都是需要子类重写的逻辑方法。
Mybatis中的BaseExecutor 基本执行器
BaseExecutor是模板类,里面很多模板方法
public abstract class BaseExecutor implements Executor {
//模板方法....
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
clearLocalCache();
return doUpdate(ms, parameter);
}
@Override
public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
return doQueryCursor(ms, parameter, rowBounds, boundSql);
}
//交给子类实现的算法步骤
protected abstract int doUpdate(MappedStatement ms, Object parameter)
throws SQLException;
protected abstract List<BatchResult> doFlushStatements(boolean isRollback)
throws SQLException;
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
throws SQLException;
protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
throws SQLException;
}
总结
定义一个算法的骨架,并允许子类为一个或多个步骤提供实现,但也会引起代码结构复杂,类结构膨胀,实际使用中,需要综合比较。
相关文章
- 腾讯云图片鉴黄集成到C# SQL Server 怎么在分页获取数据的同时获取到总记录数 sqlserver 操作数据表语句模板 .NET MVC后台发送post请求 百度api查询多个地址的经纬度的问题 try{}里有一个 return 语句,那么紧跟在这个 try 后的 finally {}里的 code 会 不会被执行,什么时候被执行,在 return 前还是后? js获取某个日期
- Idea:通过Live Template自定义模板(类注释、方法注释)
- 淘东电商项目(61) -聚合支付(基于模板方法设计模式管理支付回调)
- 微信小程序 - 手持弹幕 | 全屏炫酷滚屏神器源代码模板,超详细代码和注释复制粘贴即可使用(全屏文字滚动,支持调节滚动速度、字体大小、字体颜色)文字内容横屏滚动,手机变身 LED 屏
- 创建Vue实例对象基础语法模板
- 设计模式之---模板方法template method的使用
- 「Django 3.x 框架」前端模板获取 for 循环小结
- Django 4.x Templates 渲染页面模板示例和使用配置方法
- QCache 缓存(模板类,类似于map,逻辑意义上的缓存,方便管理,和CPU缓存无关。自动获得被插入对象的所有权,超过一定数量就会抛弃某些值)
- C++中的类模板详细讲述
- 模板方法模式
- [js高手之路]Node.js模板引擎教程-jade速学与实战1-基本用法
- 浅析setup如何通过ref获取子组件实例中的DOM结构/数据/方法及获取子组件实例数据都是空的处理(defineExpose API 的使用)、Vue3模板引用refs、在组合式API中使用template refs、for循环中如何获取及重置refs、如何监听模板引用
- 国庆假期带你玩转设计模式之模板方法模式
- 设计模式(二)模板方法模式
- 封装算法: 模板方法(Template Method)模式
- 【Unity3D】基于模板测试和顶点膨胀的描边方法
- 漫说模板方法模式---学生时代的烦恼
- Django之模板HTML转义(十五)
- nodejs jade 模板 引擎的使用方法
- Eclipse设置类和方法的注释模板
- Meteor模板