23种设计模式(6):模板方法模式

来源:互联网 发布:数据机房温度湿度标准 编辑:程序博客网 时间:2024/05/17 02:08

定义:定义一个操作中算法的框架,而将一些步骤延迟到子类中,使得子类可以不改变算法的结构即可重定义该算法中的某些特定步骤。

类型:行为类模式

类图:

        事实上,模版方法是编程中一个经常用到的模式。先来看一个例子,某日,程序员A拿到一个任务:给定一个整数数组,把数组中的数由小到大排序,然后把排序之后的结果打印出来。经过分析之后,这个任务大体上可分为两部分,排序和打印,打印功能好实现,排序就有点麻烦了。但是A有办法,先把打印功能完成,排序功能另找人做。

[java] view plaincopy
  1. abstract class AbstractSort {  
  2.       
  3.     /** 
  4.      * 将数组array由小到大排序 
  5.      * @param array 
  6.      */  
  7.     protected abstract void sort(int[] array);  
  8.       
  9.     public void showSortResult(int[] array){  
  10.         this.sort(array);  
  11.         System.out.print("排序结果:");  
  12.         for (int i = 0; i < array.length; i++){  
  13.             System.out.printf("%3s", array[i]);  
  14.         }  
  15.     }  
  16. }  

        写完后,A找到刚毕业入职不久的同事B说:有个任务,主要逻辑我已经写好了,你把剩下的逻辑实现一下吧。于是把AbstractSort类给B,让B写实现。B拿过来一看,太简单了,10分钟搞定,代码如下:

[java] view plaincopy
  1. class ConcreteSort extends AbstractSort {  
  2.   
  3.     @Override  
  4.     protected void sort(int[] array){  
  5.         for(int i=0; i<array.length-1; i++){  
  6.             selectSort(array, i);  
  7.         }  
  8.     }  
  9.       
  10.     private void selectSort(int[] array, int index) {  
  11.         int MinValue = 32767// 最小值变量  
  12.         int indexMin = 0// 最小值索引变量  
  13.         int Temp; // 暂存变量  
  14.         for (int i = index; i < array.length; i++) {  
  15.             if (array[i] < MinValue){ // 找到最小值  
  16.                 MinValue = array[i]; // 储存最小值  
  17.                 indexMin = i;   
  18.             }  
  19.         }  
  20.         Temp = array[index]; // 交换两数值  
  21.         array[index] = array[indexMin];  
  22.         array[indexMin] = Temp;  
  23.     }  
  24. }  

写好后交给A,A拿来一运行:

[java] view plaincopy
  1. public class Client {  
  2.     public static int[] a = { 1032195712043 }; // 预设数据数组  
  3.     public static void main(String[] args){  
  4.         AbstractSort s = new ConcreteSort();  
  5.         s.showSortResult(a);  
  6.     }  
  7. }  

运行结果:

排序结果:  0  1  3  4  5  7  9 10 12 32

        运行正常。行了,任务完成。没错,这就是模版方法模式。大部分刚步入职场的毕业生应该都有类似B的经历。一个复杂的任务,由公司中的牛人们将主要的逻辑写好,然后把那些看上去比较简单的方法写成抽象的,交给其他的同事去开发。这种分工方式在编程人员水平层次比较明显的公司中经常用到。比如一个项目组,有架构师,高级工程师,初级工程师,则一般由架构师使用大量的接口、抽象类将整个系统的逻辑串起来,实现的编码则根据难度的不同分别交给高级工程师和初级工程师来完成。怎么样,是不是用到过模版方法模式?

 

模版方法模式的结构

       模版方法模式由一个抽象类和一个(或一组)实现类通过继承结构组成,抽象类中的方法分为三种:

  • 抽象方法:父类中只声明但不加以实现,而是定义好规范,然后由它的子类去实现。
  • 模版方法:由抽象类声明并加以实现。一般来说,模版方法调用抽象方法来完成主要的逻辑功能,并且,模版方法大多会定义为final类型,指明主要的逻辑功能在子类中不能被重写。
  • 钩子方法:由抽象类声明并加以实现。但是子类可以去扩展,子类可以通过扩展钩子方法来影响模版方法的逻辑。
  • 抽象类的任务是搭建逻辑的框架,通常由经验丰富的人员编写,因为抽象类的好坏直接决定了程序是否稳定性。

       实现类用来实现细节。抽象类中的模版方法正是通过实现类扩展的方法来完成业务逻辑。只要实现类中的扩展方法通过了单元测试,在模版方法正确的前提下,整体功能一般不会出现大的错误。

 

模版方法的优点及适用场景

       容易扩展。一般来说,抽象类中的模版方法是不易发生改变的部分,而抽象方法是容易发生变化的部分,因此通过增加实现类一般可以很容易实现功能的扩展,符合开闭原则。

       便于维护。对于模版方法模式来说,正是由于他们的主要逻辑相同,才使用了模版方法,假如不使用模版方法,任由这些相同的代码散乱的分布在不同的类中,维护起来是非常不方便的。

       比较灵活。因为有钩子方法,因此,子类的实现也可以影响父类中主逻辑的运行。但是,在灵活的同时,由于子类影响到了父类,违反了里氏替换原则,也会给程序带来风险。这就对抽象类的设计有了更高的要求。

       在多个子类拥有相同的方法,并且这些方法逻辑相同时,可以考虑使用模版方法模式。在程序的主框架相同,细节不同的场合下,也比较适合使用这种模式。

