9 模板方法模式
模板方法模式(Template Mothod):在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
UML类图如下:
要点有两个:原语操作PrimitiveOperation以及钩子Hook()方法。
代码举例
1 public abstract class CaffeineBeverage 2 { 3 public void PrepareRecipe() 4 { 5 BoilWater(); 6 Brew(); 7 PourInCup(); 8 AddCondiments(); 9 TakeOut(); 10 } 11 public void BoilWater() 12 { 13 Console.WriteLine("Boil Water"); 14 } 15 public abstract void Brew(); 16 public void PourInCup() 17 { 18 Console.WriteLine("Pouring into Cup"); 19 } 20 public abstract void AddCondiments(); 21 //打包 22 public virtual void TakeOut(){} 23 } 24 25 class Coffee : CaffeineBeverage 26 { 27 public override void Brew() 28 { 29 Console.WriteLine("Brew Coffee Grinds"); 30 } 31 32 public override void AddCondiments() 33 { 34 Console.WriteLine("Add Sugar and Milk"); 35 } 36 public override void TakeOut() 37 { 38 Console.WriteLine("Will you take out? (y/n)"); 39 string yn = Console.ReadLine(); 40 if (yn == "y") Console.WriteLine("Take out"); 41 } 42 } 43 44 class Tea : CaffeineBeverage 45 { 46 public override void Brew() 47 { 48 Console.WriteLine("Steeping the Tea"); 49 } 50 51 public override void AddCondiments() 52 { 53 Console.WriteLine("Adding Lemon"); 54 } 55 }
这里Brew()与AddCondiments()便是两个原语操作,要在子类中重写。因为冲泡茶叶和咖啡都有类似的步骤,煮沸水BoilWater()->冲泡Brew()->倒入杯中PourInCup()->加辅料AddCondiments(),这种不变的步骤便是模板方法定义中所说的“算法步骤”,步骤或算法不能改变,所以PrepareRecipe()方法无法被重写,但在Brew()和AddCondiments()阶段,咖啡和茶叶的具体操作又有不同,比如AddCondiments(),咖啡可能加的是牛奶,而茶叶加的可能是柠檬,所以算法中这两个方法要被具体的子类实现,进行针对性的操作。
另外,这儿还有个比较特殊的Hook钩子,钩子不是抽象方法,而是虚方法,它有自己的实现,但如果子类需要覆盖,也可以重写。这儿的例子中,用是否打包带走来举例,咖啡提供打包的服务,所以就重写了TakeOut()方法,但茶不允许外带,那么就留着父类中的TakeOut()空实现就好了。由此可见,钩子真是一个形象的名称。
那么,在模板方法模式中,如果算法的一个步骤必须被子类亲自实现,则设置为原语操作(PrimitiveOperation),c#可把这种方法设置为abstract抽象方法;如果并不是强制子类重写的步骤,则用钩子,c#可用virtual虚方法实现。
好莱坞原则
别调用我们,我们会调用你。
这个原则可以保证单向的依赖,避免出现高层组件与底层组件循环依赖的糟糕情况。
遵循好莱坞原则,可以让底层组件挂钩到高层系统上,高层组件会决定什么时候和怎样使用它们。
这和依赖倒置原则非常类似,但依赖倒置原则强调的是尽量避免使用具体类,而多使用抽象类;好莱坞原则更多是在教我们一个创建弹性设计的技巧,让底层组件能被挂钩进计算框架中,而且又不会让高层组件依赖低层组件。
与策略模式、工厂方法的比较
模板方法模式由子类决定如何实现算法中的步骤;策略模式封装可互换的行为,然后使用委托来决定要采用哪一个行为;工厂方法由子类决定实例化哪个具体类。
模板方法模式用继承进行算法实现,策略模式用了组合的方式;策略模式可以在运行时改变算法,但模板方法模式无法做到这一点。
最后,工厂方法是模板方法的一种特殊版本。