设计模式(十):模板方法模式
一、概述
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
二、结构类图
三、解决问题
模板方法就是提供一个算法框架,框架里面的步骤有些是父类已经定好的,有些需要子类自己实现。相当于要去办一件事情,行动的流程已经定好了,但有些步骤需要自己去做,而有些步骤可能别人帮我们做了。就拿建网站来说,一般的程序是购买域名-->购买空间-->上传网站-->备案-->审核,每个网站的创建必须经过这样的固定程序,但除了审核不用建站者关心,其他的步骤都要建站者自己去完成。
四、应用实例
现在我们很多家庭都有了豆浆机,豆浆的营养价值不用我多说了。制作豆浆的程序简单点来说就是选材--->添加配料--->浸泡--->放到豆浆机打碎,通过添加不同的配料,可以制作出不同口味的豆浆,但是选材、浸泡和放到豆浆机打碎这几个步骤对于制作每种口味的豆浆都是一样的。
1、创建抽象类
package templatemethod.pattern; //豆浆类,抽象类 public abstract class SoyaMilk { //这是模板方法,用final修饰,不允许子类覆盖。模板方法定义了制作豆浆的程序 final void prepareRecipe(){ selectMaterial(); addCondiments(); soak(); beat(); } //选材方法,选择黄豆 void selectMaterial(){ System.out.println("第一步、选择好了新鲜黄豆"); } //可以添加不同的配料,在这里设置为抽象方法,子类必须实现 abstract void addCondiments(); //浸泡 void soak(){ System.out.println("第三步、黄豆和配料开始浸泡,大概需要5个小时"); } //放到豆浆机打碎 void beat(){ System.out.println("第四步、黄豆的配料放到豆浆机打碎"); } }
2、创建红枣豆浆
package templatemethod.pattern; //红枣豆浆 public class ReddatesSoyaMilk extends SoyaMilk{ //实现父类的添加配料方法 @Override void addCondiments() { System.out.println("第二步、添加红枣配料"); } }
3、创建核桃豆浆
package templatemethod.pattern; //核桃豆浆 public class NutSoyaMilk extends SoyaMilk{ //实现父类的添加配料方法 @Override void addCondiments() { System.out.println("第二步、添加核桃配料"); } }
4、测试制作豆浆
package templatemethod.pattern; public class SoyaMilkTest { public static void main(String[] args){ //制作红枣豆浆 System.out.println(); System.out.println("-----制作红枣豆浆步骤-------"); SoyaMilk reddatesSoyaMilk = new ReddatesSoyaMilk(); reddatesSoyaMilk.prepareRecipe(); //制作核桃豆浆 System.out.println(); System.out.println("-----制作核桃豆浆步骤-------"); SoyaMilk nutSoyaMilk = new NutSoyaMilk(); nutSoyaMilk.prepareRecipe(); } }
运行结果:
五、优缺点
1、优点
(1)、算法只存在于一个地方,也就是在父类中,容易修改。需要修改算法时,只要修改父类的模板方法或者已经实现的某些步骤,子类就会继承这些修改。
(2)、实现了最大化代码复用。父类的模板方法和已实现的某些步骤会被子类继承而直接使用。
(3)、既统一了算法,也提供了很大的灵活性。父类的模板方法确保了算法的结构保持不变,同时由子类提供部分步骤的实现。
(4)、提供了一个基本框架,容易扩展子类。模板方法有框架控制如何做事情,而由使用框架的人指定框架算法中每个步骤的细节。子类只要继承父类,实现抽象方法,就可以使用父类的算法。
2、缺点
(1)、模板方法使用继承方式复用代码,如果要在基本算法里面增加一个步骤,而该步骤是抽象的话,每个子类都要修改代码,实现这个步骤。
六、模板方法中的钩子方法
在模板方法模式的父类中,我们可以定义一个方法,它默认不做任何事,子类可以视情况要不要覆盖它,该方法称为“钩子”。我们还是用上面做豆浆的例子来讲解。
1、创建有钩子方法的父类
package templatemethod.pattern; //豆浆类,抽象类 public abstract class SoyaMilkWithHook { //这是模板方法,用final修饰,不允许子类覆盖。模板方法定义了制作豆浆的程序 final void prepareRecipe(){ selectMaterial(); //判断是否添加配料 if(customerWantsCondiments()){ addCondiments(); } soak(); beat(); } //选材方法,选择黄豆 void selectMaterial(){ System.out.println("选择好了新鲜黄豆"); } //可以添加不同的配料,在这里设置为抽象方法,子类必须实现 abstract void addCondiments(); //浸泡 void soak(){ System.out.println("材料开始浸泡,大概需要5个小时"); } //放到豆浆机打碎 void beat(){ System.out.println("材料放到豆浆机打碎"); } //钩子方法,是否添加配料 boolean customerWantsCondiments(){ return true; } }
2、创建纯豆浆子类
package templatemethod.pattern; //制作纯豆浆,不添加任何配料 public class PureSoyaMilk extends SoyaMilkWithHook{ @Override void addCondiments() { } //覆盖钩子方法,不添加配料 @Override boolean customerWantsCondiments(){ return false; } }
3、测试制作纯豆浆
package templatemethod.pattern; public class PureSoyaMilkTest { public static void main(String[] args){ //制作纯豆浆 System.out.println(); System.out.println("-----制作纯豆浆步骤-------"); SoyaMilkWithHook pureSoyaMilk = new PureSoyaMilk(); pureSoyaMilk.prepareRecipe(); } }
测试结果:
钩子方法的作用
1、让子类实现算法中的可选部分。算法中的某些步骤是可选的,子类可以做出决定是否需要这些步骤。
2、如果钩子对于子类的实现不重要时,子类可以对钩子置之不理。
3、钩子可以让子类能够有机会对模板方法中某些即将发生的(或刚刚发生的)步骤作出反应。可以在钩子中实现我们对于某个步骤执行需要作出的动作,模板方法的某个步骤执行时,调用钩子。
七、模板方法与策略的比较
1、相同点
(1)、都封装了算法。
2、不同点
(1)、策略使用组合方式实现算法,客户可以自由选择算法;模板方法使用继承方式获得算法,确保算法的基本结构不变。
(2)、策略代码复用程度低,每个算法都要对应一个类;模板方法代码复用高,子类可以继承父类的代码。
(3)、策略依赖少,自己实现整个算法;模板方法依赖高,子类依赖父类中的方法的实现。
八、使用场景
1、当要完成一件事情,它有固定的程序流程,但某些步骤是自定义的,使用模板方法。
2、当需要创建框架时,在超类中提供一个基础的方法,达到代码的复用,并允许子类指定行为。