设计模式笔记:状态模式&策略模式

Posted Monkey_Dog

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式笔记:状态模式&策略模式相关的知识,希望对你有一定的参考价值。

这几天一直在忙期末考和实训,写笔记的时间也没有多少,不说废话了:

这文主要写两种模式:状态跟策略,主要是因为他们的类图一样,并且比较简单,写在同一篇文章里面容易甄别

状态模式:允许对象在内部状态改变时改变他的行为,对象看起来好像修改了他的类

先保留概念的意思,在平常的编程中,如果需要不同的状态,很一般的做法是在你要操作的类里面定义不同的常量代表不同的状态,然后if-else依据不同的状态有不同的实现:

1、你可以想象大量的if-else语句造成的低可读性和低效率

2、其次是你修改这个类的时候很麻烦,增加或删除常量状态可能要逐个修改,这也违反了开闭原则

所谓状态模式,他的通常做法是用一个类去代表一个状态

引用《Head First 设计模式》的糖果机例子:客户通过投币、转动曲柄的方式向糖果机买糖果,类似于普通生活的自动售卖机,那么我们要写代码描述这一过程:

我们有必要把行为和状态分类:

行为:投币、退币、转动曲柄、发放糖果动作

状态:有钱状态、没钱(等待)状态、发放糖果状态、糖果售罄状态

(后面会在另外一篇里写代理模式,例子会根据状态模式的例子扩展,哈,图个方便引用head first的例子,毕竟学生狗经验有限,写不出高质量的例子)

现在我们要做的是在不同的状态改变糖果机的行为、或者不同的行为同时改变状态,针对场景,我们可以用这样的类图描述状态:


后面有一个新的状态是winnerState,他描述了客户在买糖果的时候有十分之一的概率中奖,这里没有列出来,代码会有:

现在已经很清楚地分开了状态跟行为:根据类图的代码:

public interface State {
	//投币
	public void insertQuarter();
	//退币
	public void ejectQuarter();
	//转动
	public void turnCrank();
	//发放奖品
	public void dispend();
}

public class HasQuarterState implements State {
	//我们可以看到状态跟糖果机存在依赖
	private GumballMachine gumballMachine;
	//中奖的概率事件写在投币状态,因为这是中奖的前一个状态
	Random randomWinner = new Random(System.currentTimeMillis());
	
	public HasQuarterState(GumballMachine gumballMachine) {
		this.gumballMachine = gumballMachine;
	}
	//告诉顾客已经投币,不能再次投币
	//这就是状态模式的一个特点:允许对象在内部状态改变时改变他的行为,这个在setState的方法中体现,例如在ejectQuarter()
	//而这里是跟ejectQuarter()同性质的行为方法,但是他在HasQuarterState这个状态中不做任何事情,最多只能是提示
	//顾客你已经投币了,当然仅仅是相对这个例子来说,如果有其他的场景,并不说明一定是有不干活的行为
	@Override
	public void insertQuarter() {System.out.println("You cannot inserted another quarter");}
	//状态改变时改变环境类的行为
	@Override
	public void ejectQuarter() {
		System.out.println("Quarter returned");
		//退币行为的下一个状态自然是等待状态
		gumballMachine.setState(gumballMachine.getNoQuarterState());
	}
	@Override
	public void turnCrank() {
		System.out.println("You turned...");
		int winner = randomWinner.nextInt(10);
		if(winner==0&&gumballMachine.getCount()>1){
			//中奖了,那么把糖果机状态改为中奖状态
			gumballMachine.setState(gumballMachine.getWinnerState());
		}else{
			//否则就是发放奖品状态,状态更换之后便不用再管糖果机的行为
			//因为糖果机的行为会委托给下一个状态
			gumballMachine.setState(gumballMachine.getSoldState());
		}
	}
	//告诉顾客没有糖果发放
	@Override
	public void dispend() {System.out.println("no gumball dispended");}
	
	@Override
	public String toString() {
		return "HasQuarterState";
	}
}

