第一次设计
下面,我们的学习将从咖啡和茶的制作上开始进行。
泡咖啡的步骤:
- 把水煮沸
- 用沸水冲泡咖啡
- 把咖啡倒入杯子
- 加糖和牛奶
泡茶的步骤:
- 把水煮沸
- 用沸水侵泡茶叶
- 把茶倒入杯子
- 加柠檬
下面,用代码来实现上面的步骤:
咖啡的实现:
public class Coffee
{
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");
}
}
茶的实现:
public class Tea
{
void prepareRecipe()
{
boilWater();
brewTeaBag();
addLemon();
pourInCup();
}
public void boilWater()
{
Console.WriteLine("Boiling water");
}
public void brewTeaBag()
{
Console.WriteLine("Steeping the tea");
}
public void pourInCup()
{
Console.WriteLine("pouring into cup");
}
public void addLemon()
{
Console.WriteLine("Adding Lemon");
}
}
改进
从上面的代码可以发现,有两个步骤我们是重复了的,我们可以把重复的部分抽取出来,放入到一个基类中。
把boilWater()、pourInCup()提取出来放入基类,把prepareRecipe()定义成抽象方法。但这样定义,对于prepareRecipe()仍然需要实现两次,我们将boilWater()、pourInCup()泛化,使用brew()和addCondiments()来替代,并且在基类中,将其定义成抽象方法。这样prepareRecipe()不在抽象,而是在基类中实现。
基类:
public abstract class CaffeineBeverage
{
public void prepareRecipe()
{
boilWater();
brew();
addCondiments();
pourInCup();
}
public void boilWater()
{
Console.WriteLine("Boiling water");
}
public void pourInCup()
{
Console.WriteLine("pouring into cup");
}
public abstract void brew();
public abstract void addCondiments();
}
下面看一幅图,来看看我们现在都做了些什么:
什么是模板方法
模板方法定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现
在上面的代码中,我们模板方法就是prepareRecipe(),原因:
- 它是一个方法
- 它用作一个算法模板,在这个例子中,算法是用来制作咖啡因饮料
- 在这里模板中,算法内的每一个步骤都被一个方法代表了。
定义模板方法模式
模板方法模式:在一个方法定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤
这个抽象类包含了模板方法。
primitiveOperation1()、primitiveOperation2()模板方法所用到的操作的抽象版本,模板方法本身和这两个操作的具体实现之间被解耦。
一个模板方法中可能许多具体类,这个具体类实现了抽象类的操作。
钩子(对模板方法进行挂钩)
钩子是一种被声明在抽象类中的方法,但只有空的或者默认的实现。钩子的存在,可以让子类有能力对算法的不同点进行挂钩。要不要挂钩,由子类决定。
下面来看看代码怎么写:
public abstract class CaffeineBeverageWithHook
{
void prepareRecipe()
{
boilWater();
brew();
poueInCup();
if (customerWantsCondiments())
{
addCondiments();
}
}
public abstract void brew();
public abstract void addCondiments();
void boilWater()
{
Console.WriteLine("Boiling water");
}
void poueInCup()
{
Console.WriteLine("Pouring into cup");
}
bool customerWantsCondiments()//这就是一个钩子,子类可以覆盖(这个方法通常是空的实现)
{
return true;
}
}
使用钩子
让我们看看它在实际代码里面的应用
子类继承:
public class CoffeeWithHook : CaffeineBeverageWithHook
{
public override void addCondiments()
{
Console.WriteLine("Adding Sugar through filter");
}
public override void brew()
{
Console.WriteLine("Dripping Coffee through filter");
}
public override bool customerWantsCondiments()
{
Console.WriteLine("Adding Sugar through filter?Y/N");
string s = Console.ReadLine();
if (s=="y")
{
return true;
}
return false;
}
}
测试:
static void Main(string[] args)
{
CoffeeWithHook coffeewithhool=new CoffeeWithHook();
coffeewithhool.prepareRecipe();
Console.ReadKey();
}
关于钩子
什么时候使用钩子
当算法的某个实现可选的时候,可以使用钩子。当算法的某个实现是必须的时候,使用抽象方法
钩子的目的
钩子可以让子类有能力为其基类做一些决定。
好莱坞原则
好莱坞原则:别调用我们,我们会调用你。
好莱坞原则防止依赖腐败:当高层组件依赖底层组件,而底层组件又依赖高层组件,而高层组件又依赖边测组件,边侧组件又依赖高层组件,这样依赖腐败就发生了。
在好莱坞原则下,我们允许底层组件将自己挂钩到系统上,但高层组件会决定什么时候调用这些底层组件。
好莱坞原则应用
我们在之前设计模板方法的时候,其实就用到了好莱坞原则:
在上面图中,CaffeineBeverage就是我们的高层组件,它能够控制冲泡方法的算法,只有在需要子类实现某个方法是才调用,饮料的客户代码只依赖CaffeineBeverage的抽象,而不依赖具体的类。
好莱坞原则和依赖倒置原则
在这里我们看到好莱坞原则感觉和依赖倒置原则很像,都是用于解耦。
依赖倒置原则让我们尽量避免使用具体类,而多使用抽象,它更加注重与在设计中避免依赖。
好莱坞原则则是一种用在创建框架或组件上的一种技巧,好让底层组件能够被挂钩计算,而又不会让高层组件依赖底层组件,它是创建一个有弹性的设计,允许底层结构能够互相操作,而又防止太过于依赖。
使用模板方法来排序
使鸭子类继承至IComparable接口,之后使用sort方法进行排序
public class Duck:IComparable
{
private string name;
private int weight;
public Duck(string name, int weight)
{
this.name = name;
this.weight = weight;
}
public string toString()
{
return name + "weighs" + 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;
}
}
}
排序
static void Main(string[] args)
{
List<Duck> ducks=new List<Duck>{new Duck("11",11),new Duck("2",2),new Duck("13",13),new Duck("7",7)};
DisPlay(ducks);
ducks.Sort();
DisPlay(ducks);
Console.ReadKey();
}
在上面的例子中,我们可以看到子类提供的实现排序方法的算法。