补充:钩子方法的使用

     模板方法模式中,在父类中提供了一个定义算法框架的模板方法,还提供了一系列抽象方法、具体方法和钩子方法,其中钩子方法的引入使得子类可以控制父类的行为。最简单的钩子方法就是空方法,代码如下:
[cpp] view plain copy
  1. public virtual void Display() {   }    

      当然也可以在钩子方法中定义一个默认的实现,如果子类不覆盖钩子方法,则执行父类的默认实现代码。

      另一种钩子方法可以实现对其他方法进行约束,这种钩子方法通常返回一个bool类型,即返回truefalse,用来判断是否执行某一个基本方法,下面通过一个实例来说明这种钩子方法的使用。

      某软件公司欲为销售管理系统提供一个数据图表显示功能,该功能的实现包括如下几个步骤:

      (1) 从数据源获取数据;

      (2) 将数据转换为XML格式;

      (3) 以某种图表方式显示XML格式的数据。

      该功能支持多种数据源和多种图表显示方式,但所有的图表显示操作都基于XML格式的数据,因此可能需要对数据进行转换,如果从数据源获取的数据已经是XML数据则无须转换。

       由于该数据图表显示功能的三个步骤次序是固定的,且存在公共代码(例如数据格式转换代码),满足模板方法模式的适用条件,可以使用模板方法模式对其进行设计。因为数据格式的不同,XML数据可以直接显示,而其他格式的数据需要进行转换,因此第(2)步“将数据转换为XML格式”的执行存在不确定性,为了解决这个问题,可以定义一个钩子方法IsNotXMLData()来对数据转换方法进行控制。通过分析,该图表显示功能的基本结构如图4所示:


图4  数据图表显示功能结构图

       可以将公共方法和框架代码放在抽象父类中,代码如下:

[cpp] view plain copy
  1. //DataViewer.cs  
  2. using System;  
  3.   
  4. namespace TemplateMethodSample  
  5. {  
  6.     abstract class DataViewer  
  7.     {  
  8.         //抽象方法:获取数据  
  9.         public abstract void GetData();  
  10.   
  11.         //具体方法:转换数据  
  12.         public void ConvertData()   
  13.         {  
  14.             Console.WriteLine("将数据转换为XML格式。");  
  15.         }  
  16.   
  17.         //抽象方法:显示数据  
  18.         public abstract void DisplayData();  
  19.   
  20.         //钩子方法:判断是否为XML格式的数据  
  21.         public virtual bool IsNotXMLData()  
  22.         {  
  23.             return true;  
  24.         }  
  25.   
  26.         //模板方法  
  27.         public void Process()  
  28.         {  
  29.             GetData();  
  30.             //如果不是XML格式的数据则进行数据转换  
  31.             if (IsNotXMLData())  
  32.             {  
  33.                 ConvertData();  
  34.             }  
  35.             DisplayData();  
  36.         }  
  37.     }  
  38. }  
       在上面的代码中,引入了一个钩子方法IsNotXMLData(),其返回类型为bool类型,在模板方法中通过它来对数据转换方法ConvertData()进行约束,该钩子方法的默认返回值为true,在子类中可以根据实际情况覆盖该方法,其中用于显示XML格式数据的具体子类XMLDataViewer代码如下:
[cpp] view plain copy
  1. //XMLDataViewer.cs  
  2. using System;  
  3.   
  4. namespace TemplateMethodSample  
  5. {  
  6.     class XMLDataViewer : DataViewer  
  7.     {  
  8.         //实现父类方法:获取数据  
  9.         public override void GetData()   
  10.         {  
  11.             Console.WriteLine("从XML文件中获取数据。");  
  12.         }  
  13.   
  14.         //实现父类方法:显示数据,默认以柱状图方式显示,可结合桥接模式来改进  
  15.         public override void DisplayData()   
  16.         {  
  17.             Console.WriteLine("以柱状图显示数据。");  
  18.         }  
  19.   
  20.         //覆盖父类的钩子方法  
  21.         public override bool IsNotXMLData()  
  22.         {  
  23.             return false;  
  24.         }  
  25.     }  
  26. }  
       在具体子类XMLDataViewer中覆盖了钩子方法IsNotXMLData(),返回false,表示该数据已为XML格式,无须执行数据转换方法ConvertData(),客户端代码如下:
[cpp] view plain copy
  1. //Program.cs  
  2. using System;  
  3.   
  4. namespace TemplateMethodSample  
  5. {  
  6.     class Program  
  7.     {  
  8.         static void Main(string[] args)  
  9.         {  
  10.             DataViewer dv;  
  11.             dv = new XMLDataViewer();  
  12.             dv.Process();  
  13.             Console.Read();  
  14.         }  
  15.     }  
  16. }  

       该程序运行结果如下:

XML文件中获取数据。

以柱状图显示数据。

原创粉丝点击