JAVA设计模式(14) —<行为型>模板方法模式(Template Method)

1 定义:

模板方法模式(Template Method)

Define the skeleton of an algorithm in anoperation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.(定义一个操作中的算法的框架,而将一些步骤延迟到子类中。使用子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。)

模板方法模式是一种基于继承的代码复用技术,它是一种类行为型模式

       模板方法模式是结构最简单的行为型设计模式,在其结构中只存在父类与子类之间的继承关系。通过使用模板方法模式,可以将一些复杂流程的实现步骤封装在一系列基本方法中,在抽象父类中提供一个称之为模板方法的方法来定义这些基本方法的执行次序,而通过其子类来覆盖某些步骤,从而使得相同的算法框架可以有不同的执行结果。模板方法模式提供了一个模板方法来定义算法框架,而某些具体步骤的实现可以在其子类中完成。

 1.1 通用类图:

 

    由图2可知,模版方法模式由一个抽象类和一个(或一组)实现类通过继承结构组成,模板方法模式包含如下两个角色:

       (1)抽象类(AbstractClass)在抽象类中定义了一系列基本操作(Primitive Operations),这些基本操作可以是具体的,也可以是抽象的,每一个基本操作对应算法的一个步骤,在其子类中可以重定义或实现这些步骤。同时,在抽象类中实现了一个模板方法(Template Method)用于定义一个算法的框架,模板方法不仅可以调用在抽象类中实现的基本方法,也可以调用在抽象类的子类中实现的基本方法,还可以调用其他对象中的方法。

           抽象类中的方法分为2种: 

               模版方法:由抽象类声明并加以实现。一般来说,模版方法调用抽象方法来完成主要的逻辑功能,并且,模版方法大多会定义为final类型,指明主要的逻辑功能在子类中不能被重写。由于模板方法是具体方法,因此模板方法模式中的抽象层只能是抽象类,而不是接口。 

注意:为了防止恶意的操作,一般模板方法都加上final关键字,不允许被覆写

               基本方法:是实现算法各个步骤的方法,是模板方法的组成部分。基本方法又可以分为三种:抽象方法(Abstract Method)、具体方法(Concrete Method)和钩子方法(Hook Method)    

               抽象方法:父类中只声明但不加以实现,而是定义好规范,然后由它的子类去实现。 

               具体方法:一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承 

               钩子方法:由抽象类声明并加以实现。但是子类可以去扩展,子类可以通过扩展钩子方法来影响模版方法的逻辑。 

       (2)具体子类(ConcreteClass)它是抽象类的子类,用于实现在父类中声明的抽象基本操作以完成子类特定算法的步骤,也可以覆盖在父类中已经实现的具体基本操作。


1.2 通用代码:

源码实现如下:

public abstract class AbstractClass {

      //基本方法

      protected abstract void doSomething();

     

      //模板方法

      public final void templateMethod(){

           doSomething();

      }

}

 

public class ConcreteClass extends AbstractClass{

      @Override

      protected void doSomething() {

          

      }

}

 

public class Client {

      public static void main(String[] args){

           AbstractClass c = new ConcreteClass();

           c.templateMethod();

      }

}

 

2 优点

2.1 封装不变部分,扩展可变部分(可扩展性)

         把认为不变部分的算法封装到父类实现,而可变部分的则可以通过继承来继续扩展;

2.2 提取公共部分代码,便于维护(可维护性)

         不要让维护人员到处查找相似的代码;

2.3 行为由父类控制,子类实现

         基本方法是由子类实现的,因此子类可以通过扩展的方式增加相应的功能,符合开闭原则。

 

3 缺点

按设计习惯,抽象类负责声明最抽象、最一般的事物属性与方法,实现类完成具体事物属性和方法。但模板方法却反了:抽象类定义了部分抽象方法,由子类来实现,子类执行的结果影响了父类的结果,也就是子类对父类产生了影响,在复杂项目中,会带来代码阅读的困难。对新手产生不适。

 

4 应用场景

4.1 多个子类有公有的方法,并且逻辑基本相同时。

4.2 重要、复杂的算法,可以把核心算法设计为模板方法,周边的相关细节功能则由各个子类实现。

4.3 重构时,模板方法模式是一个经常使用的模式,把相同的代码抽取到父类中,然后通过钩子函数来约束其行为。

 

5 注意事项

5.1 抽象模板中的基本方法尽量设计为protected类型,符合迪米特法则,不需要暴露的属性或方法尽量不要设置为protected类型。实现类若非必要,尽量不要扩大父类中的访问权限。

5.2 此也是一种较好的父类调用子类方法的方式。

 

6 扩展

6.1模板方法模式的本质

      模板方法模式的本质固定算法骨架

      模板方法模式主要是通过制定模板,把算法步骤固定下来,至于谁来实现,模板可以自己实现,也可以由子类去实现,还可以通过回调机制让其它类来实现。

      通过固定算法骨架来约束子类的行为,并在特定的扩展点来让子类进行功能扩展,从而让程序既有很好的复用性,又有较好的扩展性。

