一般生活中我们办理一件事需要一套指定的流水线。例如银行办事,需要先去营业厅取号、排队、办理业务、综合评分。这一套固有的流程,取号、排队、综合评分等都是固定的,不同的客户会办理不同的业务,只有这块是没个客户不同的。类似于这种有固定的流水线,在软件设计的过程中,也是时长被使用到的。
定义这条流水线的方法为模板方法,执行到每个环节在调取相应方法的函数。在设计模式中,此类的问题为模板方法模式,下面就通过案例来说一下模板方法的使用。
看下GOF《设计模式》一书中的定义:模板方法模式在一个方法中定义一个算法⻣架,并将某些步骤推迟到子类中实现。模板方法模式可以让子类在不改变算法整体结构的情况下,重新定义算法中的某些步骤。
1.模板模式的结构
抽象类模板方法 定义模板执行顺序,需要执行的函数
具体类 实现模板方法中的特定方法和钩子方法
抽象类中一般有三种类型方法:
1)、普通方法,定义固有的方法
2)、抽象方法,各子类对象需要自定义实现的方法,也就是定义中提到的,推迟到子类中实现的方法。
3)、钩子方法,一般是由子类决定是否重写,来决定模板方法中是否执行某块逻辑
//抽象类
public abstract class AbstractClass {
//模板方法 算法骨架
public void templateMethod() {
method1();
abstractMethod();
method2();
}
public void method1(){
System.out.println("模板方法1");
}
public void method2(){
System.out.println("模板方法2");
}
// 抽象方法 推迟到子类执行的方法
protected abstract void abstractMethod();
}
public class ConcreteClass extends AbstractClass{
@Override
protected void abstractMethod() {
System.out.println("具体模板方法1");
}
}
public class ConcreteClass2 extends AbstractClass{
@Override
protected void abstractMethod() {
System.out.println("具体模板方法2");
}
}
public class Client {
public static void main(String[] args) {
AbstractClass abstractClass = new ConcreteClass();
abstractClass.templateMethod();
System.out.println("----------------");
AbstractClass abstractClass2 = new ConcreteClass2();
abstractClass2.templateMethod();
}
}
执行结果:
模板方法1
具体模板方法1
模板方法2
----------------
模板方法1
具体模板方法2
模板方法2
执行结果中,模板方法中的普通方法method1,method2
,都是在抽象类中定义的,所有的子类对象执行模板方法时,都一样,自定义的部分在抽象方法中。这就是模板模式的基本骨架,很简单。
案例2,通过模拟网上购物的流程(选择商品、加入购物车、增加运费险、下单、付款),来体会一下模板模式的使用。包括钩子函数的使用。
//抽象流程
public abstract class AbstractFlow {
// 购物流程模板
public void shoppingTemplate() {
choose();
addShopStore();
//钩子方法
if (hook()) {
freightInsurance();
}
order();
pay();
}
// 商品选择
public void choose() {
System.out.println("商品选选择阶段");
}
// 加入购物车
public void addShopStore() {
System.out.println("加入购物车");
}
// 下单
public void order() {
System.out.println("下单...跳转付款页面");
}
// 运费险
public void freightInsurance() {
System.out.println("附加运费险");
}
//钩子函数
public boolean hook() {
return false;
}
// 付款
protected abstract void pay();
}
public class ShopShoeFlow extends AbstractFlow{
@Override
protected void pay() {
System.out.println("购买鞋子花费:500元");
}
}
-具体实现类2 买一件T-shirt (需要运费险)
public class ShopTshirtFlow extends AbstractFlow {
public boolean hook() {
// 衣服的尺寸因为每个品牌都不标准,这里重写钩子函数,方便增加运费险
return true;
}
@Override
protected void pay() {
System.out.println("购买 T-shirt 花费:300元");
}
}
public class Client {
public static void main(String[] args) {
// 购买鞋子
AbstractFlow shopShoeFlow = new ShopShoeFlow();
shopShoeFlow.shoppingTemplate();
System.out.println("-----------");
// 购买一件T-shirt
AbstractFlow shopTshirtFlow = new ShopTshirtFlow();
shopTshirtFlow.shoppingTemplate();
}
}
执行结果:
商品选选择阶段
加入购物车
下单...跳转付款页面
购买鞋子花费:500元
-----------
商品选选择阶段
加入购物车
附加运费险
下单...跳转付款页面
购买 T-shirt 花费:300元
我们买东西其他的流程都是一样,只有在最后付款的时候,因为不同的商品最终付款的价格也是不同的。所有把付款的方法推迟到购买具体商品的子类中实现;钩子方法的使用,钩子方法是控制整个模板算法骨架中,某一部分不是必须要在整个流程中的部分。这里可以通过子类重写钩子函数的方式,来控制注册分功能是否要在整个流程中执行。
对于鞋子来说一般我们鞋码都是固定的,不同品牌之间有细微的差别,基本不会买错。但是衣服来说的话,差别就很大,不同款式(运动、休闲)不同品牌之间的尺码都不一致。买错有更大的风险,所以对买衣服增加个运费险,通过重写钩子函数来实现。
优点:
缺点:
列举一例,BaseExecutor
提供了基本sql执行方法,实现了sql执行逻辑,骨架是定义好了,部分特定的实现是在子类的方法中实现的。如事务管理、缓存管理。
// 查询
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 调用查询方法
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
// 修改
@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);
}
//...略
// 抽象方法
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;
BaseExecutor
类实现Executor
接口中的query()、update()...
等等,都是提供一个执行流程,最终调用了本类中的抽象方法,具体的实现是放在子类中实现。
子类的实现有四个,分别不同实现了抽象类中的方法,子类的功能:
Servlet AIP
是Java Servlet
制定的标准, 可以使用 javax.servlet
和 javax.servlet.http
包创建. Java Servlet
是运行在Web服务器或应用服务器上的程序,它是作为来自Web浏览器或其他HTTP客户端的请求和HTTP服务器上的数据库或应用程序之间的中间层。
支持Http协议HttpServlet
,HttpServlet
继承于 GenericServlet
,实现了Servlet
接口,HttpServlet
提供了Http协议的请求。
//根据请求方式不同,调用不听方法
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
long lastModified;
if (method.equals("GET")) {
lastModified = this.getLastModified(req);
if (lastModified == -1L) {
this.doGet(req, resp);
} else {
long ifModifiedSince;
try {
ifModifiedSince = req.getDateHeader("If-Modified-Since");
} catch (IllegalArgumentException var9) {
ifModifiedSince = -1L;
}
if (ifModifiedSince < lastModified / 1000L * 1000L) {
this.maybeSetLastModified(resp, lastModified);
this.doGet(req, resp);
} else {
resp.setStatus(304);
}
}
} else if (method.equals("HEAD")) {
lastModified = this.getLastModified(req);
this.maybeSetLastModified(resp, lastModified);
this.doHead(req, resp);
} else if (method.equals("POST")) {
this.doPost(req, resp);
} else if (method.equals("PUT")) {
this.doPut(req, resp);
} else if (method.equals("DELETE")) {
this.doDelete(req, resp);
} else if (method.equals("OPTIONS")) {
this.doOptions(req, resp);
} else if (method.equals("TRACE")) {
this.doTrace(req, resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[]{
method};
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(501, errMsg);
}
}
// httpServlet doget
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String protocol = req.getProtocol();
String msg = lStrings.getString("http.method_get_not_supported");
if (protocol.endsWith("1.1")) {
resp.sendError(405, msg);
} else {
resp.sendError(400, msg);
}
}
//... doPost doHead doPut ..略
这里的模板方法就是Service()
,根据不同的请求,调用HttpServlet
中的指定方法,这里篇幅有限贴了 doGet()
方法,发现没有任何实现。会报40*
的错误,看下Httpservelet的子类。
这里具体的方法都由子类来实现,HttpServlet
不能被实例化是个抽象类,具体的方法请求都由子类来实现,HttpServlet
在这里充当模板模式的抽象类,不同子类的实现为具体的实现类,重新不同请求方法。