public class NoQuarterState implements State {
	private GumballMachine gumballMachine;
	public NoQuarterState(GumballMachine gumballMachine) {
		this.gumballMachine = gumballMachine;
	}
	@Override
	public void insertQuarter() {
		System.out.println("You inserted a quarter");
		gumballMachine.setState(gumballMachine.getHasQuarterState());
	}
	@Override
	public void ejectQuarter() {System.out.println("You haven't inserted a quarter");}
	@Override
	public void turnCrank() {System.out.println("You turned,but there is no quarter");}
	@Override
	public void dispend() {System.out.println("You need to pay first");}
	
	@Override
	public String toString() {
		return "NoQuarterState";
	}
}

public class SoldState implements State {
	private GumballMachine gumballMachine;
	
	public SoldState(GumballMachine gumballMachine) {
		this.gumballMachine = gumballMachine;
	}
	@Override
	public void insertQuarter() {System.out.println("Please wait,we are already giving you a gumball");}
	@Override
	public void ejectQuarter() {System.out.println("Sorry,you already turned the crank");}
	@Override
	public void turnCrank() {System.out.println("Turning twice doesn't get you another gumball");}
	@Override
	public void dispend() {
		gumballMachine.releaseBall();
		if(gumballMachine.getCount()>0){
			//奖品发放完毕,转换为等待状态
			gumballMachine.setState(gumballMachine.getNoQuarterState());
		}else{
			System.out.println("Oops,out of gumballs!");
			gumballMachine.setState(gumballMachine.getSoldOutState());
		}
	}
	@Override
	public String toString() {
		return "SoldState";
	}
}

//糖果卖完了,自然是什么都不能干
//糖果机把行为委托给这个状态,顾客访问售罄状态的时候什么都干不了
public class SoldOutState implements State {
	private GumballMachine gumballMachine;
	
	public SoldOutState(GumballMachine gumballMachine) {
		this.gumballMachine = gumballMachine;
	}
	@Override
	public void insertQuarter() {System.out.println("You cannot insert a quarter,the machine is sold out");}
	@Override
	public void ejectQuarter() {System.out.println("You cannot eject,you haven't inserted a quarter yet");}
	@Override
	public void turnCrank() {System.out.println("You turned,but there are no gumball");}
	@Override
	public void dispend() {System.out.println("No gumball dispended");}
	@Override
	public String toString() {
		return "SoldOutState";
	}
}

//WinnerState跟SoldState干的活基本一样,都被委托
//dispend这个方法中的行为,但是他们还是有差别的,因为你完全可以把这两个状态写在一个类里面
//但是:一个类,一个责任
//他们是两种不一样的状态,如果当获奖概率改变、或者不在提供获奖概率的时候,就有可能修改SoldState
//这就混起来了,反之,我们可以很简单的弃用WinnerState而不担心SoldState
//这可能仍会对HasQuarterState做出小修改,毕竟没有不干活就收获的事情,这种方法能把耦合降低而已
public class WinnerState implements State {
	private GumballMachine gumballMachine;
	public WinnerState(GumballMachine gumballMachine) {
		this.gumballMachine = gumballMachine;
	}
	@Override
	public void insertQuarter() {System.out.println("You cannot insert a quarter");}
	@Override
	public void ejectQuarter() {System.out.println("You cannot return a quarter");}
	@Override
	public void turnCrank() {System.out.println("You turned,but there is no quarter");}
	@Override
	public void dispend() {
		System.out.println("You are a Winner!");
		gumballMachine.releaseBall();
		if(gumballMachine.getCount()<=0){
			gumballMachine.setState(gumballMachine.getSoldOutState());
		}else{
			gumballMachine.releaseBall();
			if(gumballMachine.getCount()>0){
				gumballMachine.setState(gumballMachine.getNoQuarterState());
			}else{
				System.out.println("Oops,out of gumballs!");
				gumballMachine.setState(gumballMachine.getSoldOutState());
			}
		}
	}
	@Override
	public String toString() {
		return "WinnerState";
	}
}
到此为止,我们看到了一系列的状态类,还有他们被委托的行为

这是存在依赖的,我们可以猜测到这些状态类重用性很低,这就是状态模式的一个缺点:造成大量的类却难以重用,同时如果场景不一样,依然可以存在状态的共享,即可以重用,这样自然是可以使用单例模式的

