设计模式学习(九)——模板方法模式

参考书——《HeadFirst设计模式》

和书中一样,用冲咖啡和沏茶来说这个设计模式


       冲咖啡:                     沏茶:                                    制作饮料:

step1       烧水                       烧水                                       烧水

step2      用沸水冲咖啡粉         用沸水冲茶叶      ===========》               用沸水冲

step3     倒入杯中               倒入杯中                                      倒入杯中

step4     加糖加奶               加柠檬                                        加适当的调料


我们看到step1和step3是一样的,换而言之,可以具体实现,step2和step4虽然不一样但是我们可以进行提取,也就是抽象。等等,想到了什么?!抽象类   P.s:抽象类和接口的区别http://dev.yesky.com/436/7581936.shtml 

public abstract class Beverage {
	
	final void prepareRecipe() {
		boilwater();
		brew();
		pourIncup();
		addCondiments();
	}
	
	abstract void brew();
	
	abstract void addCondiments();
	
	void boilwater() {
		System.out.println("烧水");
	}
	
    void pourIncup() {
    	System.out.println("倒入杯中");
	}
}


模板方法——就是这里的prepareRecipe(),它定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现。

“炫酷的地方”:1)Beverage类主导一切,它拥有这个算法,并且保护这个算法;

                2)对子类来说,Beverage类的存在,可以提高代码的复用;

                3)算法存在一个地方,便于维护;

                4)模板方法提供了一个框架,可以让其他的饮料插进来,新的饮料只要实现自己的方法就行;

                5)Beverage类专注在算法本身,而由子类提供完整的实现。


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


    其实,模板就是一个方法。更具体地说,这个方法将算法定义成一组步骤,其中任何步骤都可以是抽象的,由子类负责实现,这可以确保算法的结构保持不变,同时由子类提供部分实现。模板方法本身和这些抽象的原语操作的具体实现之间解耦了。


 有了这个抽象类,我们就可以写具体的Coffee和Tea类了,代码如下:

public class Coffee extends Beverage {

	void brew() {
		System.out.println("用沸水冲咖啡");
    }
    
	void addCondiments() {
		System.out.println("加糖加奶");
    }

}

到这里,发现问题了,冲杯Coffee,前三个步骤是必须的,但是添加调料就不是必须的了,有的人不喜欢给咖啡里加糖加奶,怎么办?


    基于这个问题我们引出"钩子"方法

    在上面的链接的这篇文章里,有一段话“在abstract class的定义中,我们可以赋予方法的默认行为。

    对此我的理解:给一个方法赋予默认行为,这个方法是子类可以重写也可以不重写,子类可以通过重写来提升方法的性能,这样的方法就可以成为“钩子”方法。有人会说,普通的方法(没有被final修饰)也可以做到可以重写也可以不重写啊。难道都是“钩子”方法?!

    在别人的博客看到这样的话:“继承包含这样一层含义:父类中凡是已经实现好的方法(相对于抽象方法而言),实际上是在设定一系列的规范和契约,虽然它不强制要求所有的子类必须遵从这些契约,但是如果子类对这些非抽象方法任意修改,就会对整个继承体系造成破坏

    所以,我觉得“钩子”方法和普通方法,其实是个度的问题,普通方法,就如上面这段话的红色字所说,它有它自己规定的契约,不太希望被改变。而“钩子”方法是规定了默认行为(这个默认行为甚至可以为空,“钩子”方法什么都不做),最为宽泛的,子类可以在其上进行规定。


接下来,我们看看如何用"钩子"方法解决上面的问题——有的人不喜欢给咖啡里加糖加奶,怎么办?

先来看Beverage类:

public abstract class Beverage {
	
	final void prepareRecipe() {
		boilwater();
		brew();
		pourIncup();
		if(customerWantsCondiments()){
			addCondiments();
		}
	}
	
	abstract void brew();
	
	abstract void addCondiments();
	
	void boilwater() {
		System.out.println("烧水");
	}
	
    void pourIncup() {
    	System.out.println("倒入杯中");
	}
    
    boolean customerWantsCondiments() {
    	return true;
    }
}

这个customerWantsCondiments()就是一个“钩子”方法,只会返回true,不做别的事,子类可以覆盖也可以不覆盖。


再来看Coffee类:


public class Coffee extends Beverage {

	void brew() {
		System.out.println("用沸水冲咖啡");
    }
    
	void addCondiments() {
		System.out.println("加糖加奶");
    }
	
	boolean customerWantsCondiments() {
    	String answer = getUserInput();
    	if(answer.toLowerCase().startsWith("y")){
    		return true;
    	}else{
    		return false;
    	}
    }
	
	public String getUserInput() {
		String answer = null;
		System.out.println("需要加糖加奶么?(y/n)");
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
		try{
			answer = in.readLine();
		}catch(IOException e){
			System.out.println("抱歉,出错了!");
		}
		if(answer==null){
			answer = "no";
    	}
		return answer;
	}
}

问题解决了!“钩子”方法竟然能够作为条件控制,影响抽象类中的算法流程,实在不赖吧 微笑

这里我们总结下“钩子”方法的几个作用:

1)可以让子类实现算法中可选的部分;

2)在“钩子”方法对子类的实现并不重要的时候,子类可以对它置之不理;

3)让子类能够有机会对模板方法中即将发生的(or刚刚发生的)步骤做出反应;

4)也可以让子类有能力为抽象类做一些决定(上面的代码用的就是这个)。


P.S:

好莱坞原则:别调用我们,我们会调用你。

在好莱坞原则下,我们允许低层组件将自己挂钩到系统上,但是高层组件会决定什么时候和怎样使用这些低层组件。也就是说高层组件对低层组件采用了好莱坞原则。


好莱坞原则和模板方法模式

还是用我们上述的例子来说。

Beverage是我们的高层组件,它能够控制饮料的冲泡算法,只有在需要子类实现某个方法时,才调用子类。

饮料的客户代码只依赖Beverage抽象,而不依赖具体的Tea或Coffee,这可以减少整个系统的依赖。









  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值