具体的定义:
模板方法模式在一个方法中定义了一个算法的骨架,而将一些步骤延迟到子类之中,模板方法使得子类可以不再改变算法的情况下,重新定义算法中的某些步骤。这个模式是用来创建一个算法的模板,模板具体说来可以看作是一种方法,更具体的说是我们定义了一组步骤,其中人和步骤都可以是抽象的,由子类负责实现。这个可以确保算法的结构保持不变,同时由子类提供部分实现。
有些人喜欢喝咖啡,有些呢喜欢喝茶,那么这两种人除了表面上都喜欢喝饮品之外,他们更深层的原因在于其第比较依赖于咖啡因,也就是说,两个人喜欢的音频类别不同,但是饮品中的某些物质的相同。对于这个问题,我们从代码的角度来进行模拟:
生产Coffee的过程:
public class Coffee {
void prepareRecipe(){
//烧开热水
boilWater();
brewCoffeeGrinds();
//倒入杯子中
pourInCup();
//添加糖和牛那
addSugarAndMilk();
}
public void boilWater(){
System.out.println("Boiling water");
}
public void brewCoffeeGrinds(){
System.out.println("Dripping Coffee through filter");
}
public void pourInCup(){
System.out.println("Pouring into cup");
}
public void addSugarAndMilk(){
System.out.println("Adding Sugar and Milk");
}
}
生产tea的过程:
public class Tea {
void prepareRecipe(){
boilWater();
steepTeaBag();
pourInCup();
addLemon();
}
public void boilWater(){
System.out.println("Boiling water");
}
public void steepTeaBag(){
System.out.println("Stepping the tea");
}
public void addLemon(){
System.out.println("Adding Lemon");
}
public void pourInCup(){
System.out.println("Pouring into cup");
}
}
通过比较可以发现,两个类的方法中,某些方法是一样的,例如boilWater方法和pourInCup方法。这样,这种就成为了重复的代码,这表示,我们需要整理一下设计思路了,那么我们可以将其中相同的方法抽象出来,作为一个基类。例如我们可以设计为下面这样:
1.我们的总的咖啡店:
public abstract class CaffeIncBavarage {
final void prepareRecipe(){
boilWater();
brew();
pourInCup();
addCondiments();
}
abstract void brew();
abstract void addCondiments();
void boilWater(){
System.out.println("Boiling water");
}
void pourInCup(){
System.out.println("Pouring into cup");
}
}
再分析可以发现,采用相同方法的有:
1. 把水煮沸;
2. 用热水泡咖啡或者是茶
3. 把饮料倒进杯子
4. 在饮料内加入适当的调料
接下来开始抽象prepareReclpe方法,其实我们发现,浸泡和冲泡的差异并不是很大,所以我们给他们一个新的方法的名称,比如说,我们把它抽象为brew()方法,然后不管泡茶还是冲泡咖啡,我们都使用这个名名称,然后,加牛奶和加糖也很相似,都是向饮品中添加调料,所以,我们也可以再给他们一个方法,叫做addCondiments()。这样一来方法就变为一下的内容了,也就是我们设计的超类。
之后重新设计的子类中Coffee为:
public class CoffeeEx extends CaffeIncBavarage {
public void brew(){
System.out.println("Dripping Coffee through filter");
}
public void addCondiments(){
System.out.println("Adding Sugar and Milk");
}
}
设计的生产茶的子类为:
public class TeaEx extends CaffeIncBavarage{
public void brew(){
System.out.println("Steping the tea");
}
public void addCondiments(){
System.out.println("Adding Lemon");
}
}
写了这么多,可能有点迷糊,我们所呀使用的模式到底在哪里呢,其实是在超类里面的一个方法,它定义了一个算法的实现的过程,可能会部分实现某些功能。
写一个测试类测试一下:
public class ModelTest {
public static void main(String[] args) {
TeaEx tea = new TeaEx();
tea.prepareRecipe();
}
}
运行结果:
再主函数中声明了一个Tea的对象,这个对象是继承自超类的,之后,我们调用perpareRecipe ()方法,这样,就会使用模板方法控制算法,让它的子类提供某些方法的实现。其实这里倒是优点多态的味道,需要那个声明哪个对应的类,进行实例化。可以发现,我们只调用了perpareRecipe方法,但是其能够完整的给了我们一杯茶,那么来分析一下:
首先,开水煮沸是来自于超类的boilWater()方法,这件事情是在超类中实现的。
接下来,当我们泡茶的时候才知道要怎么做:寻找到子类的对应的brew()方法的Tea类的实现方法执行;
之后,和上面的boilWater()方法一样,由超类的方法来进行处理。最后,添加调料,这和brew方法的实现方式是一样的。
相比原来的设计,模板方法模式有哪些好处呢:
1. 主要的控制权由各个类转移到一个共同的超类,其拥有这个算法,而且也保护这个算法。两个子类中的重复代码也比较少。
2. 算法的修改,只修改一个地方就可以了,容易修改,同时,不再需要打开子类去修改很多的地方。
3. 其提供了一个类似于“框架”的模式,可以让其他的咖啡饮料插入进来,只需要自己实现方法就可以了。
4. 算法不在散落在各个类中,主要集中于超类,超类定义算法本身,而子类为完整的实现。
Hook()方法问题:
然而我们又做了一点改变:
final void prepareRecipe(){
boilWater();
brew();
pourInCup();
addCondiments();
hook();
}
而其定义为:
void hook(){}
那么他有什么用处呢?
此方法是一种被声明在抽象类中的方法,但是只有空的或默认的实现,此方法的存在可以让子类有能力对算法的不同点进行挂钩,要不要挂勾,这个是有算法决定的。
来分析第一种,使用案例,首先我们改变一下原来的超类:
public abstract class CoffeeIncBeverageWithHook {
void prepareRecipe(){
boilWater();
brew();
pourInCup();
if(customerWantsCondiments()){
addCondiments();
}
}
abstract void brew();
abstract void addCondiments();
void boilWater(){
System.out.println("Bolling water");
}
void pourInCup(){
System.out.println("Pouring into cup");
}
boolean customerWantsCondiments(){
return true;
}
}
之后,我们对Coffee类进行修改:
public class CoffeeWithHook extends CoffeeIncBeverageWithHook {
@Override
public void brew() {
System.out.println("Dripping coffee through filter");
}
@Override
public void addCondiments() {
System.out.println("Adding Sugar and Milk");
}
/***********************************hook()方法控制区域*********************************/
public boolean customerWantsCondiments(){
String answer = getUserInput();
if(answer.toLowerCase().startsWith("y")){
return true;
}else{
return false;
}
}
private String getUserInput(){
String answer = null;
System.out.println("Would you like milk and sugar with your coffee (y/n) ?");
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
try{
answer = in .readLine();
}catch(IOException e){
System.out.println("IO Error trying to read your answer");
}
if(answer == null){
return "no";
}
return answer;
}
}
执行测试程序:
public class CoffeeTestWithHooks {
public static void main(String[] args) {
CoffeeWithHook coffee = new CoffeeWithHook();
coffee.prepareRecipe();
TeaWithHook tea = new TeaWithHook();
tea.prepareRecipe();
}
}
测试结果:
测试成功,同时也可以体会到,我们新添加的这个hook方法能够影响抽象类算法的流程,具体的说,当我们的算法中的子类必须提供算法中某个方法的或者个某个步骤的实现时,就是用抽象方法,如果这个部分是可选的,就可以使用这种方式,子类可以实现这个方法,但不是一定要这么做,这个需结合具体的业务流程。
上述的hook这么几种用法:1.可以让子类实现算法中可选的部分,或者这部分对算法不重要的时候,可以不考虑;2.可以使得子类有机会能够对模板方法中某些即将发生的步骤作出反应。
还有,当我们使用模板方法模式的时候,一定要记得这一点,不要让算法的步骤太多,太多了实现起来很麻烦,太少了代码缺少弹性,主要是看情况折衷改一下。如果某些步骤是可选的,那么可以将其设置为hook()方法,这样可以让子类的压力减轻。
减少依赖过度(组件层面、框架层面)
当高层的组件依赖底层的组件,而底层的组件有依赖于高层的组件,而高层组件还依赖于边侧组件,边侧组件有依赖于底层组件时,依赖过度的现象便发生了,这种情况下很难搞懂程序是怎么设计的。换一种思路,我们可以将底层的组件挂钩到高层组件下面,高层组件将决定什么时候使用这些底层的组件,换句话说,将控制权交给高层组件。而依赖倒置则是教我们尽量避免使用具体类,而多使用抽象。并不是说一点不能有底层组件调用高层组件,而是需要避免让高层和底层组件之间有明显的环状依赖。