近日, ofo 小黄车宣布入驻法国巴黎, 正式进入全球第 20 个国家, 共享单车已然改变了我们的出行方式. 就拿我自己来说, 每当下班出地铁的第一件事, 以光速锁定一辆共享单车, 百米冲刺的速度抢在别人之前占领它.
而大家都是重复着同样的动作, 拿出手机开锁, 骑车, 上锁, 结算, 哇~ 这是何等壮观的场景, 甚至还有的不用开锁直接把车骑走的, 锁坏了嘛.
为什么要用模板方法模式
现在共享单车以开锁的方式来分, 一般有扫码开锁和密码开锁两种, 来看共享单车使用流程的实现.
正常的思维逻辑是, 抽象一个父类, 子类继承父类并实现父类方法. OK, 看抽象类代码:
- public abstract class AbstractClass {
- // 开锁
- public abstract void unlock();
- // 骑行
- public abstract void ride();
- // 上锁
- public abstract void lock();
- // 结算
- public abstract void pay();
- // 用户使用
- public abstract void use();
- }
抽象类定义了我们使用共享单车的几个基本流程, 现在有两种不同开锁方式单车的使用, 都继承抽象类, 代码如下:
- //
- public class ScanBicycle extends AbstractClass {
- @Override
- public void unlock() {
- System.out.println("扫码开锁");
- }
- @Override
- public void ride() {
- System.out.println("骑起来很拉风");
- }
- @Override
- public void lock() {
- System.out.println("上锁");
- }
- @Override
- public void pay() {
- System.out.println("结算");
- }
- @Override
- public void use() {
- unlock();
- ride();
- lock();
- pay();
- }
- }
以上是通过扫码方式开锁骑行, 再来看密码开锁骑行, 代码如下:
- public class CodeBicycle extends AbstractClass {
- @Override
- public void unlock() {
- System.out.println("密码开锁");
- }
- @Override
- public void ride() {
- System.out.println("骑起来很拉风");
- }
- @Override
- public void lock() {
- System.out.println("上锁");
- }
- @Override
- public void pay() {
- System.out.println("结算");
- }
- @Override
- public void use() {
- unlock();
- ride();
- lock();
- pay();
- }
- }
好了, 两种方式都定义好了, 看客户端的调用:
- public class Client {
- public static void main(String[] args) {
- ScanBicycle scanBicycle = new ScanBicycle();
- scanBicycle.use();
- System.out.println("========================");
- CodeBicycle codeBicycle = new CodeBicycle();
- codeBicycle.use();
- }
- }
结果如下:
扫码开锁
骑起来很拉风
上锁
结算
========================
扫码开锁
骑起来很拉风
上锁
结算
相信都已经看出代码的问题, use 方法的实现是一样的, 也就是代码重复了, 这是病必须得治, 药方就是模板方式模式.
模板方法模式
定义
定义抽象类并且声明一些抽象基本方法供子类实现不同逻辑, 同时在抽象类中定义具体方法把抽象基本方法封装起来, 这就是模板方法模式.
UML
模板方法模式
模板方法模式涉及到的角色有两个角色:
- 抽象模板角色: 定义一组基本方法供子类实现, 定义并实现组合了基本方法的模板方法.
- 具体模板角色: 实现抽象模板角色定义的基本方法
模板方法模式还涉及到以下方法的概念:
基本方法
抽象方法: 由抽象模板角色声明, abstract 修饰, 具体模板角色实现.
钩子方法: 由抽象模板角色声明并实现, 具体模板角色可实现加以扩展.
具体方法: 由抽象模板角色声明并实现, 而子类并不实现.
模板方法
抽象模板角色声明并实现, 负责对基本方法的调度, 一般以 final 修饰, 不允许具体模板角色重写. 模板方法一般也是一个具体方法.
模式实战
利用模板方式模式对上面的代码进行重构, 来看抽象模板角色, 代码如下:
- public abstract class AbstractClass {
- protected boolean isNeedUnlock = true; // 默认需要开锁
- /**
- * 基本方法, 子类需要实现
- */
- protected abstract void unlock();
- /**
- * 基本方法, 子类需要实现
- */
- protected abstract void ride();
- /**
- * 钩子方法, 子类可实现
- *
- * @param isNeedUnlock
- */
- protected void isNeedUnlock(boolean isNeedUnlock) {
- this.isNeedUnlock = isNeedUnlock;
- }
- /**
- * 模板方法, 负责调度基本方法, 子类不可实现
- */
- public final void use() {
- if (isNeedUnlock) {
- unlock();
- } else {
- System.out.println("======== 锁坏了, 不用解锁 ========");
- }
- ride();
- }
- }
抽象模板角色定义了 unlock 和 ride 两个使用单车的基本方法, 还有一个钩子方法, 用来控制模板方法逻辑顺序, 核心是 use 模板方法, 用 final 修饰, 该方法完成对基本方法调度. 注意, 模板方法中对基本方法的调度是有顺序有规则的. 还有一点, 基本方法都是 protected 修饰的, 因为基本方法都是在以 public 修饰的模板方法中调用, 并且可以由子类实现, 并不需要暴露给其他类调用.
现在来看两个具体模板角色的实现:
- // 扫码开锁的单车
- public class ScanBicycle extends AbstractClass {
- @Override
- protected void unlock() {
- System.out.println("========" + "扫码开锁" + "========");
- }
- @Override
- protected void ride() {
- System.out.println(getClass().getSimpleName() + "骑起来很拉风");
- }
- protected void isNeedUnlock(boolean isNeedUnlock) {
- this.isNeedUnlock = isNeedUnlock;
- }
- }
- // 密码开锁的单车
- public class CodeBicycle extends AbstractClass {
- @Override
- protected void unlock() {
- System.out.println("========" + "密码开锁" + "========");
- }
- @Override
- protected void ride() {
- System.out.println(getClass().getSimpleName() + "骑起来很拉风");
- }
- protected void isNeedUnlock(boolean isNeedUnlock) {
- this.isNeedUnlock = isNeedUnlock;
- }
- }
可以看到, 相比之前的实现, 现在两个具体类都不需要实现 use 方法, 只负责实现基本方法的逻辑, 职责上变得更加清晰了. 来看用户如何使用:
- public class Client {
- public static void main(String[] args) {
- ScanBicycle scanBicycle = new ScanBicycle();
- scanBicycle.use();
- CodeBicycle codeBicycle = new CodeBicycle();
- codeBicycle.use();
- }
- }
运行结果如下:
======== 扫码开锁 ========
ScanBicycle 骑起来很拉风
======== 密码开锁 ========
CodeBicycle 骑起来很拉风
当我以百米冲刺的速度跑到共享单车面前时, 发现这辆车的锁是坏掉的, 也就是不用开锁, 免费的, 骑回家收藏也没问题. 在代码中只要调用钩子方法 isNeedUnlock 就好, 实现如下:
- public class Client {
- public static void main(String[] args) {
- ScanBicycle scanBicycle = new ScanBicycle();
- scanBicycle.isNeedUnlock(false);
- scanBicycle.use();
- CodeBicycle codeBicycle = new CodeBicycle();
- codeBicycle.isNeedUnlock(true);
- codeBicycle.use();
- }
- }
运行结果如下:
======== 锁坏了, 不用解锁 ========
ScanBicycle 骑起来很拉风
======== 密码开锁 ========
CodeBicycle 骑起来很拉风
上面提到模板方法对基本方法的调度是有顺序的, 也就是说模板方法中的逻辑是不可变的, 子类只实现可以被实现的基本方法, 但不会改变模板方法中的顶级逻辑. 而钩子方法的使用只是对模板方法中逻辑的控制, 影响的是模板方法的结果, 并不会改变原有逻辑.
模板方法模式的优缺点
优点
1) 良好的封装性. 把公有的不变的方法封装在父类, 而子类负责实现具体逻辑.
2) 良好的扩展性: 增加功能由子类实现基本方法扩展, 符合单一职责原则和开闭原则.
3) 复用代码.
缺点
1) 由于是通过继承实现代码复用来改变算法, 灵活度会降低.
2) 子类的执行影响父类的结果, 增加代码阅读难度.
总结
模板方法模式看上去简单, 但是整个模式涉及到的都是面向对象设计的核心, 比如继承封装, 基于继承的代码复用, 方法的实现等等. 当中还有涉及到一些关键词的使用, 也是我们 Java 编程中需要掌握的基础. 总体来说, 模板方法模式是很好的学习对象. 下一篇是中介者模式, 您的点赞和关注是我的动力, 再会!
设计模式 Java 源码 GitHub 下载: https://github.com/jetLee92/DesignPattern
来源: https://juejin.im/entry/5adcb82ef265da0b8262613f