模版方法模式

一、   基本概述

下面列出咖啡、茶的冲泡方法。

1.咖啡冲泡方法

(1)  把水煮沸

(2)  用沸水冲泡咖啡

(3)  把咖啡倒进杯子

(4)  家牛奶和糖

2.茶的冲泡方法

(1)  把水煮沸

(2)  用沸水浸泡茶叶

(3)  把茶倒进杯子

(4)  加柠檬

在使用代码来完成这些方法时,我们一般想到的创建2个类(咖啡、茶类)来单独实现这四个步骤。或者更好点是创建一个饮料父类来共享第一步与第三步,然后继承父类实现各自饮料的第二步与第四步。那么还有更好一点的方式来处理上面的问题吗?有,可以使用模版方法模式。

 

二、详细说明

1.模版方法模式:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模版方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

这个模式是用来创建一个算法的模版。什么是模版?如你所见,模版就是一个方法。更具体地说,这个方法将算法定义成一组步骤,其中的任何步骤都可以是抽象的,有子类负责实现。这可以确保算法的结构保持不变,同时由子类提供部分实现。

 

 

 

钩子是一种被声明在抽象类中的方法,但只有空的或者默认的实现,钩子的存在,可以让子类有能力对算法的不同点进行挂钩。要不要挂钩,由子类自行决定。

 

问:当我创建一个模版方法时,怎么才能知道什么时候该使用抽象方法,什么时候使用钩子呢?

答:当你的子类“必须”提供算法中某个方法或步骤的实现时,就使用抽象方法。如果算法的这个部分是可选的,就用钩子。如果是钩子的话,子类可以选择实现这个钩子,但并不强制这么做。

问:使用钩子真正的目的是什么?

答:钩子有几种用法。钩子可以让子类实现算法中可选的部分,或者在钩子对于子类的实现并不重要的时候,子类可以对此钩子置之不理。钩子的另一个用法,是让子类能够有机会对模版方法中某些即将发生的步骤作出反应。钩子也可以让子类有能力为其抽象类作一些决定。

  如在Asp.net MVC框架中,你创建一个控制器,默认都是继承Controller类,而Controller类中就有钩子,如OnActionExecuted、OnActionExecuting、OnResultExecuted、OnResultExecuting等,这些钩子能够在你的控制器中进行实现,以便控制或加工对请求的动作与返回的执行。

 

2.设计原则:好莱坞原则:别调用(打电话给)我们,我们会调用(打电话给)你。

    好莱坞原则可以给我们一种防止“依赖腐败”的方法。当高层组件依赖底层组件,而底层组件又依赖高层组件,而高层组件又依赖边侧组件,而边侧组件有依赖底层组件时,依赖腐败就发生了。在这种情况下,没有人可以轻易地搞懂系统是如何设计的。

    在好莱坞原则之下,我们允许底层组件将自己挂钩到系统上,但是高层组件会决定什么时候和怎样使用这些底层组件。换句话说,高层组件对待底层组件的方式是“别调用我们,我们会调用你”。

    好莱坞原则和模版方法之间的连接其实还算明显:当我们设计模版方法模式时,我们告诉子类,“不要调用我们,我们会调用你”。

 

问:底层组件不可以调用高层组件中的方法吗?

答:并不尽然,事实上,底层组件在结束时,常常会调用从超类中继承来的方法。我们所要做的是,避免让高层和底层组件之间有明显的环状依赖。

 

  模版方法模式是一个很常见的模式,到处都是。尽管如此,你必须拥有一双锐利的眼镜,因为模版方法有许多实现,而它们看起来并不一定和书上所说的设计一致。

  这个模式很常见是因为对创建框架来说,这个模式好用。有框架控制如何做事情,而由你(使用这个框架的人)指定框架算法中每个步骤的细节。

如在C#数组中,有Array类提供一个Sort()方法用来排序,该方法有2个参数(一个是数组,一个是IComparer 接口),Sort方法提供了排序算法,实现IComparer 接口的参数提供了数组元素怎么进行比较大小。

 

问:这真的是一个模版方法模式吗?

答:我们都知道,荒野中的模式并非总是如同教科书例子一般地中规中矩,为了符合当前的环境和实现的约束,它们总是要被适当地修改。这个Array类的Sort()方法的设计者受到一些约束,通常我们无法设计一个类继承C#数组,而Sort()方法希望能够适用于所有的数组(每个数组都是不同的类)。所以它们定义了一个静态方法,而由被排序的对象内的每个元素自行提供比较大小的算法部分。所以,这虽然不是教科书上的模版方法,但它的实现仍然符合模版方法模式的精神。再者,由于不需要继承数组可以使用这个算法,这样使得排序变得更有弹性、更有用。

4.总结:

1.好莱坞原则告诉我们,将决策权放在高层模块中,以便决定如何以及何时调用底层模块。

2.你将咋真实世界代码中看到模版方法模式的许多变体,不要期待它们全都是一眼就可以被你认出的。

3.策略模式和模版方法模式都封装算法,一个用组合,一个用继承。

4.工厂方法是模版方法的一种特殊版本。

三、代码列表

public abstract class CaffeineBeverage
{
    public void PrepareRecipe()
    {
        BoilWater();
        Brew();
        PourInCup();
        AddCondiments();
        Hook();
    }

    protected abstract void Brew();

    protected abstract void AddCondiments();

    private void BoilWater()
    {
        Console.WriteLine("Boiling water");
    }

    private void PourInCup()
    {
        Console.WriteLine("Pouring into cup");
    }

    protected virtual void Hook()
    {
        
    }
}
public class Coffee:CaffeineBeverage
{
    protected override void Brew()
    {
        Console.WriteLine("冲泡咖啡");
    }

    protected override void AddCondiments()
    {
    }
    
}
public class Tea : CaffeineBeverage
{
    protected override void Brew()
    {
        Console.WriteLine("浸泡茶");
    }

    protected override void AddCondiments()
    {
        Console.WriteLine("加柠檬");
    }
}
View Code

---------以上内容根据《Head First Design Mode》进行整理

posted @ 2017-06-13 20:34  殇曲?  阅读(335)  评论(0编辑  收藏  举报