2、对设计原则的体现

      模板方法很好的体现了开闭原则和里氏替换原则

      首先从设计上分离变与不变,然后把不变的部分抽取出来,定义到父类中,比如算法骨架,一些公共的、固定的实现等。这些不变的部分被封闭起来,尽量不去修改它们,要想扩展新的功能,那就使用子类来扩展,通过子类来实现可变化的步骤,对于这种新增功能的做法是开放。

       其次,能够实现统一的算法骨架,通过切换不同的具体实现来切换不同的功能,一个根本原因就是里氏替换原则,遵循这个原则,保证所有的子类实现的是同一个算法模板,并能在使用模板的地方,根据需要切换不同的具体实现。

   

7 范例

7.1泡茶与泡咖啡

我们可以看出泡茶泡咖啡可以共用一个相同的泡法(算法):

          把水煮沸——>用沸水冲泡——>倒入杯子中——>加入调料。

首先是抽象类,该抽象类提供了冲泡咖啡或者茶的具体流程,并且实现了逻辑步骤,煮沸水和倒入杯子中。将沸水冲泡和加入调料交由具体的子类(咖啡、茶)来实现。

[java]  view plain copy print ?
  1. public abstract class CaffeineBeverage {  
  2.       
  3.     /** 
  4.      *  
  5.      * @desc  
  6.      *          模板方法,用来控制泡茶与冲咖啡的流程 
  7.      *          申明为final,不希望子类覆盖这个方法,防止更改流程的执行顺序 
  8.      * @return void 
  9.      */  
  10.     final void prepareRecipe(){  
  11.         boilWater();  
  12.         brew();  
  13.         pourInCup();  
  14.         addCondiments();  
  15.     }  
  16.       
  17.     /** 
  18.      * @desc  
  19.      *       将brew()、addCondiment()声明为抽象类,具体操作由子类实现 
  20.      * @return void 
  21.      */  
  22.     abstract void brew();  
  23.       
  24.     abstract void addCondiments();  
  25.       
  26.     void boilWater(){  
  27.         System.out.println("Boiling water...");  
  28.     }  
  29.       
  30.     void pourInCup(){  
  31.         System.out.println("Pouring into Cup...");  
  32.     }  
  33. }  

          然后是具体的子类实现:

          Coffee.java

[java]  view plain copy print ?
  1. public class Coffee extends CaffeineBeverage{  
  2.   
  3.     void addCondiments() {  
  4.         System.out.println("Adding Sugar and Milk...");  
  5.     }  
  6.   
  7.     void brew() {  
  8.         System.out.println("Dripping Coffee through filter...");  
  9.     }  
  10.   
  11. }  

          Tea.java

[java]  view plain copy print ?
  1. public class Tea extends CaffeineBeverage{  
  2.   
  3.     void addCondiments() {  
  4.         System.out.println("Adding Lemon...");  
  5.           
  6.     }  
  7.   
  8.     void brew() {  
  9.         System.out.println("Steeping the tea...");  
  10.     }  
  11.   
  12. }  

          完成,做了这么久终于可以泡杯咖啡来喝了。

[java]  view plain copy print ?
  1. public class Test {  
  2.     public static void main(String[] args) {  
  3.         Tea tea = new Tea();  
  4.         tea.prepareRecipe();  
  5.     }  
  6. }  

          从上面的运行结果可以看出,我们的模板方法模式表现的非常良好,但是我们似乎忽略了一些东西?如果某些客户并不喜欢加入调料,而喜欢原生态的,但是我们的算法总是会给客户加入调料,怎么解决?

          遇到这个问题我们可以使用钩子。所谓钩子就是一种被声明在抽象类中的方法,但只有空的或者默认的实现。子的存在可以使子类能够对算法的不同点进行挂钩,即让子类能够对模板方法中某些即将发生变化的步骤做出相应反应。当然要不要挂钩,由子类决定

          所以对于上面的要求,我们可以做出如下修改。

[java]  view plain copy print ?
  1. public abstract class CaffeineBeverageWithHook {  
  2.       
  3.     void prepareRecipe(){  
  4.         boilWater();  
  5.         brew();  
  6.         pourInCup();  
  7.         if(customerWantsCondiments()){    //如果顾客需要添加调料,我们才会调用addCondiments()方法  
  8.             addCondiments();  
  9.         }  
  10.     }  
  11.       
  12.     abstract void brew();  
  13.       
  14.     abstract void addCondiments();  
  15.       
  16.     void boilWater(){  
  17.         System.out.println("Boiling water...");  
  18.     }  
  19.       
  20.     void pourInCup(){  
  21.         System.out.println("Pouring into Cup...");  
  22.     }  
  23.       
  24.     public boolean customerWantsCondiments(){  
  25.         return true;  
  26.     }  
  27. }  

          客户是否需要加入调料,只需要回答y或者n

