Template Method Pattern 模版方法模式

Template Method Pattern

作者:崔涛涛(07770225)

  今天我要在这里介绍的是C#设计模式中的模版方法模式。

  问题:现在有一家汽车生产商需要一个管理汽车生产的管理软件。其中在该系统中有一个管理组装汽车的子模块。该模块要能根据不同的汽车类型来组装相应的汽车。汽车的被组装部分分别是汽车的车盖,车地盘,后备箱,车胎。现在来试着实现下该过程。

 

  根据上面的描述,我在这里选择里跑车和商务车来进行演示。跑车的车盖,车地盘,后备箱,车胎均不是标准的,而商务车的车盖,车地盘不是标准的。我将不同类型的汽车分别封装到不同的类中,将汽车组装过程交给汽车类进行管理。

我在这里展示我的不假思索的解决方案:

  类图如下:

  

  实现代码:

  

跑车类代码:
   /// <summary>
    /// 跑车
    /// </summary>
    public class SportsCar
    {
        /// <summary>
        /// 组装汽车
        /// </summary>
        /// <param name="pTextContainer">显示组装信息的文本容器</param>
        public void AssembleCar(string pTextContainer)
        {
            pTextContainer = "开始组装汽车:\n";
            pTextContainer += "\t 第一步" + Step1() + "\n";
            pTextContainer += "\t 第二步" + Step2() + "\n";
            pTextContainer += "\t 第三步" + Step3() + "\n";
            pTextContainer += "\t 第四步" + Step4() + "\n";
        }

        /// <summary>
        /// 组装步骤一
        /// </summary>
        /// <returns>组装步骤一信息</returns>
        public string Step1()
        {
            return "跑车地盘";
        }

        /// <summary>
        /// 组装步骤二
        /// </summary>
        /// <returns>组装步骤二信息</returns>
        public string Step2()
        {
            return "敞篷车盖";
        }

        /// <summary>
        /// 组装步骤三
        /// </summary>
        /// <returns>组装步骤三信息</returns>
        public string Step3()
        {
            return "敞篷跑车后备箱";
        }

        /// <summary>
        /// 组装步骤四
        /// </summary>
        /// <returns>组装步骤四信息</returns>
        public string Step4()
        {
            return "跑车车胎";
        }
    }

    商务车类代码:
   /// <summary>
    /// 跑车
    /// </summary>
    public class OfficeCar
    {
        /// <summary>
        /// 组装汽车
        /// </summary>
        /// <param name="pTextContainer">显示组装信息的文本容器</param>
        public void AssembleCar(string pTextContainer)
        {
            pTextContainer = "开始组装汽车:\n";
            pTextContainer += "\t 第一步" + Step1() + "\n";
            pTextContainer += "\t 第二步" + Step2() + "\n";
            pTextContainer += "\t 第三步" + Step3() + "\n";
            pTextContainer += "\t 第四步" + Step4() + "\n";
        }

        /// <summary>
        /// 组装步骤一
        /// </summary>
        /// <returns>组装步骤一信息</returns>
        public string Step1()
        {
            return "商务车地盘";
        }

        /// <summary>
        /// 组装步骤二
        /// </summary>
        /// <returns>组装步骤二信息</returns>
        public string Step2()
        {
            return "商务车车盖";
        }

        /// <summary>
        /// 组装步骤三
        /// </summary>
        /// <returns>组装步骤三信息</returns>
        public string Step3()
        {
            return "标准后备箱";
        }

        /// <summary>
        /// 组装步骤四
        /// </summary>
        /// <returns>组装步骤四信息</returns>
        public string Step4()
        {
            return "标准车胎";
        }
    }

  

  现在就让我们开探讨下这种方案中的缺陷。

         先让我们比较下 类SportsCar和 类OfficeCar这两个类。从中我们可以看出这两个类的功能逻辑一样:

    1. 方法AssembleCar()的逻辑过程一样;

    2. 只是方法Step1(),Step2(),Step3(),Step4()中的逻辑不同。

      这样分别实现Sports Car 和 Office Car就产生不必要的代码,同时程序员也多付出了不必要的劳动。在代码量大的情况下,这是很累人的,很不给力。

    

 

      下面是我用模版方法模式来解决类继承带来的缺陷。

  先让我们了解下模版方法模式的用意:

  准备一个抽象类,将部分逻辑以具体方法以及具体构造子的形式实现,然后声明一些抽象方法来迫使子类实现剩余的逻辑。不同的子类可以以不同的方式实现这些  抽象方法,从而对剩余的逻辑有不同的实现。这就是模版方法模式的用意。(参考《C#设计模式》)

  模版方法模式的结构图:

    

  从上面的文字叙述和类图可以看出,在模版方法模式中,模版类提供功能逻辑(在方法TemplateMethod实现功能,在一系列方法PrimitiveMethod实现功能的具体细节),该功能逻辑可以根据需要被重写进行相应的调整,但是不能破坏,否则这就失去了模版方法模式的意义,变成了单纯的类继承。不过在一般情况,模板类的派生类都只重写全部或者一部分PrimitiveMethod,而不会重写模版方法TemplateMethod。

注:模版进程经常是被处理成抽象类,方法PrimitiveMethod在模版类中声明称抽象的,然后交给派生类进行实现。具体如何处理还得看实际情况。不过模版类的逻辑结构不能被破坏。

       下面,我让我们来试试模版方法模式。

  运用模版方法模式后的类图:

  

好的,现在让我们来看看模版方法模式是否解决了之前不假思索的不足。

  从上面的类图中我们可以看出,模板提供类基本汽车的组装过程,派生子类_SportsCar和_OfficeCar 只是从父类中重写他们需要的成员。这就避免了不必要的冗余代码。

  

代码
1 相关代码:
2 汽车模版:
3 /// <summary>
4 /// 汽车模版
5 /// </summary>
6 public abstract class _TemplateCar
7 {
8 /// <summary>
9 /// 组装汽车
10 /// </summary>
11 /// <param name="pTextContainer">显示组装信息的文本容器</param>
12 public void AssembleCar(string pTextContainer)
13 {
14 pTextContainer = "开始组装汽车:\n";
15 pTextContainer += "\t 第一步" + Step1() + "\n";
16 pTextContainer += "\t 第二步" + Step2() + "\n";
17 pTextContainer += "\t 第三步" + Step3() + "\n";
18 pTextContainer += "\t 第四步" + Step4() + "\n";
19 }
20
21 /// <summary>
22 /// 组装步骤一
23 /// </summary>
24 /// <returns>组装步骤一信息</returns>
25 public virtual string Step1()
26 {
27 return "标准车地盘";
28 }
29
30 /// <summary>
31 /// 组装步骤二
32 /// </summary>
33 /// <returns>组装步骤二信息</returns>
34 public virtual string Step2()
35 {
36 return "标准车盖";
37 }
38
39 /// <summary>
40 /// 组装步骤三
41 /// </summary>
42 /// <returns>组装步骤三信息</returns>
43 public virtual string Step3()
44 {
45 return "标准后备箱";
46 }
47
48 /// <summary>
49 /// 组装步骤四
50 /// </summary>
51 /// <returns>组装步骤四信息</returns>
52 public virtual string Step4()
53 {
54 return "标准车胎";
55 }
56 }
57 跑车子类:
58 public class _SportsCar : _TemplateCar
59 {
60 /// <summary>
61 /// 重写组装步骤一
62 /// </summary>
63 /// <returns>重写后的组装步骤一信息</returns>
64 public override string Step1()
65 {
66 return "跑车地盘";
67 }
68
69 /// <summary>
70 /// 重写组装步骤二
71 /// </summary>
72 /// <returns>重写后的组装步骤二信息</returns>
73 public override string Step2()
74 {
75 return "敞篷车盖";
76 }
77
78 /// <summary>
79 /// 重写组装步骤三
80 /// </summary>
81 /// <returns>重写后的组装步骤三信息</returns>
82 public override string Step3()
83 {
84 return "敞篷跑车后备箱";
85 }
86
87 /// <summary>
88 /// 重写组装步骤四
89 /// </summary>
90 /// <returns>重写后的组装步骤四信息</returns>
91 public override string Step4()
92 {
93 return "跑车车胎";
94 }
95 }
96
97 商务车子类:
98 public class _OfficeCar : _TemplateCar
99 {
100 /// <summary>
101 /// 重写组装步骤一
102 /// </summary>
103 /// <returns>重写后的组装步骤一信息</returns>
104 public override string Step1()
105 {
106 return "商务车地盘";
107 }
108
109 /// <summary>
110 /// 重写组装步骤二
111 /// </summary>
112 /// <returns>重写后的组装步骤二信息</returns>
113 public override string Step2()
114 {
115 return "商务车车盖";
116 }
117 }
118

由于模板模式主要体现的是利用类的继承特性来进行代码重构,因此在这里将《C# 设计模式》中提到的重构的原则在下面列出。我个人认为这些原则对学习者的帮助挺大的。

下面是《C# 设计模式》一书中提到的重构原则:

在对一个继承的等级结构做重构时,一个应当遵从的原则便是将行为尽量移动到结构的高端,而将状态尽量移动到结构的低端。

1995年,Auer曾在文献【AUER95】中指出:

  1. 应当根据行为而不是状态定义一个类。也就是说,一个类的实现首先建立在行为的基础之上,而不是建立在状态的基础之上。
  2. 在实现行为时,是用抽象状态而不是用具体状态。如果一个行为涉及到对象的状态时,使用间接的引用而不是直接的引用。换言之,应当使用取值方法而不是直接引用属性。
  3. 给操作划分层次。一个类的行为应当放到一个小组核心方法(Kernel Methods)里面,这些方法可以很方便地在子类中加以置换。
  4. 将状态属性的确认推迟到子类中。不要在抽象类中过早地声明属性变量,应将它们尽量地推迟到子类中去声明。在抽象超类中,如果需要状态属性的话,可以调用抽象的取值方法,而将抽象的取值方法的实现放到具体子类中。

如果能够遵从这样的原则,那么就可以在等级结构中将接口与实现分隔开来,将抽象与具体分割开来,从而保证代码可以最大限度地被复用。这个过程实际上是将设计师引导到模版方法模式上去。

参考资料:《C# 设计模式》 。

 

posted @ 2010-11-28 16:15  天津城建学院软件工程  阅读(683)  评论(2编辑  收藏  举报