概述
模板方法模式(TemplateMethod Pattern),父类中定义处理流程的框架,在子类中实现具体处理。模块方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
模式结构
适用环境
有多个子类共有的方法,且逻辑相同
重要的、复杂的方法,可以考虑作为模板方法
典型案例
AQS
在并发编程中,ReentrantLock
锁大家一定不陌生,看过源码的小伙伴一定知道,ReentrantLock
内部基于 AQS
实现。这里 acquire()
相当于模板方法,tryAcquire(arg)
相当于基本方法。
在 模板方法
中,模板方法一般用 final
修饰,避免子类对整体流程进行修改。
1 2 3 4 5 6 7 8 9 10 11 12 public final void acquire (int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } protected boolean tryAcquire (int arg) { throw new UnsupportedOperationException(); }
接下来,我们看下具体的子类实现
ReentrantLock非公平锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 abstract static class Sync extends AbstractQueuedSynchronizer {}static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L ; final void lock () { if (compareAndSetState(0 , 1 )) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1 ); } protected final boolean tryAcquire (int acquires) { return nonfairTryAcquire(acquires); } }
ThreadPoolExecutor线程池Worker类
1 2 3 4 5 6 7 8 9 10 11 12 13 private final class Worker extends AbstractQueuedSynchronizer implements Runnable { protected boolean tryAcquire (int unused) { if (compareAndSetState(0 , 1 )) { setExclusiveOwnerThread(Thread.currentThread()); return true ; } return false ; } }
登录流程
现在的应用, 很多是支持 验证码
、邮箱
、 用户名
等多种方式登录的。 在后台,除了校验方式不同外,其他流程基本相同。这里我们可以在父类实现主要流程,将不同的逻辑放在子类实现,同时还要保证流程的一致性,这里可以用模板方法来实现。
首先来个超类,定义登录流程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public abstract class LoginService { public final void login (Map<String, String> params) { verifyData(); doSomething(); packAndRetureData(); } private void packAndRetureData () { System.out.println("all return login data" ); } private void doSomething () { System.out.println("all do some thing" ); } protected abstract void verifyData () ; }
具体子类,email登录
1 2 3 4 5 6 public class EmailLoginService extends LoginService { @Override protected void verifyData () { System.out.println("verify email data" ); } }
短信登录
1 2 3 4 5 6 public class SmsLoginService extends LoginService { @Override protected void verifyData () { System.out.println("verify sms data" ); } }
登录业务模拟
1 2 3 4 5 6 7 8 9 10 11 public class LoginTest { public static void main (String[] args) { EmailLoginService loginService = new EmailLoginService(); loginService.login(null ); } }
应用支付
对接过第三方支付的小伙伴都知道,在接入第三方支付时,一般支付结果都会通过异步回调的形式,通知商户服务器。而我们得到这些数据时一般都是同一流程的处理方式:验签 — 更新订单状态 — 给第三方支付服务器响应。 下面以支付宝和微信为例:支付宝和微信的验签方式和响应结果方式都是不一样的, 而更新订单状态都是商户这边处理所以业务逻辑是一样的。
抽象模板类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 public abstract class AbstractPayNotifyTemplate { public void onNotify (Map<String, String> params) { final boolean sign = verifySign(params); if (sign) { setResponse(PayStatus.ERROR); return ; } final String orderSn = params.get("out_trade_no" ); updateOrderPayStatusSuccess(orderSn); setResponse(PayStatus.SUCCESS); } protected abstract boolean verifySign (Map<String, String> params) ; private void updateOrderPayStatusSuccess (String orderSn) { } protected abstract void setResponse (PayStatus status) ; }
具体模板类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 public class AliPayNotifyTemplate extends AbstractPayNotifyTemplate { @Override protected boolean verifySign (Map<String, String> params) { return true ; } @Override protected void setResponse (PayStatus status) { String res = Objects.equals(PayStatus.SUCCESS, status) ? "success" : "error" ; } } public class WxPayNotifyTemplate extends AbstractPayNotifyTemplate { @Override protected boolean verifySign (Map<String, String> params) { return true ; } @Override protected void setResponse (PayStatus status) { String returnCode = "FAIL" ; String returnMsg = "" ; if (Objects.equals(PayStatus.SUCCESS, status)) { returnCode = "SUCCESS" ; returnMsg = "OK" ; } String res = String.format("<xml><return_code><![CDATA[%s]]></return_code><return_msg><![CDATA[%s]]></return_msg></xml>" , returnCode, returnMsg); } }
总结
从作用上来看,模板方法模式与策略模式都是对一个基类的方法有多种不同的实现。
而策略模式的具体实现中,采用的是组合 + 接口的策略。而模板方法模式就完全基于 Java 语言的多态特性实现的继承。从设计模式的思想来说,组合是优于继承的。
更大的区别在于,策略模式是从一个功能整体上出发的,每种策略对该功能都有不同的实现。而模板方法模式是对一个算法或流程细分为多个步骤,每个步骤都可以被子类重写,但是整个流程是不能被重新定义的。从代码实现上来讲,当模板方法中的需要重写的步骤占了绝大部分时,子类只有几个方法不用重写,在这种情况下不如使用策略模式。而当模板方法中需要重写的步骤不多时,使用策略模式会造成不同的实现类中有很多重复的代码。