八、模板方法模式
1、概念
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
模板就是一个方法,更具体地说,这个方法将算法定义成一组步骤,其中的任何步骤都可以是抽象的,由子类负责。这可以保证算法的结构保持不变,同时由子类提供部分实现。
2、例子
将泡茶和冲咖啡的制作步骤通过模板方法模式进行改造
//抽象超类中定义了 模板方法
public abstract class CaffeineBeverage {
//模板方法中定义了算法
final void prepareRecipe(){
boilWater();
brew();
pourInCup();
//利用钩子实现根据客户要求是否添加调料
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 Tea extends CaffeineBeverage {
//实现超类中的两个抽象方法
@Override
void brew() {
System.out.println("Steeping the tea");
}
@Override
void addCondiments() {
System.out.println("Adding Lemon");
}
}
//子类:咖啡,其中覆盖掉了父类中的钩子方法
public class CoffeeWithHook extends CaffeineBeverage {
@Override
void brew() {
System.out.println("Dripping Coffee through filter");
}
@Override
void addCondiments() {
System.out.println("Adding Sugar and Milk");
}
//覆盖钩子实现自己的方法
@Override
boolean customerWantsCondiments() {
String ans = getUserInput();
if (ans.toLowerCase().startsWith("y")) {
return true;
} else {
return false;
}
}
//读取用户是否需要在咖啡中加入调料
private String getUserInput() {
String ans = null;
System.out.print("Would you like milk and suger with your coffee (y/n)? ");
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
try {
ans = in.readLine();
} catch (IOException e) {
e.printStackTrace();
}
if (ans == null) {
ans = "no";
}
return ans;
}
}
//测试类
public class CaffeineBeverageTest {
public static void main(String[] args) {
CaffeineBeverage tea = new Tea();
CaffeineBeverage coffeeWithHook = new CoffeeWithHook();
System.out.println("\nMaking tea.........");
tea.prepareRecipe();
System.out.println("\nMaking coffee.........");
coffeeWithHook.prepareRecipe();
}
}
//测试结果
Making tea.........
Boiling water
Steeping the tea
Pouring into cup
Adding Lemon
Making coffee.........
Boiling water
Dripping Coffee through filter
Pouring into cup
Would you like milk and suger with your coffee (y/n)? Yes --->咖啡中钩子实现的方法
Adding Sugar and Milk
3、关于钩子
钩子是一种声明在抽象类中的方法,但只有空的或者默认的实现。钩子的存在,可以让子类有能力对算法的不同点进行挂钩。
钩子的几种用法:
- 钩子可以让子类实现算法中的可选部分,或者在钩子对于子类的实现并不重要的时候,子类可以对此钩子置之不理。
- 让子类能够有机会对模板方法中某些即将发生的(或者刚刚发生的)步骤作出反应。例如名为 justReOrderList() 的钩子方法允许子类在内部列表重新组织后执行某些动作(如在屏幕上重新显示数据)。
某些步骤是可选的,所以你可以将这些步骤实现成钩子,而不是实现成抽象方法,这样可以让抽象类的子类减轻负荷
4、设计原则
- 别调用我们,我们会调用你:由超类主控一切,当它们需要的时候,自然会去调用子类
低层组件可以参与计算,但是高层组件控制合适以及如何让低层组件参加。低层组件绝对不可以直接调用高层组件。避免让高层和低层组件之间有明显的环形依赖。
5、要点
- 模板方法定义了算法的步骤,把这些步骤的实现延迟到子类
- 模板方法模式为我们提供了一种代码复用的重要技巧
- 模板方法的抽象类可以定义 具体方法、抽象方法和钩子
- 抽象方法由子类实现
- 钩子是一种方法,它在抽象类中不做事,或者只做默认的事情,子类可以选择要不要去覆盖他
- 为了防止子类改变模板方法中的算法,可以将模板方法声明为final
- 将决策权放在高层模块,以便决定何时以及如何调用底层模块
- 在实际中,模板方法模式有许多的变体,不要期待他们全部能被一眼看出
- 策略模式和模板方法都封装算法,一个用组合,一个用继承
- 工厂方法是模板方法的一种特殊版本
九、迭代器和组合模式
1、迭代器模式概念
迭代器模式提供了一种方法顺序访问一个集合对象的各个元素,而又不暴露其内部的表示。
把游走的任务放在迭代器上,而不是聚合上。这样简化了聚合的接口和实现,也让责任各得其所。
2、自定义迭代器例子
//迭代器接口
public interface Iterator {
boolean hasNext();
Object next();
}
//实体类
public class MenuItem {
}
//午餐项迭代器
public class DinerMenuIterator implements Iterator {
MenuItem[] items;
int position = 0;
public DinerMenuIterator(MenuItem[] items) {
this.items = items;
}
@Override
public boolean hasNext() {
return position < items.length && items[position] != null;
}
@Override
public Object next() {
if (hasNext()) {
return items[position++];
} else {
return null;
}
}
}
//煎饼迭代器
public class PancakeHouseIterator implements Iterator{
ArrayList<MenuItem> items;
int position = 0;
public PancakeHouseIterator(ArrayList<MenuItem> items) {
this.items = items;
}
@Override
public boolean hasNext() {
return position < items.size() && items.get(position) != null;
}
@Override
public Object next() {
if (hasNext()) {
return items.get(position++);
} else {
return null;
}
}
}
3、迭代器模式类图
4、设计原则
- 一个类应该只有一个引起变化的原因
这个原则告诉我们将一个责任只指派给一个类。类的每个字人都有改变的潜在区域。超过一个责任,意味着超过一个改变的区域。
内聚(cohesion)用来衡量一个类或者模块的紧密地达到单一目的或责任。当一个模块或者一个类被设计只支持一组相关的功能时,称之为高内聚;反之,被设计成支持一组不相关的功能时,我们说他具有低内聚。遵守上面这个原则的类具有很高的凝聚力,而且比背负许多责任的内聚类更加容易维护。
5、组合模式概念
组合模式允许你将对象组合成树状结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。
组合模式让我们能用树形方式创建对象的结构,树里面包含了组合以及个别的对象。
使用组合结构,我们能把相同的操作应用在组合和个别对象上,换句话说,在大多数情况下,我们可以忽略对象组合和个别对象之间的差别。
6、组合模式类图
7、组合模式例子
见书 360 页
8、组合模式和单一责任设计原则
组合模式以单一责任设计原则换取了透明性(transparency)。通过让组件的接口同时包含一些管理子节点和叶节点的操作,客户就可以将组合和叶节点一视同仁。也就是说,一个元素究竟是组合还是叶节点对客户是透明的。
为了保持透明性,组合内所有的对象都必须实现相同的接口,否则客户就必须操心哪个对象是使用的哪个接口,就失去了组合模式的意义。
9、要点
- 得带起允许访问聚合的元素,而不需要暴露它的内部结构
- 迭代器将遍历聚合的工作封装到一个对象中
- 当使用迭代器的时候,我们依赖聚合提供遍历
- 迭代器提供了一个通用的接口,让我们遍历聚合的项,当我们编码使用聚合的项时,就可以使用多态机制
- 应该努力让一个类只分配一个责任
- 组合模式提供了一个结构,可同时包容个别对象和组合对象
- 组合模式允许客户对个别对象和组合对象一视同仁
- 组合构造内部的任何对象称为组件,组件可以是组合,也可以是叶节点
- 在实现组合模式时,有许多设计上的折衷。你要根据需要平衡透明性和安全性。
十、状态模式
1、概念
状态模式允许对象在内部状态变化时改变它的行为,对象看起来好像修改了它的类
2、例子
不使用状态模式实现糖果机
//糖果机
public class GumballMachineOld {
final static int SOLD_OUT = 0;
final static int NO_QUARTER = 1;
final static int HAS_QUARTER = 2;
final static int SOLD = 3;
int state = SOLD_OUT; //记录当前状态
int cnt = 0; //当前糖果数量
public GumballMachineOld(int cnt) {
this.cnt = cnt;
if (cnt > 0) {
state = NO_QUARTER;
}
}
//对于投入硬币,不同状态的反应
public void insertQuarter() {
if (state == HAS_QUARTER) {
System.out.println("已经投入硬币,无需再投!");
} else if (state == NO_QUARTER) {
state = HAS_QUARTER;
System.out.println("投入硬币!");
} else if (state == SOLD_OUT) {
System.out.println("糖果已售罄!");
} else if (state == SOLD) {
System.out.println("请等待,糖果就在路上!");
}
}
//对于退回硬币,不同状态的反应
public void ejectQuarter() {
if (state == HAS_QUARTER) {
System.out.println("退回硬币!");
state = NO_QUARTER;
} else if (state == NO_QUARTER) {
System.out.println("你还没有投入硬币!");
} else if (state == SOLD_OUT) {
System.out.println("对不起,你已经转动了曲柄!");
} else if (state == SOLD) {
System.out.println("没有硬币投入,无法退回!");
}
}
//对于转动曲柄,不同状态的反应
public void turnCrank() {
if (state == HAS_QUARTER) {
System.out.println("你转动了曲柄,正在放出糖果!");
state = SOLD;
dispense();
} else if (state == NO_QUARTER) {
System.out.println("你转动了曲柄,但你还没有投入硬币!");
} else if (state == SOLD_OUT) {
System.out.println("你转动了曲柄,但糖果已售罄!");
} else if (state == SOLD) {
System.out.println("再转一次也只有一颗糖果!");
}
}
//放出糖果
private void dispense() {
if (state == SOLD) {
System.out.println("一颗糖果滚了出来!");
cnt--;
if (cnt == 0) {
System.out.println("糖果买完了!");
state = SOLD_OUT;
} else {
state = NO_QUARTER;
}
} else if (state == NO_QUARTER) {
System.out.println("你需要先投入硬币!");
} else if (state == SOLD_OUT) {
System.out.println("无法发放糖果!");
} else if (state == HAS_QUARTER) {
System.out.println("无法发放糖果!");
}
}
}
使用状态模式实现糖果机,并添加中奖游戏(有10%的机会得到两颗糖果)
//状态接口,其中定义了可能会的动作(请求)
public interface State {
void insertQuarter();
void ejectQuarter();
void turnCrank();
void dispense();
}
//没有硬币状态
public class NoQuarterState implements State{
GumballMachine gumballMachine;
public NoQuarterState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println("投入硬币!");
gumballMachine.setState(gumballMachine.getHasQuarterState());
}
@Override
public void ejectQuarter() {
System.out.println("你还没有投入硬币!");
}
@Override
public void turnCrank() {
System.out.println("你转动了曲柄,但你还没有投入硬币!");
}
@Override
public void dispense() {
System.out.println("你需要先投入硬币!");
}
}
//有硬币状态
public class HasQuarterState implements State{
//随机数生成生成
Random randomWinner = new Random(System.currentTimeMillis());
GumballMachine gumballMachine;
public HasQuarterState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println("已经投入硬币,无需再投!");
}
@Override
public void ejectQuarter() {
System.out.println("退回硬币!");
gumballMachine.setState(gumballMachine.getNoQuarterState());
}
@Override
public void turnCrank() {
System.out.println("你转动了曲柄,正在放出糖果!");
int winner = randomWinner.nextInt(10);
if (winner == 0 && (gumballMachine.getCnt() > 1)) {
gumballMachine.setState(gumballMachine.getWinnerState());
} else {
gumballMachine.setState(gumballMachine.getSoldState());
}
}
@Override
public void dispense() {
System.out.println("无法发放糖果!");
}
}
//售卖状态
public class SoldState implements State{
GumballMachine gumballMachine;
public SoldState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println("请等待,糖果就在路上!");
}
@Override
public void ejectQuarter() {
System.out.println("你已经转动曲柄,无法退回!");
}
@Override
public void turnCrank() {
System.out.println("再转一次也只有一颗糖果!");
}
@Override
public void dispense() {
//先放出糖果
gumballMachine.releaseBall();
//再根据是否还有糖果进行状态设置
if (gumballMachine.getCnt() > 0) {
gumballMachine.setState(gumballMachine.getNoQuarterState());
} else {
System.out.println("糖果买完了!");
gumballMachine.setState(gumballMachine.getSoldOutState());
}
}
}
//赢得两颗糖果状态类
public class WinnerState implements State{
GumballMachine gumballMachine;
public WinnerState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println("请等待,糖果就在路上!");
}
@Override
public void ejectQuarter() {
System.out.println("你已经转动曲柄,无法退回!");
}
@Override
public void turnCrank() {
System.out.println("再转一次也只有一颗糖果!");
}
@Override
public void dispense() {
//先放出一糖果
System.out.println("你中奖了,你将能得到两颗糖果!");
gumballMachine.releaseBall();
if (gumballMachine.getCnt() == 0) {//再根据是否还有糖果
System.out.println("糖果买完了!");
gumballMachine.setState(gumballMachine.getSoldOutState());
} else {
gumballMachine.releaseBall();
if(gumballMachine.getCnt() == 0) {
gumballMachine.setState(gumballMachine.getSoldOutState());
} else {
System.out.println("糖果买完了!");
gumballMachine.setState(gumballMachine.getNoQuarterState());
}
}
}
}
//售罄状态
public class SoldOutState implements State{
GumballMachine gumballMachine;
public SoldOutState(GumballMachine gumballMachine) {
this.gumballMachine = gumballMachine;
}
@Override
public void insertQuarter() {
System.out.println("糖果已售罄!");
}
@Override
public void ejectQuarter() {
System.out.println("对不起,你已经转动了曲柄!");
}
@Override
public void turnCrank() {
System.out.println("你转动了曲柄,但糖果已售罄!");
}
@Override
public void dispense() {
System.out.println("已售罄,无法发放糖果!");
}
}
//使用状态模式的糖果机
public class GumballMachine {
//所有的状态
State soldOutState;
State soldState;
State noQuarterState;
State hasQuarterState;
State winnerState;
State state = soldOutState;
int cnt = 0; //当前糖果数量
public GumballMachine(int cnt) {
soldOutState = new SoldOutState(this);
soldState = new SoldState(this);
noQuarterState = new NoQuarterState(this);
hasQuarterState = new HasQuarterState(this);
winnerState = new WinnerState(this);
this.cnt = cnt;
if (cnt > 0) {
state = noQuarterState;
}
}
public void insertQuarter() {
state.insertQuarter();
}
public void ejectQuarter() {
state.ejectQuarter();
}
public void turnCrank() {
state.turnCrank();
state.dispense();
}
void setState(State state) {
this.state = state;
}
void releaseBall() {
System.out.println("一颗糖果正在滚出");
if (cnt != 0) {
cnt--;
}
}
//填充糖果机
public void refill(int cnt) {
this.cnt = cnt;
state = noQuarterState;
}
}
3、类图
4、要点
- 状态模式允许一个对象基于内部状态而拥有不同的行为
- 和程序状态机(PSM)不同,状态模式用类表示状态
- Context会将行为委托给当前的状态对象
- 通过将每个状态封装到一个类,我们把以后需要做的任何改变局部化了
- 状态模式和策略模式有相同的类图,但他们的意图不同
- 策略模式通常会用行为或者算法来配置Context类
- 状态模式允许Context随着状态的改变而改变行为。
- 状态转换可以由state类或者Context类控制
- 使用状态模式通常会导致设计中类的数量大量增加
- 状态类可以被多个Context实例共享