从一般开始
星巴霖饮料店今天开业了!!!小店初开,提供咖啡和茶两种饮品,这是我们的独家配方法,
下面是星巴霖咖啡的冲泡法:
- 把水煮沸;
- 用沸水冲泡咖啡;
- 把咖啡倒进杯子;
- 加糖和牛奶。
然后是星巴霖茶的冲泡法:
- 把水煮沸;
- 用沸水浸泡茶叶;
- 把茶倒进杯子;
- 加柠檬。
下面让我们来实现咖啡和茶,代码见目录SimpleBarista
。
进一步复用
代码很简单,观察后发现boilWater()
和pourInCup()
是相同的,我们可以将其抽象出来,见类图:
我们已经对茶和咖啡进行了一些抽象了,还有没有其它的东西可以抽象呢?回顾它们的冲泡方法:
- 把水煮沸;
- 用热水包咖啡或茶;
- 把饮料倒进杯子;
- 在饮料中加入适当的调料;
那么,现在可以尝试将prepareRecipe()
抽象化:
咖啡的prepareRecipe()
:
1 | void Coffee::prepareRecipe() { |
茶的prepareRecipe()
:
1 | void Tea::prepareRecipe() { |
两者的第2步操作,冲泡和浸泡,差异其实不大,将第2步抽象出来,比如说brew()
。加糖和牛奶与加柠檬本质上都属于加调料,可以抽象成addCondiments()
。那么,我们可以将prepareRecipe()
方法修改,现在,新的prepareRecipe()
方法如下:
1 | void prepareRecipe() { |
这样一来,prepareRecipe()
就成了一个模板方法,因为:
- 它是一个方法;
- 它是一个模板,因为它的算法是固定的,但是具体的实现是不固定的,比如说,咖啡和茶的冲泡方法就不一样。
代码见目录Barista
。
模板方法模式
模板方法模式在一个方法中定义了一个算法的骨架,而将一些步骤延迟的子类中。模板方法是的子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤;
模板方法的主要在于需要进行的抽象。
1 | class AbstractClass { //这是一个抽象类,被用作基类,子类需要实现其操作 |
钩子
钩子是一种被声明在基类中的方法,但只有空的或者默认的实现。钩子的存在,可以让子类有能力对算法的不同点进行挂钩。要不要挂钩,由子类自行决定。
当顾客点咖啡的时候,我们如何得知顾客是否想要加调料呢?
答案就是,开口问!
1 | class CaffeineBeverageWithHook { |
设计原则
好莱坞原则
别调用我们,我们会调用你。这是一种反转控制,也就是说,高层组件对待底层组件的方式是“别调用我们,我们会调用你”。
策略模式与模板方法模式
策略模式和模板方法模式都封装算法,一个用组合,一个用继承。两者的区别在于:
- 策略模式:封装可互换的行为,然后使用委托来决定使用哪一个行为;
- 模板方法模式:封装算法的骨架,而将一些步骤延迟到子类中;
可以延迟到子类中实现的步骤,视为可互换的行为(比如brew()
),这样就又可以采用策略模式对不同的步骤进行封装,便于灵活替换;
完整示例代码详见:代码链接