现在是时候写糖果机本类了:

public class GumballMachine {
	//定义一堆状态类
	private State soldOutState;
	private State hasQuarterState;
	private State noQuarterState;
	private State soldState;
	private State winnerState;
	//定义目前状态
	private State state;
	//糖果数量
	private int count;
	public GumballMachine(int count) {
		soldOutState = new SoldOutState(this);
		hasQuarterState = new HasQuarterState(this);
		noQuarterState = new NoQuarterState(this);
		soldState = new SoldState(this);
		winnerState = new WinnerState(this);
		this.count = count;
		if(count>0){
			state = noQuarterState;
		}
	}
	//这些行为委托给状态
	public void insertQuarter(){state.insertQuarter();}
	public void ejectQuarter(){state.ejectQuarter();}
	public void turnCrank(){
		state.turnCrank();
		//转动曲柄后,state指向的状态发生了改变,自然调用的是不同状态的行为
		state.dispend();
	}

	public void releaseBall(){
		System.out.println("A gumball comes rolling out the slot");
		if(count>0){count--;}
	}
	
	//以下是get、set方法
	public int getCount() {return count;}
	public void setCount(int count) {this.count = count;}
	
	public void setState(State state) {this.state = state;}
	public State getState() {return state;}
	
	public State getSoldOutState() {return soldOutState;}
	public void setSoldOutState(State soldOutState) {this.soldOutState = soldOutState;}

	public State getHasQuarterState() {return hasQuarterState;}
	public void setHasQuarterState(State hasQuarterState) {this.hasQuarterState = hasQuarterState;}

	public State getNoQuarterState() {return noQuarterState;}
	public void setNoQuarterState(State noQuarterState) {this.noQuarterState = noQuarterState;}

	public State getSoldState() {return soldState;}
	public void setSoldState(State soldState) {this.soldState = soldState;}

	public State getWinnerState() {return winnerState;}
	public void setWinnerState(State winnerState) {this.winnerState = winnerState;}
	
	@Override
	public String toString() {
		return "state ="+this.state+"   "+"count ="+this.count;
	}
}
测试:

public class GumballMachineTestDrive {
	public static void main(String[] args) {
		System.out.println("还没有投币的时候");
		GumballMachine gumballMachine = new GumballMachine(5);
		System.out.println(gumballMachine);
		
		System.out.println("投币后");
		gumballMachine.insertQuarter();
		System.out.println(gumballMachine);
		
		System.out.println("转动曲柄");
		gumballMachine.turnCrank();
		System.out.println(gumballMachine);
		
		System.out.println("现在尝试多次投币");

		System.out.println("第二次尝试");
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		System.out.println(gumballMachine);
		
		System.out.println("第三次尝试");
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		System.out.println(gumballMachine);
		
		System.out.println("第四次尝试");
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		System.out.println(gumballMachine);
		
		System.out.println("第五次尝试");
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		System.out.println(gumballMachine);
		
		System.out.println("第六次尝试");
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		System.out.println(gumballMachine);
	}

}
//还没有投币的时候
//state =NoQuarterState   count =5
//投币后
//You inserted a quarter
//state =HasQuarterState   count =5
//转动曲柄
//You turned...
//A gumball comes rolling out the slot
//state =NoQuarterState   count =4
//现在尝试多次投币
//第二次尝试
//You inserted a quarter
//You turned...
//A gumball comes rolling out the slot
//state =NoQuarterState   count =3
//第三次尝试
//You inserted a quarter
//You turned...
//A gumball comes rolling out the slot
//state =NoQuarterState   count =2
//第四次尝试
//You inserted a quarter
//You turned...
//A gumball comes rolling out the slot
//state =NoQuarterState   count =1
//第五次尝试
//You inserted a quarter
//You turned...
//A gumball comes rolling out the slot
//Oops,out of gumballs!
//state =SoldOutState   count =0
//第六次尝试
//You cannot insert a quarter,the machine is sold out
//You turned,but there are no gumball
//No gumball dispended
//state =SoldOutState   count =0


这是一个简单的状态模式,如果有兴趣可以研究一下android的状态机应用,也是使用了状态模式,笔者经验有限不能探知一二,这里给出一篇文章参考:

http://www.cnblogs.com/bastard/archive/2012/06/05/2536258.html

看看状态模式的类图(跟策略模式基本一样,下面会给出策略模式,同时总结一下区别)

适用性:

1、对象的行为依赖于它的状态(属性)并且可以根据它的状态改变而改变它的相关行为。
2、代码中包含大量与对象状态有关的条件语句,这些条件语句的出现,会导致代码的可维护性和灵活性变差,不能方便地增加和删除状态,使客户类与类库之间的耦合增强。在这些条件语句中包含了对象的行为,而且这些条件对应于对象的各种状态。

这亮点在例子中均有体现,不再解释,状态模式在工作流或游戏等类型的软件中得以广泛使用,甚至可以用于这些系统的核心功能设计,如在政府OA办公系统中,一个批文的状态有多种:尚未办理;正在办理;正在批示;正在审核;已经完成等各种状态,而且批文状态不同时对批文的操作也有所差异。使用状态模式可以描述工作流对象(如批文)的状态转换以及不同状态下它所具有的行为。

优点:

1、封装了转换规则。
2、枚举可能的状态,在枚举状态之前需要确定状态种类。
3、将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
4、允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
5、可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
缺点:
1、状态模式的使用必然会增加系统类和对象的个数。(但使用状态模式仍然是值得的)
2、状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱。
3、状态模式对“开闭原则”的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态;而且修改某个状态类的行为也需修改对应类的源代码。

到了这里,我们继续理解以下概念的意思:允许对象在内部状态改变时改变他的行为,对象看起来好像修改了他的类

1、状态经常需要切换,在不同的状态下对象的行为不同,状态模式允许把行为委托给状态类,在内部状态改变时通过状态类改变环境类的行为

2、将不同对象下的行为单独提取出来封装在具体的状态类中,使得环境类对象在其内部状态改变时可以改变它的行为,对象看起来似乎修改了它的类,而实际上是由于切换到不同的具体状态类实现的

我们看看策略模式的类图:在状态模式的基础上修改了一点,顺便在这也总结一次策略模式


策略模式:定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法独立于使用它的客户而变化

1、完成一项任务,往往可以有多种不同的方式,每一种方式称为一个策略,我们可以根据环境或者条件的不同选择不同的策略来完成该项任务。系统可以灵活地选择解决途径,也能够方便地增加新的解决途径。

2、在软件系统中,有许多算法可以实现某一功能,如查找、排序等,硬编码实现的两种方式:(维护困难,可读性低)

(1)可以将这些算法写到一个类中,在该类中提供多个方法,每一个方法对应一个具体的查找算法

(2)算法封装在一个统一的方法中,通过if…else…等条件判断语句来进行选择

最佳解决办法:可以定义一些独立的类来封装不同的算法,每一个类封装一个具体的算法,在这里,每一个封装算法的类我们都可以称之为策略(Strategy),为了保证这些策略的一致性,一般会用一个抽象的策略类来做算法的定义,而具体每种算法则对应于一个具体策略类。用一句话来说,就是“准备一组算法,并将每一个算法封装起来,使得它们可以互换”

利用《Head First 设计模式》的场景:一套模拟鸭子的游戏,鸭子有不同的行为,当然,针对一个行为,有些鸭子有这个行为,有些鸭子没有这个行为(比如真实鸭子会飞,玩具鸭子不会飞)

简单起见,我不写类图了,先看看Strategy以及他的子类实现

//我们可以把这个接口的fly方法想象成是一个算法,由子类实现不同的算法实现方式
//超类跟子类组成算法族,而替换指的是用子类去替换算法,使得不变的鸭子实体跟变化的行为分离开
public interface FlyBehavior {
	public void fly();
}

//不会飞
public class FlyWithNoWay implements FlyBehavior {
	@Override
	public void fly() {System.out.println("I can not fly!");}
}

public class FlyWithWings implements FlyBehavior {
	@Override
	public void fly() {System.out.println("I'm flying with wings!");}
}
这里定义了一组策略——会飞和不会飞,接下来是Context类,即鸭子类:

//鸭子的超类
public abstract class Duck {
	