[java]  view plain copy print ?
  1. public class CoffeeWithHook extends CaffeineBeverageWithHook{  
  2.     void addCondiments() {  
  3.         System.out.println("Adding Sugar and Milk...");  
  4.     }  
  5.   
  6.     void brew() {  
  7.         System.out.println("Dripping Coffee through filter...");  
  8.     }  
  9.       
  10.     /** 
  11.      * 覆盖该钩子,提供自己的实现方法 
  12.      */  
  13.     public boolean customerWantsCondiments(){  
  14.         if("y".equals(getUserInput().toLowerCase())){  
  15.             return true;  
  16.         }  
  17.         else{  
  18.             return false;  
  19.         }  
  20.     }  
  21.   
  22.     public String getUserInput(){  
  23.         String answer = null;  
  24.         System.out.print("Would you like milk and sugar with your coffee(y/n):");  
  25.         BufferedReader in = new BufferedReader(new InputStreamReader(System.in));  
  26.         try {  
  27.             answer = in.readLine();  
  28.         } catch (IOException e) {  
  29.             e.printStackTrace();  
  30.         }  
  31.         if(answer == null){  
  32.             return "n";  
  33.         }  
  34.         return answer;  
  35.           
  36.     }  
  37. }  

          测试程序

[java]  view plain copy print ?
  1. public class Test {  
  2.     public static void main(String[] args) {  
  3.         CoffeeWithHook coffeeHook = new CoffeeWithHook();  
  4.         coffeeHook.prepareRecipe();  
  5.     }  
  6. }     

          运行结果

         从上面可以看出钩子能够作为条件来进行控制。

 7.2(手机开机过程,有钩子实现)

 

实现如下:

 

[java]  view plain copy
  1. package _04_TemplateMethod;  
  2.   
  3. public abstract class Phone {  
  4.     //模板方法:开机  
  5.     public final void turnOn(){  
  6.         initSystem();  
  7.         showLogo();  
  8.           
  9.         if(isPlayMusic())  
  10.             playMusic();  
  11.           
  12.         lockAndSleep();  
  13.     }  
  14.     //初始系统  
  15.     protected abstract void initSystem();  
  16.     //显示开机logo  
  17.     protected abstract void showLogo();  
  18.     //播放开机音乐  
  19.     protected abstract void playMusic();  
  20.     //锁屏并待机  
  21.     protected abstract void lockAndSleep();  
  22.       
  23.     //是否播放开机音乐  :钩子函数  
  24.     protected boolean isPlayMusic(){      
  25.         return true;  
  26.     }  
  27. }  
  28.   
  29. public class SmartPhone extends Phone{  
  30.   
  31.     @Override  
  32.     protected void initSystem() {  
  33.         System.out.println("正在加载智能机系统 。。。");  
  34.     }  
  35.   
  36.     @Override  
  37.     protected void showLogo() {  
  38.         System.out.println("显示LOGO:3G梦想!");  
  39.     }  
  40.   
  41.     @Override  
  42.     protected void playMusic() {  
  43.         System.out.println("播放开机音乐:123456");  
  44.     }  
  45.   
  46.     @Override  
  47.     protected void lockAndSleep() {  
  48.         System.out.println("一切就绪,锁屏待机");  
  49.     }  
  50.   
  51.     @Override  
  52.     protected boolean isPlayMusic(){  
  53.         return false;//什么年代了,智能机不播开机音乐!  
  54.     }  
  55. }  
  56.   
  57. public class FeaturePhone extends Phone{  
  58.     @Override  
  59.     protected void initSystem() {  
  60.         System.out.println("正在加载功能机系统 。。。");  
  61.     }  
  62.   
  63.     @Override  
  64.     protected void showLogo() {  
  65.         System.out.println("显示LOGO:NOKIA待机王!");  
  66.     }  
  67.   
  68.     @Override  
  69.     protected void playMusic() {  
  70.         System.out.println("播放开机音乐:123456");  
  71.     }  
  72.   
  73.     @Override  
  74.     protected void lockAndSleep() {  
  75.         System.out.println("一切就绪,锁屏待机");  
  76.     }     
  77. }  
  78.   
  79. public class TestPhone {  
  80.     public static void main(String[] args) {  
  81.         Phone sp = new SmartPhone();  
  82.         sp.turnOn();  
  83.           
  84.         Phone fp = new FeaturePhone();  
  85.         fp.turnOn();          
  86.     }  
  87. }  
  88.   
  89. 结果:  
  90.   
  91. 正在加载智能机系统 。。。  
  92. 显示LOGO:3G梦想!  
  93. 一切就绪,锁屏待机  
  94.   
  95. 正在加载功能机系统 。。。  
  96. 显示LOGO:NOKIA待机王!  
  97. 播放开机音乐:123456  
  98. 一切就绪,锁屏待机  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值