模版方法模式
1、定义 在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,模版方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
具体的如何理解这定义呢?
比如我们煮咖啡与冲泡茶,两者的过程大致是相同的。但是里面的具体内容有细微的差别。其类如下:
//本节主要讲模版模式。
class Coffee
{
public void prepareRecipe()
{
boilWater();
brewCoffeeGrinds();
pourInCup();
addSugarAndMilk();
}
public void boilWater()
{
Console.WriteLine("Boiling water");
}
public void brewCoffeeGrinds()
{
Console.WriteLine("Dripping Coffee through filter");
}
public void pourInCup()
{
Console.WriteLine("Pouring into cup");
}
public void addSugarAndMilk()
{
Console.WriteLine("Adding Sugar and Milk");
}
}
class Tea
{
public void boilingWater()
{
Console.WriteLine("Boiling Water");
}
public void steepTeaBag()
{
Console.WriteLine("Steeping the tea");
}
public void addLemon()
{
Console.WriteLine("Adding Lemon");
}
public void pourInCup()
{
Console.WriteLine("Pouring into cup");
}
}
算法均为:
1)把水煮沸
2)用热水泡咖啡或茶
3)把饮料倒进杯子
4)在饮料内加入适当的调料
UML图为:
下面我们将抽象这个算法过程。其实模板方法的精髓就在于此,将算法的框架确定出来,但是对于要在每个步骤里面添加什么由具体的子类来确定。容易让我们与策略模式相混淆。策略模式就行为以接口的方式呈现给子类。有更多的可操作性,但是对程序员也就有更多的要求。
//从上面的代码中我们可以看到很多的代码冗余
//因此我们可以将公共 部分给提取出来
//或许我们会想到这样的方法
abstract class CaffeineBeverage
{
public void prepareRecipe()
{
boilingWater();
brew();
pourInCup();
addCondiments();
}
public abstract void brew();
public abstract void addCondiments();
public void boilingWater()
{
Console.WriteLine("Boiling water");
}
public void pourInCup()
{
Console.WriteLine("Pouring into cup");
}
}
//对原来的类进行继承
class ExtendTea1 : CaffeineBeverage
{
public override void brew()
{
Console.WriteLine("Steeping the tea");
}
public override void addCondiments()
{
Console.WriteLine("Adding Lemon");
}
}
class ExtendCodffee1 : CaffeineBeverage
{
public override void brew()
{
Console.WriteLine("Dripping Coffee through filter");
}
public override void addCondiments()
{
Console.WriteLine("Adding Suger and Milk");
}
}
使得一些步骤依赖于子类进行。也就是允许子类为一个或多个步骤提供实现。但是我们不得不说这种方式有点限制了整个算法。于是钩子出现了。所谓钩子就是一种被声明在抽象类中的方法,但是只有空的或默认的实现。钩子可以让子类有能力对算法的不同点进行挂钩。根据钩子做更多的判断和动作。如下:
abstract class CaffeeineBeverageWithHook
{
public void prepareRecipe()
{
boilingWater();
brew();
pourInCup();
if (customerWantsCondiments())//利用钩子来判断子类的执行方式
{
addCondiments();
}
}
public abstract void brew();
public abstract void addCondiments();
public void boilingWater()
{
Console.WriteLine("Boiling water");
}
public void pourInCup()
{
Console.WriteLine("Pouring into cup");
}
//这个函数为钩子函数,也就是说子类可以覆盖这个方法,子类也可以采取不处理的方式。这个钩子函数就为子类能够进行拓展
public bool customerWantsCondiments()
{
return true;
}
}
class CoffeeWithHook : CaffeeineBeverageWithHook
{
public override void brew()
{
Console.WriteLine("Dripping Coffee through filter");
}
public override void addCondiments()
{
Console.WriteLine("Adding Sugar and Milk");
}
public bool customerWantsCondiments()
{
String answer = getUserInput();
if (answer.ToLower().StartsWith("y"))
{
return true;
}
else
{
return false;
}
}
public String getUserInput()
{
String answer = null;
Console.WriteLine("Would you like milk and sugar with your coffee (y/n) ?");
answer= Console.ReadLine();
if (answer == null)
{
return "no";
}
return answer;
}
}
运行结果为:
从钩子中我们学到一个新的知识点是:不要调用我,我会调用你。降低依赖程度。只有在需要调用具体子类中的某个方法的时候才会执行子类中的相关动作。你比如brew这个函数。在boilWater时,其实还是父类的动作。着就是好莱坞原则。
下面讲述另一个例子。比如在很多的集合中,需要对集合中的元素进行排序,排序就需要比较之间的键值。其实在C#内部就已经有这个比较的接口,我们只要实现这接口,就能够对数组内部进行自定义的排序。
class Duck :IComparable
{
public String name;
public int weight;
public Duck(String name,int weight)
{
this.name = name;
this.weight = weight;
}
public int CompareTo(Object obj)
{
Duck otherDuck = (Duck)obj;
if (this.weight < otherDuck.weight)
{
return -1;
}
else if (this.weight == otherDuck.weight)
{
return 0;
}
else
{
return 1;
}
}
}
class DuckSortTestDriver
{
public static void test()
{
Duck[] ducks =
{
new Duck("Daffy",8),
new Duck("Dewey",2),
new Duck("Roward",7),
new Duck("Lovie",2),
new Duck("Donald",10),
new Duck("Huey",2)
};
Console.WriteLine("\nBefore sorting");
display(ducks);
Array.Sort(ducks);
Console.WriteLine("\nAfter sorting");
display(ducks);
}
public static void display(Duck[] ducks)
{
for (int i = 0; i < ducks.Length; i++)
{
Console.WriteLine("name:"+ducks[i].name+" weigh is:"+ducks[i].weight);
}
}
}
运行结果为:
小结:
模板方法的子类的变化起始不大,只有在相对的部分,在子类中进行重写。它主要为我们提供一种代码复用的技巧。决策权主要放在高层中。那么策略模式则是封装可互换的行为,然后使用委托来决定要采用哪一个行为。工厂方法由子类实例化那个具体类。