有些人没有咖啡就活不下去,有些人则离不开茶。两者共同的成分是什么?当然是咖啡因了!但还不只这样,茶和咖啡的冲泡方式非常相似:
一、咖啡冲泡法
    1.把水煮沸
    2.用沸水冲泡咖啡
    3.把咖啡倒进杯子
    3.加糖和牛奶
二、茶冲泡法
    1.把水煮沸
    2.用沸水浸跑茶叶
    3.把茶倒进杯子
    4.加柠檬
让我们写一些代码来快速搞定咖啡和茶的类

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");
	}
}
public class Tea {
	//茶冲泡法跟咖啡类似,但2、4这两个步骤不一样,是泡茶专有的
	void prepareRecipe() {
		boilWater();
		steepTeaBag();
		pourInCup();
		addLemon();
	}
	public void boilWater() {
		System.out.println("Boiling water");
	}
	public void steepTeaBag() {
		System.out.println("Steeping the tea");
	}
	public void addLemon() {
		System.out.println("Adding Lemon");
	}
	public void pourInCup() {
		System.out.println("Pouring into cup");
	}
}

注意,泡茶里面的第1、3和冲咖啡方法是一样的,也就是说这里有重复的代码,所以,咖啡和茶还有什么其他的共同点呢?
注意两份冲泡法都采用了相同的算法:
   1.把水煮沸
   2.用热水泡咖啡或茶
   3.把饮料倒进杯子
   4.在饮料内加入适当的调料
那么我们有办法将prepareRecipe()也抽象化吗?我们来看看。
第一个问题:咖啡使用brewCoffeeGrinds()和addSugarAndMilk()方法,而茶使用steepTeaBag()和addLemon()方法。浸泡(steep)和冲泡(brew)差异其实不大,所以我们给它新的方法名称brew(),类似加糖和加柠檬很相似,都是在饮料中加入调料,我们也给个新名称addCondiments()好了,这样一来,prepareRecipe()方法就是这样:

void prepareRecipe() {
		boilWater();
		brew();
		pourInCup();
		addCondiments();
}

第二步,我们有了新的prepareRecipe方法,但是需要让她能够符合代码,我们先从CaffeineBeverage(咖啡因饮料)超类开始:

public abstract class CaffeineBeverage {//抽象类
	//被声明为final,我们不希望子类覆盖这个方法
	final void prepareRecipe() {
		boilWater();
		brew();
		pourInCup();
		addCondiments();
	}
	//因咖啡和茶处理这些方法的做法不同,所以这2个方法必须被声明为抽象的,剩余的给子类去操心
	abstract void brew();
	abstract void addCondiments();
	//这2个方法不变,不需要子类去处理
	void boilWater() {
		System.out.println("Boiling water");
	}
	void pourInCup() {
		System.out.println("Pouring into cup");
	}
}

最后,我们需要处理咖啡和茶类,这2个类现在都是依赖超类CaffeineBeverage来处理冲泡法,所以只需要自行处理冲泡和添加调料部分:

public class Tea extends CaffeineBeverage {
	public void brew() {
		System.out.println("Steeping the tea");
	}
	public void addCondiments() {
		System.out.println("Adding Lemon");
	}
}
public class Coffee extends CaffeineBeverage {
	public void brew() {
		System.out.println("Dripping Coffee through filter");
	}
	public void addCondiments() {
		System.out.println("Adding Sugar and Milk");
	}
}

回过头去看看我们刚才做了些什么。基本上,我们刚刚实现的就是模板方法模式,模板方法定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现。
模板方法模式在一个方法中定义了一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
钩子是一种被声明在抽象类中的方法,但只有空的或者默认的实现。钩子的存在,可以让子类有能力对算法的不同点进行挂钩,要不要挂钩,由子类自行决定。我们先来看看钩子的用途:

public abstract class CaffeineBeverageWithHook {
	void prepareRecipe() {
		boilWater();
		brew();
		pourInCup();
		//我们加了一个小小的条件语句,而该条件是否成立,是由一个具体方法//customerWantsCondiments决定的。如果顾客“想要”调料时我们才调用addCondiments
		if (customerWantsCondiments()) {
			addCondiments();
		}
	}
	abstract void brew();
	abstract void addCondiments();
	void boilWater() {System.out.println("Boiling water");}
	void pourInCup() {System.out.println("Pouring into cup");}
	//这里定义了一个方法,通常是空的缺省实现。
	//这个就是一个钩子,子类可以覆盖这个方法,但不见得一定要这么做
	boolean customerWantsCondiments() {
		return true;
	}
}
//我们如何得知顾客是否想要调料呢?开口问呀。。。
public class CoffeeWithHook extends CaffeineBeverageWithHook {
	public void brew() {
		System.out.println("Dripping Coffee through filter");
	}
	public void addCondiments() {
		System.out.println("Adding Sugar and Milk");
	}
	//我们覆盖了这个钩子,提供自己的功能
	public boolean customerWantsCondiments() {
		//让用户输入他们对调料的决定,返回true或false
		String answer = getUserInput();
		if (answer.toLowerCase().startsWith("y")) {
			return true;
		} else {
			return false;
		}
	}
	//这个方法询问用户是否想要奶和糖,通过命令行获得用户输入
	private String getUserInput() {
		String answer = null;
		System.out.print("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 ioe) {
			System.err.println("IO error trying to read your answer");
		}
		if (answer == null) {
			return "no";
		}
		return answer;
	}
}
//好了,我们来测试一下
public class BeverageTestDrive {
	public static void main(String[] args) {
		//创建一杯咖啡
		CoffeeWithHook coffeeHook = new CoffeeWithHook();
		//然后调用prepareRecipe
		System.out.println("\nMaking coffee...");
		coffeeHook.prepareRecipe();
	}
}

我们有一个新的设计原则,称为好莱坞原则:
       别调用我们,我们会调用你。
我们允许低层组件将自己挂钩到系统上,但是高层组件会决定什么时候和怎么使用这些低层组件。换句话说。高层组件对待低层组件的方式是“别调用我们,我们会调用你” 

 

评论
发表评论

您还没有登录,请登录后发表评论

blank
搜索本博客
我的相册
5c2c51d9-1a15-3713-8084-e1742c808801-thumb
P1080256
共 30 张
存档
最新评论