	//一个算法的接口,利用他来相互替换
	FlyBehavior flyBehavior;
	public FlyBehavior getFlyBehavior() {return flyBehavior;}
	public void setFlyBehavior(FlyBehavior flyBehavior) {this.flyBehavior = flyBehavior;}
	//叫
	public void quack(){System.out.println("I can quack!");}
	//游泳
	public void swim(){System.out.println("I can swim");}
	//外观,由于场景设定上外观是所有鸭子都不一样,所以交给子类实现
	public abstract void display();
}

//外观是绿色的头部,不会飞
public class MallardDuck extends Duck{
	@Override
	public void display() {System.out.println("I have a green head!");}
}

//外观是红色的头部,会飞
public class RedheadDuck extends Duck{
	@Override
	public void display() {System.out.println("I have a red head!");}
}
测试:

public class mainclass {
	public static void main(String[] args) {
		Duck red = new RedheadDuck();
		red.setFlyBehavior(new FlyWithWings());
		red.getFlyBehavior().fly();
		
		Duck Mallard = new  MallardDuck();
		Mallard.setFlyBehavior(new FlyWithNoWay());
		Mallard.getFlyBehavior().fly();
	}
}
//I'm flying with wings!
//I can not fly!
通过动态set进去行为,不同的鸭子就有不同的飞行方式,并且在运行时可以更换他的行为,当有这样一个问题:类不应该是状态跟行为的集合吗?把行为分离出来会不会不妥当?——那么我们可以这样讲:行为也有自己的实例变量,比如行为的属性(飞行的时候的速度、高度等),这便能组成一个类

优点:

1、策略模式提供了对“开闭原则”的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。
2、策略模式提供了管理相关的算法族的办法。
3、策略模式提供了可以替换继承关系的办法。
4、使用策略模式可以避免使用多重条件转移语句

缺点

1、客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
2、策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。(其实没有天上掉下来的馅饼,既然能解决问题,必然会付出一定的代价,这个代价是值得的)

3、Strategy子类可能不会用到超类的某些参数信息,或者不会用到环境类传进来的一些信息,假如有传进来的话,这样可能加大系统的开销

适用环境:
1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
2、一个系统需要动态地在几种算法中选择一种。(可能选择需要权衡时间、空间的算法)
3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
4、不希望客户端知道复杂的、与算法相关的数据结构,在具体策略类中封装算法和相关的数据结构,提高算法的保密性与安全性。


现在回到正题:状态模式跟策略模式的区别

1、最主要的区别在于:状态模式中环境类把行为委托给了状态类,而策略模式中策略跟环境类是独立的能相互替换——一种是双向关联、一种却具有高独立性

2、状态模式中,客户不需要关心状态,只需要关心行为,因为所有事情状态会帮你做好,然而策略模式却要求客户知道所有策略,这里的客户并不一定是用户,或者是使用你的框架的程序猿,不过即使是用户,有谁能保证他们不需要了解你的策略?

认真想象,他们两种模式还是很不一样的,一种是为了替换的灵活性,一种是描述内部状态并改变行为,所以,有时候类图一样,但不说明他们索设计出来的目的就是一样,当然没有任何模式是固定的,随机应变

关于状态模式最后的思考:状态模式有一个很明显的缺点就是依赖,因为他会操控环境类的行为,那么,假如我们把环境类实现一个相同的接口,以形参的方式传进状态类而不是以变量的形式set进来是否会改善情况?同理,或者需不需要写一个通用的回调接口CallBack,接口内有call的方法,通过传形参匿名内部类的方式new CallBack(){@Override public  void call(){//实现回调代码}}当然这是在环境类定义并调用的,在环境类统一只是callback.call(),这样子或许会降低耦合性?


参考:图说设计模式、设计模式、Head First 设计模式

以上是关于设计模式笔记:状态模式&策略模式的主要内容,如果未能解决你的问题,请参考以下文章

设计模式之策略模式和状态模式(strategy pattern & state pattern)

Android 设计模式 笔记 - 状态模式

Android 设计模式 笔记 - 状态模式

尝试使用片段保存夜间模式状态

设计模式—策略模式 状态模式

javascript设计模式-策略模式