设计模式之模板方法模式
2016-02-11 15:07
344 查看
模板方法模式:由子类决定如何实现父类算法中的哪一步。
例子:咖啡机自动冲咖啡,但是我们可能做卡布奇诺(Cappuccino),也可能做焦糖玛奇朵(CaramelMacchiato),但是总体来说步骤一致,只是最后加入的其他材料不太一样。
冲咖啡的步骤:获得咖啡豆(getBeans()),放入咖啡机(putIntoCoffeemaker()),磨碎咖啡豆(grindBeans()),冲咖啡(makeCoffee())。
假设有这么一个抽象类,AutoCoffee,它的代码如下:
客户端不用关心自动冲咖啡内部的步骤如何实现,只需要调用autoMake()方法就够了,既然这样,我们的步骤方法,都设为protected。
然后,我们需要一个设定,让咖啡机自动冲卡布奇诺,只需要在最后加入牛奶和奶泡就好了:
Cappuccino.java
CaramelMacchiato.java
Clinet.java
输出结果:
获得咖啡豆
放入咖啡机
磨碎咖啡豆
冲咖啡
加入牛奶和奶泡
---------------------
获得咖啡豆
放入咖啡机
磨碎咖啡豆
冲咖啡
加入牛奶和焦糖
以上就是模板方法。是的,就是这么简单,或许你有疑问:如果子类需要更改冲咖啡的过程,或者需要稍微不同怎么办?
针对第一种情况,如果真的很不一样,需要大改,几近完全不同,那么,你不应该继承AutoCoffee这个抽象类,而是写一个类。但是如果只是稍微不同,那么我们就引用钩子(hook)的概念。
假设,我们的磨碎咖啡豆这一步骤,有手摇和电动两种选择,无论是卡布奇诺还是焦糖玛奇朵,我们都采用手动,但是实现方式不一样,我们可以这样编写抽象类AutoCoffee
新增加一个属性:flag和setter方法,以及判断flag的isMotorDriven()方法,grindBeans()变为两个方法,一个是grindBeansByMotorDriven()电动磨碎咖啡豆,一个是grindBeansByHand()手动磨碎咖啡豆。autoMake()修改为判断isMotorDriven(),根据条件不同,选择是手动还是电动。flag为true,默认为电动。
子类实现:
卡布奇诺重写isMotorDriven()方法,返回false(而非flag属性),这样就只能是手动,客户端无法自己选择(即使调用setFlag()也不能改变),而焦糖玛奇朵是可以通过setFlag()来设置是手动还是电动。
结果如下:
获得咖啡豆
放入咖啡机
手动磨碎咖啡豆
冲咖啡
加入牛奶和奶泡
------------------
获得咖啡豆
放入咖啡机
手动磨碎咖啡豆
冲咖啡
加入牛奶和焦糖
模板方法还体现了一个重要的设计原则:好莱坞原则。但是很多人搞不清楚好莱坞原则和依赖倒置原则,改天另开博客,详述好莱坞原则、依赖倒置原则、控制反转和依赖注入的区别。
例子:咖啡机自动冲咖啡,但是我们可能做卡布奇诺(Cappuccino),也可能做焦糖玛奇朵(CaramelMacchiato),但是总体来说步骤一致,只是最后加入的其他材料不太一样。
冲咖啡的步骤:获得咖啡豆(getBeans()),放入咖啡机(putIntoCoffeemaker()),磨碎咖啡豆(grindBeans()),冲咖啡(makeCoffee())。
假设有这么一个抽象类,AutoCoffee,它的代码如下:
public abstract class AutoCoffee { //获得咖啡豆 protected void getBeans(){ System.out.println("获得咖啡豆"); } //放入咖啡机 protected void putIntoCoffeemaker(){ System.out.println("放入咖啡机"); } //磨碎咖啡豆 protected void grindBeans(){ System.out.println("磨碎咖啡豆"); } //冲咖啡 protected void makeCoffee(){ System.out.println("冲咖啡"); } //自动冲咖啡 final public void autoMake(){ this.getBeans(); this.putIntoCoffeemaker(); this.grindBeans(); this.makeCoffee(); } }
客户端不用关心自动冲咖啡内部的步骤如何实现,只需要调用autoMake()方法就够了,既然这样,我们的步骤方法,都设为protected。
然后,我们需要一个设定,让咖啡机自动冲卡布奇诺,只需要在最后加入牛奶和奶泡就好了:
Cappuccino.java
public class Cappuccino extends AutoCoffee { @Override public void makeCoffee() { super.makeCoffee(); System.out.println("加入牛奶和奶泡"); } }再来一个设定,自动冲焦糖玛奇朵,只需要在最后加入牛奶和焦糖:
CaramelMacchiato.java
public class CaramelMacchiato extends AutoCoffee { @Override public void makeCoffee() { super.makeCoffee(); System.out.println("加入牛奶和焦糖"); } }OK,这样,来一个客户端:
Clinet.java
public class Clinet { public static void main(String[] args) { AutoCoffee cappuccino = new Cappuccino(); AutoCoffee caramelMacchiato = new CaramelMacchiato(); cappuccino.autoMake(); caramelMacchiato.autoMake(); } }
输出结果:
获得咖啡豆
放入咖啡机
磨碎咖啡豆
冲咖啡
加入牛奶和奶泡
---------------------
获得咖啡豆
放入咖啡机
磨碎咖啡豆
冲咖啡
加入牛奶和焦糖
以上就是模板方法。是的,就是这么简单,或许你有疑问:如果子类需要更改冲咖啡的过程,或者需要稍微不同怎么办?
针对第一种情况,如果真的很不一样,需要大改,几近完全不同,那么,你不应该继承AutoCoffee这个抽象类,而是写一个类。但是如果只是稍微不同,那么我们就引用钩子(hook)的概念。
假设,我们的磨碎咖啡豆这一步骤,有手摇和电动两种选择,无论是卡布奇诺还是焦糖玛奇朵,我们都采用手动,但是实现方式不一样,我们可以这样编写抽象类AutoCoffee
public abstract class AutoCoffee { private boolean flag = true; //获得咖啡豆 protected void getBeans(){ System.out.println("获得咖啡豆"); } //放入咖啡机 protected void putIntoCoffeemaker(){ System.out.println("放入咖啡机"); } //钩子方法 protected boolean isMotorDriven(){ return flag; } public void setFlag(boolean flag) { this.flag = flag; } //磨碎咖啡豆 protected void grindBeansByMotorDriven(){ System.out.println("电动磨碎咖啡豆"); } protected void grindBeansByHand(){ System.out.println("手动磨碎咖啡豆"); } //冲咖啡 protected void makeCoffee(){ System.out.println("冲咖啡"); } //自动冲咖啡 final public void autoMake(){ this.getBeans(); this.putIntoCoffeemaker(); if(isMotorDriven()){ this.grindBeansByMotorDriven(); }else{ this.grindBeansByHand(); } this.makeCoffee(); } }
新增加一个属性:flag和setter方法,以及判断flag的isMotorDriven()方法,grindBeans()变为两个方法,一个是grindBeansByMotorDriven()电动磨碎咖啡豆,一个是grindBeansByHand()手动磨碎咖啡豆。autoMake()修改为判断isMotorDriven(),根据条件不同,选择是手动还是电动。flag为true,默认为电动。
子类实现:
public class Cappuccino extends AutoCoffee { //设为手动 @Override protected boolean isMotorDriven() { return false; } @Override public void makeCoffee() { super.makeCoffee(); System.out.println("加入牛奶和奶泡"); } }
public class CaramelMacchiato extends AutoCoffee { @Override public void makeCoffee() { super.makeCoffee(); System.out.println("加入牛奶和焦糖"); } }Clinet.java
public class Clinet { public static void main(String[] args) { AutoCoffee cappuccino = new Cappuccino(); AutoCoffee caramelMacchiato = new CaramelMacchiato(); cappuccino.autoMake(); System.out.println("------------------"); caramelMacchiato.setFlag(false); caramelMacchiato.autoMake(); } }
卡布奇诺重写isMotorDriven()方法,返回false(而非flag属性),这样就只能是手动,客户端无法自己选择(即使调用setFlag()也不能改变),而焦糖玛奇朵是可以通过setFlag()来设置是手动还是电动。
结果如下:
获得咖啡豆
放入咖啡机
手动磨碎咖啡豆
冲咖啡
加入牛奶和奶泡
------------------
获得咖啡豆
放入咖啡机
手动磨碎咖啡豆
冲咖啡
加入牛奶和焦糖
模板方法还体现了一个重要的设计原则:好莱坞原则。但是很多人搞不清楚好莱坞原则和依赖倒置原则,改天另开博客,详述好莱坞原则、依赖倒置原则、控制反转和依赖注入的区别。
相关文章推荐
- HTTP与HTTPS握手的那些事
- test1
- Oracle 11g 关闭内存自动管理
- React JSX语法说明
- Educational Codeforces Round 7 C. Not Equal on a Segment(思维)
- js 中特殊形势的函数-匿名函数的应用
- Android应用安全开发之防范无意识的数据泄露
- bzoj2654 喵星球上的点名 后缀数组
- struts2学习笔记--ActionContext对象
- 窥探算法之美妙——统计整数二进制中1的个数
- 1024. Palindromic Number (25)
- 重头在来 -- dell1440 升级硬件,重装系统。
- android——error opening trace file: No such file or directory (2)
- 【C】指针变量
- 【C】指针变量
- ThinkPHP - session 数据库存储驱动
- 【uva12345】dynamic len 树状数组套线段树
- 蓝桥杯-基础练习-杨辉三角形
- test0
- C#中yield关键字用法(以及delegate/Lambde/Linq之间的关系)