该系列文章系个人读书笔记及总结性内容,任何组织和个人不得转载进行商业活动!
状态模式:
状态模式和策略模式是“双胞胎,在出生时才分开”;
策略模式是围绕可以互换的算法来创建成功业务的;
状态模式则是通过改变对象内部的状态来帮助对象控制自己的行为;
状态转换实例——工作的糖果机:
我们需要糖果机控制如下流程,而且设计需要有弹性、好维护(未来可能增加更多行为);
简单分析下:
这张图像是一张状态图,每个圆圈都是一个状态,每个箭头都是状态的转换;
这里的每一个状态都代表机器不同的配置以某种方式行动,需要某些动作将目前的状态转换到另一个状态;
比如,“没有25分钱”的状态下,放进25分钱的硬币,就会进入“25分钱”的状态,即状态的转换;
而且,对于任何一个可能的多国内说,我们都要检查,看看我们所处的状态和动作是否合适;
状态机 101:
我们来看下实现状态机(state machine)的简单介绍;
1)首先,找出所有的状态;
2)创建一个实例变量来持有目前的状态,定义每个状态的值;
比如,我们可以用 final static int SOLD_OUT = 0;//来表示糖果售罄的状态;(其他状态都是不同的整数值)
3)现在,将所有系统中可以发生的动作整合起来;
任何一个动作都会造成状态的转换;
4)创建一个类,作用就是一个状态机;
这里我们使用了一个通用的技巧:对对象内的状态建模——通过创建一个实例变量来持有状态值,并在方法内书写条件代码来处理不同的状态;
我们开始实现-糖果机:
【Code GumballMachine.java】
public class GumballMachine{
//all state
final static int SOLD_OUT = 0;
final static int NO_QUARTER = 1;
final static int HAS_QUARTER = 2;
final static int SOLD = 3;
//default sold out
int state = SOLD_OUT;
int count = 0;//gumbal count
public GumballMachine(int count){
this.count = count;
if(count >0){
state = NO_QUARTER;
}
}
//投入25分钱
public void insertQuarter(){
if (state == HAS_QUARTER){
System.out.print("- you have insert a quarter -\n");
}else if (state == NO_QUARTER){
state = HAS_QUARTER;
System.out.print("- you inserted a quarter -\n");
}else if (state == SOLD_OUT){
System.out.print("- the machine is sold out -\n");
}else if (state == SOLD){
System.out.print("- please wait, we are giving you a gumbal -\n");
}
}
//退回25分钱
public void ejectQuarter(){
if (state == HAS_QUARTER){
System.out.print("- quarter returned -\n");
state = NO_QUARTER;
}else if (state == NO_QUARTER){
System.out.print("- you have not inserted a quarter -\n");
}else if (state == SOLD_OUT){
System.out.print("- do not this, you have not inserted a quarter -\n");
}else if (state == SOLD){
System.out.print("- Sorry, you already turned the crank -\n");
}
}
//转动曲柄
public void turnCrank(){
if (state == HAS_QUARTER){
System.out.print("- turned -\n");
state = SOLD;
dispense();
}else if (state == NO_QUARTER){
System.out.print("- you turned, but you have not inserted a quarter -\n");
}else if (state == SOLD_OUT){
System.out.print("- gumbal has sold out -\n");
}else if (state == SOLD){
System.out.print("- turning twice no use -\n");
}
}
//发放糖果
public void dispense(){
if (state == HAS_QUARTER){
System.out.print("- No gumbal dispensed -\n");
}else if (state == NO_QUARTER){
System.out.print("- you need pay first -\n");
}else if (state == SOLD_OUT){
System.out.print("- No gumbal dispensed -\n");
}else if (state == SOLD){
System.out.print("- gumbal is comming -\n");
count = count - 1;
if (count == 0){
System.out.print("- Sorry, out of gumbals -\n");
state = SOLD_OUT;
}else{
state = NO_QUARTER;
}
}
}
//other methods
public String toString() {
String str = "";
if (state == HAS_QUARTER){
str = "- Machine state: " + "HAS_QUARTER, count = " +count+ "-\n";
}else if (state == NO_QUARTER){
str = "- Machine state: " + "NO_QUARTER, count = " +count + "-\n";
}else if (state == SOLD_OUT){
str = "- Machine state: " + "SOLD_OUT, count = " +count + "-\n";
}else if (state == SOLD){
str = "- Machine state: " + "SOLD, count = " +count + "-\n";
}
return str;
}
}
测试运行:
【Code GumballMachineDrive.java】
public class GumballMachineDrive{
public static void main(String[] args){
GumballMachine gumbalMachine = new GumballMachine(2);
System.out.print(gumbalMachine + "\n");
gumbalMachine.insertQuarter();
gumbalMachine.turnCrank();
System.out.print(gumbalMachine + "\n");
gumbalMachine.insertQuarter();
gumbalMachine.turnCrank();
System.out.print(gumbalMachine + "\n");
gumbalMachine.insertQuarter();
gumbalMachine.turnCrank();
System.out.print(gumbalMachine + "\n");
}
}
上面是我们实现的状态机101,而且通过我们的测试,运行是正常的;
但是,我们收到了新的需求:
当曲柄被转动时,有10%的几率掉下来两颗糖果;
此时我们能体会到一开始设计的糖果机并不容易扩展:
加上一个新的状态,就必须在每个方法中加入一个新的条件判断来处理赢家状态;
我们需要重构这份代码,以便我们能容易地维护和维修他;
遵循“封装变化”原则:
将每个状态的行为都放在各自的类中,那么每个状态只要实现它自己的动作就可以了;
糖果机只需要委托给代表当前状态的状态对象;
新的设计:
将状态对象封装在各自的类中,然后在动作发生时委托给当前状态;
1)首先,定义一个State接口,每个动作都有一个对应的方法;
2)然后为机器中的每个状态实现状态类,这些类将负责在对应的状态下进行机器的行为;
3)最后,拜托条件代码,取而代之的是,将动作委托到状态类;
在实现之后,我们会发现,优化的版本不仅遵守了设计原则,实际上我们还实现了状态模式;
状态模式的状态机:
创建State接口,所有的状态都必须实现这个接口;
接口定义的方法直接映射到糖果机上可能发生的动作;
将设计中的每个状态都封装成一个类,每个都实现State接口;
我们先不增加10%概率赢的状态和操作,只是添加下重填这个事件(售完状态 可以通过重填 转换到 需要25分钱的状态),实现如下:
【Code GumballMachineDrive.java】
public class GumballMachineDrive{
public static void main(String[] args){
GumballMachine gumbalMachine = new GumballMachine(2);
System.out.print(gumbalMachine + "\n");
gumbalMachine.insertQuarter();
gumbalMachine.turnCrank();
System.out.print(gumbalMachine + "\n");
gumbalMachine.insertQuarter();
gumbalMachine.turnCrank();
System.out.print(gumbalMachine + "\n");
gumbalMachine.insertQuarter();
gumbalMachine.turnCrank();
System.out.print(gumbalMachine + "\n");
gumbalMachine.refill(5);
System.out.print(gumbalMachine + "\n");
}
}
【Code GumballMachine.java】
public class GumballMachine{
State soldOutState;
State noQuarterState;
State hasQuarterState;
State soldState;
State state = soldOutState;
int count = 0;//gumbal count
public GumballMachine(int numberGumballs){
soldOutState = new SoldOutState(this);
noQuarterState = new NoQuarterState(this);
hasQuarterState = new HasQuarterState(this);
soldState = new SoldState(this);
this.count = numberGumballs;
if(numberGumballs >0){
state = noQuarterState;
}
}
//投入25分钱
public void insertQuarter(){
state.insertQuarter();
}
//退回25分钱
public void ejectQuarter(){
state.ejectQuarter();
}
//转动曲柄
public void turnCrank(){
state.turnCrank();
state.dispense();
}
//发放糖果
public void dispense(){
state.dispense();
}
//重填
public void refill(int numberGumball){
this.count = numberGumball;
state.refill(numberGumball);
}
//辅助方法
void releaseBall(){
System.out.print("发了一颗糖 \n");
if (count != 0){
count = count - 1;
}
}
void setState(State state){
this.state = state;
}
State getState(){
return state;
}
int getCount(){
return count;
}
State getSoldOutState(){
return soldOutState;
}
State getNoQuarterState(){
return noQuarterState;
}
State getHasQuarterState(){
return hasQuarterState;
}
State getSoldState(){
return soldState;
}
//other methods
public String toString() {
String str = "";
if (state == hasQuarterState){
str = "- Machine state: " + "HAS_QUARTER, count = " +count+ "-\n";
}else if (state == noQuarterState){
str = "- Machine state: " + "NO_QUARTER, count = " +count + "-\n";
}else if (state == soldOutState){
str = "- Machine state: " + "SOLD_OUT, count = " +count + "-\n";
}else if (state == soldState){
str = "- Machine state: " + "SOLD, count = " +count + "-\n";
}
return str;
}
}
【Code State.java】
public interface State{
//投入25分钱
public void insertQuarter();
//退回25分钱
public void ejectQuarter();
//转动曲柄
public void turnCrank();
//发放糖果
public void dispense();
//重填
public void refill(int numberGumball);
}
【Code NoQuarterState.java】
public class NoQuarterState implements State{
GumballMachine gumballMachine;
public NoQuarterState(GumballMachine gumballMachine){
this.gumballMachine = gumballMachine;
}
//投入25分钱
public void insertQuarter(){
gumballMachine.setState(gumballMachine.getHasQuarterState());
System.out.print("-已投入 25分钱 \n");
}
//退回25分钱
public void ejectQuarter(){
System.out.print("-还没 投入 25分钱 \n");
}
//转动曲柄
public void turnCrank(){
System.out.print("-还没 投入 25分钱 \n");
}
//发放糖果
public void dispense(){
System.out.print("-还没 投入 25分钱 \n");
}
//重填
public void refill(int numberGumball){
System.out.print("-还有糖果 \n");
}
}
【Code HasQuarterState.java】
public class HasQuarterState implements State{
GumballMachine gumballMachine;
public HasQuarterState(GumballMachine gumballMachine){
this.gumballMachine = gumballMachine;
}
//投入25分钱
public void insertQuarter(){
System.out.print("-已投入 25分钱 \n");
}
//退回25分钱
public void ejectQuarter(){
gumballMachine.setState(gumballMachine.getNoQuarterState());
System.out.print("-退还 25分钱 \n");
}
//转动曲柄
public void turnCrank(){
gumballMachine.setState(gumballMachine.getSoldState());
System.out.print("-转动曲柄 \n");
}
//发放糖果
public void dispense(){
System.out.print("-糖果还没出来 \n");
}
//重填
public void refill(int numberGumball){
System.out.print("-还有糖果 无法 重填 \n");
}
}
【Code SoldState.java】
public class SoldState implements State{
GumballMachine gumballMachine;
public SoldState(GumballMachine gumballMachine){
this.gumballMachine = gumballMachine;
}
//投入25分钱
public void insertQuarter(){
System.out.print("-请稍后 我们正在发发糖 \n");
}
//退回25分钱
public void ejectQuarter(){
System.out.print("-请稍后 我们正在发发糖 \n");
}
//转动曲柄
public void turnCrank(){
System.out.print("-请稍后 我们正在发发糖 \n");
}
//发放糖果
public void dispense(){
gumballMachine.releaseBall();
if (gumballMachine.getCount() > 0){
gumballMachine.setState(gumballMachine.getNoQuarterState());
}else{
gumballMachine.setState(gumballMachine.getSoldOutState());
System.out.print("-糖果空了 \n");
}
}
//重填
public void refill(int numberGumball){
System.out.print("-糖果 在售中 \n");
}
}
【Code SoldOutState.java】
public class SoldOutState implements State{
GumballMachine gumballMachine;
public SoldOutState(GumballMachine gumballMachine){
this.gumballMachine = gumballMachine;
}
//投入25分钱
public void insertQuarter(){
System.out.print("-糖果空了 \n");
}
//退回25分钱
public void ejectQuarter(){
System.out.print("-糖果空了 \n");
}
//转动曲柄
public void turnCrank(){
System.out.print("-糖果空了 \n");
}
//发放糖果
public void dispense(){
System.out.print("-糖果空了 \n");
}
//重填
public void refill(int numberGumball){
gumballMachine.setState(gumballMachine.getNoQuarterState());
System.out.print("-糖果 已重填 \n");
}
}
检查一下我们现在做的:
将每个状态的行为局部化到他自己的类中;
将容易产生问题的if语句删除,方便维护;
让每一个状态“对修改关闭”,让糖果机“对扩展开放”;
定义状态模式:
状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类;
这个模式将状态封装成为独立的类,并将动作委托到代表当前状态的对象;我们是在使用组合通过简单引用不同的状态对象来造成改变的假象;
类图:
我们发现这个类图和策略模式的类图一样:
以状态模式而言,我们将一群行为封装在状态对象中,context的行为随时可委托到那些状态对象中的一个;
当前状态在状态对象集合中游走改变,以反映出context内部的状态;
但是,context的客户对于状态对象了解不多,甚至浑然不觉;
而以策略模式而言,客户通常主动指定Context所要组合的策略对象是哪一个;策略模式让我们有弹性,能够在运行时改变策略;
一般来说,我们把策略模式想成是除了继承之外的一种弹性替代方案;如果你使用继承定义了一个类的行为,你将被这个行为困住,甚至要修改它都很难;有了策略模式,你可以通过组合不同的对象来改变行为;
我们把状态模式想成是不用在context中放置许多条件判断的替代方案;通过将行为包装进状态对象中,你可以通过在context内简单地改变状态对象来改变context的行为;
注意:
一般来讲,当状态改变时固定的时候,就适合放在Context中;然而,当转换是更动态的时候,通常会放在状态类中;
客户也不会直接改变Context的状态;
不持有自己内部状态的状态对象是可以在多个上下文实例之间共享的;
对于State既可以是接口,也可以是抽象类,当没有共同功能可以放进抽象类时,就使用接口;
10%概率抽奖的问题解决:
接下来我们编码实现,验证可扩展性;
这里我们只列出修改的类:
【Code WinnerState.java】
public class WinnerState implements State{
GumballMachine gumballMachine;
public WinnerState(GumballMachine gumballMachine){
this.gumballMachine = gumballMachine;
}
//投入25分钱
public void insertQuarter(){
System.out.print("-请稍后 我们正在发发糖 \n");
}
//退回25分钱
public void ejectQuarter(){
System.out.print("-请稍后 我们正在发发糖 \n");
}
//转动曲柄
public void turnCrank(){
System.out.print("-请稍后 我们正在发发糖 \n");
}
//发放糖果
public void dispense(){
System.out.print("-Winner! \n");
gumballMachine.releaseBall();
if (gumballMachine.getCount() == 0){
gumballMachine.setState(gumballMachine.getSoldOutState());
}else{
gumballMachine.releaseBall();
if (gumballMachine.getCount() > 0){
gumballMachine.setState(gumballMachine.getNoQuarterState());
}else{
gumballMachine.setState(gumballMachine.getSoldOutState());
}
}
}
//重填
public void refill(int numberGumball){
System.out.print("-请稍后 我们正在发发糖 \n");
}
}
【Code GumballMachine.java】
public class GumballMachine{
State soldOutState;
State noQuarterState;
State hasQuarterState;
State soldState;
State winnerState;//add
State state = soldOutState;
int count = 0;//gumbal count
public GumballMachine(int numberGumballs){
soldOutState = new SoldOutState(this);
noQuarterState = new NoQuarterState(this);
hasQuarterState = new HasQuarterState(this);
soldState = new SoldState(this);
winnerState = new WinnerState(this);//add
this.count = numberGumballs;
if(numberGumballs >0){
state = noQuarterState;
}
}
//投入25分钱
public void insertQuarter(){
state.insertQuarter();
}
//退回25分钱
public void ejectQuarter(){
state.ejectQuarter();
}
//转动曲柄
public void turnCrank(){
state.turnCrank();
state.dispense();
}
//发放糖果
public void dispense(){
state.dispense();
}
//重填
public void refill(int numberGumball){
this.count = numberGumball;
state.refill(numberGumball);
}
//辅助方法
void releaseBall(){
System.out.print("发了一颗糖 \n");
if (count != 0){
count = count - 1;
}
}
void setState(State state){
this.state = state;
}
State getState(){
return state;
}
int getCount(){
return count;
}
State getSoldOutState(){
return soldOutState;
}
State getNoQuarterState(){
return noQuarterState;
}
State getHasQuarterState(){
return hasQuarterState;
}
State getSoldState(){
return soldState;
}
State getWinnerState(){//add
return winnerState;
}
//other methods
public String toString() {
String str = "";
if (state == hasQuarterState){
str = "- Machine state: " + "HAS_QUARTER, count = " +count+ "-\n";
}else if (state == noQuarterState){
str = "- Machine state: " + "NO_QUARTER, count = " +count + "-\n";
}else if (state == soldOutState){
str = "- Machine state: " + "SOLD_OUT, count = " +count + "-\n";
}else if (state == soldState){
str = "- Machine state: " + "SOLD, count = " +count + "-\n";
}
return str;
}
}
【Code HasQuarterState.java】
import java.util.Random;//add
public class HasQuarterState implements State{
Random randomWinner = new Random();//add
GumballMachine gumballMachine;
public HasQuarterState(GumballMachine gumballMachine){
this.gumballMachine = gumballMachine;
}
//投入25分钱
public void insertQuarter(){
System.out.print("-已投入 25分钱 \n");
}
//退回25分钱
public void ejectQuarter(){
gumballMachine.setState(gumballMachine.getNoQuarterState());
System.out.print("-退还 25分钱 \n");
}
//转动曲柄
public void turnCrank(){
System.out.print("-转动曲柄 \n");
int winner = randomWinner.nextInt(10);//add
if ((winner == 0) && (gumballMachine.getCount() > 1)){
gumballMachine.setState(gumballMachine.getSoldState());
}else{
gumballMachine.setState(gumballMachine.getWinnerState());
}
}
//发放糖果
public void dispense(){
System.out.print("-糖果还没出来 \n");
}
//重填
public void refill(int numberGumball){
System.out.print("-还有糖果 无法 重填 \n");
}
}
【Code GumballMachineDrive.java】
public class GumballMachineDrive{
public static void main(String[] args){
GumballMachine gumbalMachine = new GumballMachine(6);
System.out.print(gumbalMachine + "\n");
gumbalMachine.insertQuarter();
gumbalMachine.turnCrank();
System.out.print(gumbalMachine + "\n");
gumbalMachine.insertQuarter();
gumbalMachine.turnCrank();
System.out.print(gumbalMachine + "\n");
gumbalMachine.insertQuarter();
gumbalMachine.turnCrank();
System.out.print(gumbalMachine + "\n");
gumbalMachine.refill(5);
System.out.print(gumbalMachine + "\n");
}
}
运行结果:
dispense()方法即使是没有25分钱,句柄转动也总是被调用;我们可以轻易地修改这部分:让turnCrank()返回一个布尔值,或者引入异常;
状态:封装基于状态的行为,并将行为委托到当前状态;
策略:将可以互换的行为封装起来,然后使用委托的方法,决定使用哪一个行为;
模板方法:由子类决定如何实现算法中的某些步骤
总结:
1.状态模式允许一个对象基于内部状态而拥有不同的行为
2.和程序状态机(PSM)不同,状态模式用类代表状态;
3.Context会将行为委托给当前状态对象;
4.通过将每个状态封装进一个类,我们把以后需要做的任何改变局部化了;
5.状态模式和策略模式有相同的类图,但是他们的意图不同;
6.策略模式通常会用行为或算法来配置Context类;
7.状态模式允许Context随着状态的改变而改变行为;
8.状态转换可以由State类或Context类控制;
9.使用状态模式通常会导致设计中类的数目大量增加;
10.状态类可以被多个Context实例共享;
OO基础:
抽象;
封装
继承;
多态;
OO原则:
封装变化
多用组合,少用继承
针对接口编程,不针对实现编程
为交互对象之间的松耦合设计而努力;
类应该对扩展开放,对修改关闭;
依赖抽象,不要依赖具体类;
只和朋友交谈(最少知识原则);
别找我,我会找你(好莱坞原则:由超类主控一切,需要的时候自然会去调用子类);
类应该只有一个改变的理由(单一职责原则);
OO模式:
策略模式:定义算法族,分别封装起来,让他们之间互相替换,此模式让算法的变化独立于使用算法的客户;
观察者模式:在对象之间定义一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象都会收到通知,并自动更新;
装饰者模式:动态地将责任附加到对象上;想要扩展功能,装饰者提供有别于继承的另一种选择;
简单工厂模式;
工厂方法模式:定义了一个创建对象的接口,但由子类决定要实例化的类是哪一个;工厂方法让类把实例化推迟到子类;
抽象工厂模式:提供一个接口,用于创建相关或依赖对象的家族,而不需要明确具体的类;
单件模式:确保一个类只有一个实例,并提供全局访问点;
命令模式:将请求封装成对象,这可以让你使用不同的请求,队列或者日志请求来参数化其他对象;命令模式也支持撤销操作;
适配器模式:将一个类的接口转换成客户期待的另一个接口,适配器让原来不兼容的类可以合作无间;
外观模式:提供了一个统一的接口,用来访问子系统中的一群接口;外观模式定义了高层接口,让子系统更容易使用;
模板方法模式:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中;模板方法使得子类可以在不改变算法结构的情况下,重新定义/捕获算法中的某些步骤;
迭代器模式:提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示;
组合模式:允许你将对象组成树形结构来表现整体/部分的层次结构;组合能让客户以一致的方式处理个别对象和对象组合;
——状态模式:允许对象在内部状态改变时改变他的行为,对象看起来好像修改了它的类;