前言
至此我们已经学完了创建型和结构型设计模式,相信大家对设计模式应该有了自己的认识。快马加鞭,我们就此开启行为型设计模式的学习。本文首先介绍较为常用的模板方法模式。
一、场景问题
使用程序模拟烹饪的整个过程,为了简化问题,目前只烹饪蔬菜和肉两种。
- 烹饪蔬菜:准备菜,起锅烧油,翻炒,加调料,出锅…
- 烹饪肉类:准备肉,焯水,起锅烧油,翻炒,加调料,出锅…
二、解决方案
1、传统解决方案
1.1、解决思路
定义抽象烹饪类,建立两个子类分别实现蔬菜与肉的烹饪。
-
代码实现
-
抽象烹饪类
public abstract class AbstractCook { /** * 烹饪 * @author xianzilei **/ public abstract void cook(); }
-
烹饪实现
-
蔬菜
public class VegetablesCook extends AbstractCook { @Override public void cook() { //开始 System.out.println("烹饪开始..."); //准备原材料 System.out.println("准备蔬菜..."); //加食用油 System.out.println("添加食用油..."); //加调味品 System.out.println("加生抽、盐..."); //翻炒出锅结束 System.out.println("烹饪结束..."); } }
-
肉
public class MeatCook extends AbstractCook { @Override public void cook() { //开始 System.out.println("烹饪开始..."); //准备原材料 System.out.println("准备肉..."); //焯水 System.out.println("肉焯水..."); //加食用油 System.out.println("添加食用油..."); //加调味品 System.out.println("加老抽、生抽、酱油、盐..."); //翻炒出锅结束 System.out.println("烹饪结束..."); } }
-
-
-
客户端测试
public static void main(String[] args) { //烹饪蔬菜 AbstractCook vegetablesCook = new VegetablesCook(); vegetablesCook.cook(); System.out.println("------------------------------------"); //烹饪肉 AbstractCook meatCook = new MeatCook(); meatCook.cook(); }
输出
烹饪开始... 准备蔬菜... 添加食用油... 加生抽、盐... 烹饪结束... ------------------------------------ 烹饪开始... 准备肉... 肉焯水... 添加食用油... 加老抽、生抽、酱油、盐... 烹饪结束...
1.2、弊端
上面的设计简单易懂,且相互隔离。但是仔细一看很有问题。首先就是存在大量重复的代码,烹饪开始结束,加食用油,加调料等;其次对于烹饪这个操作,它的执行流程一般是固定不变的,因此我们可以将烹饪操作定义出框架放在父类中,子类只存放框架中的动态的代码。
2、模板方法模式方案
理解了上面的改进策略,那么代码就很简单了
2.1、代码实现
-
重写抽象烹饪类
public abstract class AbstractCookTemplate { /** * 烹饪操作模板方法 * @author xianzilei **/ public final void cook() { //1、开始 start(); //2、准备原材料 prepareRawMaterials(); //3、焯水(子类可以选择是否执行) if (needBoiledWater()) { boiledWater(); } //4、加食用油 addCookingOil(); //5、加调味品 addCondiment(); //6、翻炒出锅结束 end(); } //烹饪开始 private void start() { System.out.println("烹饪开始..."); } //添加油 private void addCookingOil() { System.out.println("添加食用油..."); } //烹饪结束 private void end() { System.out.println("烹饪结束..."); } /**------------以上三个方法是公共方法----------------*/ //准备材料,子类负责实现 protected abstract void prepareRawMaterials(); //加调料,子类负责实现 protected abstract void addCondiment(); /**------------以上两个方法需要子类自己实现----------------*/ //焯水,钩子方法,一般是空实现,由子类来覆写 protected void boiledWater() { } //是否需要焯水,钩子方法,一般是默认实现,由子类选择性覆写 protected boolean needBoiledWater() { return false; } }
模板方法通常是固定了操作的执行流程,因此模板方法一般采用
final
修饰,避免子类误覆写导致失去使用模板方法模式的意义。 -
具体实现
-
蔬菜
public class VegetablesCook extends AbstractCookTemplate { @Override public void prepareRawMaterials() { System.out.println("准备蔬菜..."); } @Override public void addCondiment() { System.out.println("加生抽、盐..."); } }
蔬菜只实现需要实现的方法,因为蔬菜一般不需要焯水,因此对于焯水的钩子方法无需覆写,采用默认即可。
-
肉
public class MeatCook extends AbstractCookTemplate { @Override public void prepareRawMaterials() { System.out.println("准备肉..."); } @Override public void boiledWater() { System.out.println("肉焯水..."); } @Override public boolean needBoiledWater() { return true; } @Override public void addCondiment() { System.out.println("加老抽、生抽、酱油、盐..."); } }
肉的实现不仅需要实现抽象方法,因为肉一般需要焯水,所以会覆写钩子方法,同时定义焯水细节。
-
-
客户端测试
public static void main(String[] args) { //烹饪蔬菜 AbstractCookTemplate vegetablesCook = new VegetablesCook(); vegetablesCook.cook(); System.out.println("------------------------------------"); //烹饪肉 AbstractCookTemplate meatCook = new MeatCook(); meatCook.cook(); }
输出
烹饪开始... 准备蔬菜... 添加食用油... 加生抽、盐... 烹饪结束... ------------------------------------ 烹饪开始... 准备肉... 肉焯水... 添加食用油... 加老抽、生抽、酱油、盐... 烹饪结束...
2.2、方案解析
模板方法模式在抽象类中规定了操作的执行流程框架,子类只要实现自己需要的细节。一方面提高代码复用率,另一方面可以规范流程,减少不必要的缺陷。
三、模式剖析
1、模式定义
模板方法模式(Template Pattern):定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
2、模式结构
模板方法模式一般包含如下角色
-
Abstract Class
:抽象类负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。这些方法一般包含如下:
- 模板方法:定义算法的骨架,一般是按照某种顺序调用其余方法。
- 抽象方法:子类的特殊实现细节,由子类自己实现。
- 具体方法:公共代码,一般不具备特殊性,当然子类也可以覆写来实现自己的特殊性。
- 钩子方法:在抽象类中已经实现(或空实现),一般包括用于判断执行的逻辑方法和具体的需要子类实现的方法。
abstract class AbstractClass { public void TemplateMethod() //模板方法 { SpecificMethod(); abstractMethod1(); abstractMethod2(); if(isNeedExcute){ excute(); } } public void SpecificMethod() //具体方法 { System.out.println("抽象类中的具体方法被调用..."); } public abstract void abstractMethod1(); //抽象方法1 public abstract void abstractMethod2(); //抽象方法2 //钩子方法:判断是否需要执行 public boolean isNeedExcute() { return false; } //钩子方法 public void excute() { //一般为空实现,需要使用到的子类会覆写自己的逻辑 } }
-
Concrete Class
:具体子类实现抽象类中所定义的抽象方法和钩子方法。
//具体子类 class ConcreteClass extends AbstractClass { public void abstractMethod1() { System.out.println("抽象方法1的实现被调用..."); } public void abstractMethod2() { System.out.println("抽象方法2的实现被调用..."); } //钩子方法 public boolean isNeedExcute() { return true; } //钩子方法 public void excute() { //子类自己的逻辑... } }
3、模式结构图
4、优缺点
- 优点
- 提高代码复用率
- 规定算法的执行流程,使得子类实现细节处理时不会改变算法的执行次序。
- 反向控制。通过钩子方法来控制某一段代码逻辑是否需要执行
- 缺点
- 不同的实现都需要定义一个类,随着功能的不断扩展会导致类的数量剧增。
5、使用场景
- 对一些复杂的算法进行分割,将其算法中固定不变的部分设计为模板方法和父类具体方法,而一些可以改变的细节由其子类来实现。即:一次性实现一个算法的不变部分,并将可变的行为留给子类来实现。
- 各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。
- 需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制