状态(State)模式

状态模式是一种对象行为模式,它允许一个对象在内部状态改变时改变其行为。例如,在TCP连接中,对象根据连接状态(如监听、已建立、已关闭等)执行不同的操作。该模式通过引入状态类和具体状态子类,将状态相关的行为封装起来,使得状态转换更加明确,同时避免了复杂的条件语句。适用场景包括对象的行为取决于其状态以及需要根据状态改变行为的情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

状态(State)模式

隶属类别——对象行为模式


1. 意图

允许一个对象在其内部状态发生改变时改变它的行为。对象看起来似乎修改了它的类。

2. 别名

状态对象(Object for States)

3. 动机

考虑一个表示网络连接的类TCPConnection.一个TCPConnection对象的状态处于若干不同状态之一:连接已建立(Established)、正在监听(Listening)、连接已关闭(Closed).当一个TCPConnection对象收到其他对象的请求时,它根据自己的当前状态作出不同的反应。例如,一个Open请求的结果依赖于该连接时处于连接已关闭状态还是连接已建立状态。State模式描述了TCPConnection如何在每一种状态下表现出不同的行为。

这一模式的关键四小时引入一个称为TCPState的抽象类来表示网络的连接状态。TCPState类为各表示不同的操作状态的子类声明了一个公共的接口。TCPState的子类实现与特定状态相关的行为。例如,TCPEstabilished和TCPClosed类分别实现了特定于TCPConnection的连接已建立状态和连接已关闭的行为。

在这里插入图片描述

TCPConnection类维护一个表示TCP连接当前状态的状态对象(一个TCPState子类的实例)。TCPConnection类将所有与状态相关的请求委托给这个状态对象。TCPConnection使用它的TCPState子类实例来执行特定于连接状态的操作。

一旦连接状态改变,TCPConnection对象就会改变它所使用的状态对象。例如当连接从已建立状态转变为已关闭状态是,TCPConnection会用一个TCPClosed的实例来代替原来的TCPEstablished的实例。

4. 适用性

在以下两种情况下均可使用State模式:

  • 一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为。
  • 一个操作中含有庞大的多分支的条件语句,切这些分支依赖于该对象的状态。这个状态通常用一个或多个枚举常量表示。通常,有多个操作包含这一相同的条件结构。State模式将每一个条件分支分入一个独立的类中。这使得你可以根据对象的自身情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化。

5. 结构

在这里插入图片描述

6. 参与者

  • Context(环境,如TCPConnection)
    • 定义客户感兴趣的接口。
    • 维护一个ConcreteState子类的实例,这个实例定义当前状态。
  • State(状态,如TCPState)
    • 定义一个接口以封装与Context的一个特定状态相关的一些行为.
  • ConcreteState subclasses(具体状态子类,如TCPEstablished,TCPListen,TCPClosed)
    • 每一个子类实现了与Context的一个状态相关的一些行为。

7. 协作

  • Context将与状态相关的请求委托给当前的ConcreteState对象处理。
  • Context可将自身作为一个参数传递给处理该请求的状态对象。这使得状态对象在必要时可以访问Context.
  • Context是客户使用的主要接口。客户可用状态对象来配置一个Context,一旦一个Context配置完毕,它的客户不再需要直接与状态对象打交道。
  • Context或ConcreteState都可决定哪个状态是另外那一个的后继者,以及在是在何种条件下进行状态转换。

8. 效果

State模式有下面一些效果:

    1. 它将特定状态相关的行为局部化,并将不同的状态分割开来 State模式将所有与一个特定的状态相关的行为都放入一个对象中。因为所有与状态相关的代码都存在于某一个State子类中,所以通过定义新的子类可以很容易的增加新的状态和转换。

      另一个方法是使用数据值定义内部状态并且让Context来显式地检查这些数据,但这样将会使整个Context的实现看起来很相似的条件语句或Case语句,增加一个新的状态可能需要改变若干个操作,这使得维护变得复杂了。

      State模式避免了这个问题,但可能会引入另一个问题,因为该模式将不同状态的行为分布在多个State子类中,这就增加了子类的数目,相对于单个类的实现来说不够紧凑。但是如果有许多状态时这样的分布实际上更好一些,否则需要使用巨大的条件语句。

      正如很长的过程一样,巨大的条件语句是不受欢迎的。它们形成一大整块并且使得代码不够清晰,这又使得它们修改和扩展。State模式提供了一个更好的方法来组织与特定状态相关的代码。决定状态转移的逻辑不在单块的if或switch语句中,而是分布在State子类之间。将每一个状态转化和动作封装到一个类中,就把着手点从执行状态提高到整个对象的状态。这将使代码结构化并使意图更加清晰。

    1. 它使得状态转化显式化 当一个对象仅以内部数据值来定义当前状态时,其状态仅表现为对一些变量的赋值,这不够明确,为不同的状态引入独立的对象使得转换更加明确了。而且,State对象可保证Context不会内部发生内部状态不一致的情况,因为从Context的角度看,状态转换时原子的——只需重新绑定一个变量(即Context的State对象变量),而无需为多个变量赋值。
    1. State对象可被共享 如果State对象没有实例变量——即它们表示的状态完全以它们的类型来编码——那么各Context对象就可以共享一个State对象。当状态以这种方式被共享,它们必然是没有内部状态,只有行为的轻量级对象。
    1. 类的数目急剧增加(缺点) 当我们把状态转换和动作封装到一个类中时,类的数目也在不断增多。

9. 实现

实现State模式有多方面的考虑:

    1. 谁定义状态转换 State模式不指定哪一个参与者定义状态转换准则。如果该准则是固定的,那么它们可在Context中完全实现。然而若让State子类自身制定它们的后继状态以及何时进行转化,通常更灵活更合适。这需要Context增加一个接口,让State对象显式地设定Context的当前状态。

      用这种方法分散转换逻辑可以很容易地定义新的State子类来修改和扩展该逻辑。这样做的一个缺点是,一个State子类至少拥有一个其他子类的信息,这就在各子类之间产生了实现依赖。

    1. 基于表的另一种方法 在C++ Programming Style中,Cargil描述了另一种将结构加载在状态驱动的代码上的方法:他使用表将输入映射到状态转化。对每一个状态,一张表将每一个可能的输入映射到一个后继状态。实际上,这种方法将条件代码映射为查找表(How to do this???)

      表的主要好处是它们的规则性:你可以通过更改数据而不是更改程序代码来改变状态转换的准则、然而它也有一些缺点:

      • 对表的查找通常不如函数调用效率高。
      • 用统一的、表格形式表示转换逻辑是的转换准则变得不够明确而难以理解。
      • 通常难以加入伴随状态的一些动作。表驱动的方法描述了状态和他们之间转换,但必须扩充这个机制以便在每一个转换上能够进行任意的计算。

      表驱动的状态机和State模式的主要区别可以被终结如下:State模式对状态相关的行为进行建模,而表驱动的方法着重与定义状态转换。

    1. 创建和销毁State对象 一个常见的值得考虑的实现上的权衡是,究竟是 1. 仅当需要State对象时才创建它们并随后销毁它们,还是2.提前创建它们并且始终不销毁它们?

      当将要进入的状态在运行时是不可知的,并且上下文不经常改变时,第一种选择较为可取。这种方法避免创建不会被用到的对象,如果State对象存储大量的信息是这一点很重要。当状态改变很频繁是,第二种方法较好。在这种情况下最好避免销毁状态,因为可能很快再次需要用到它们。此时可以预先一次付清各个状态的开销,并且在运行过程中根本不存在销毁状态对象的开销。但是这种方法可能不太方面,因为Context必须保存对所有可能会进入的那些状态的引用。

    1. 使用动态继承 改变一个响应特定请求的行为可以用在运行时刻改变这个对象的类的方法实现,但这在大多数面向对象程序设计语言中都是不可能的。Self和其他一些基于委托的语言的却是例外,它们提供了这种机制,从而直接支持State模式。Self中的对象可将操作委托给其他对象以达到某种形式的动态继承。在运行时刻改变委托的目标有效地改变了继承的结构。这一机制允许对象改变它们的行为,也就是改变他们的类。

10. 代码示例

首先设置State——State.java

public interface State {
	void insertQuarter();	
	void ejectQuarter();	
	void turnCrack();
	void dispense();
	void refill(int count);
}

以及ConcreteState——SoldState.java & SoldOutState.java & NoQuarterState.java & HasQuarterState.java

SoldState.java

public class SoldState implements State{
	GumballMachine gumballMachine;
	
	public SoldState(GumballMachine gumballMachine) {
		this.gumballMachine = gumballMachine;
	}
	
	@Override
	public void insertQuarter() {
		System.out.println("Please wait, we're already giving you a gumball");
	}
	
	@Override
	public void ejectQuarter() {
		System.out.println("Sorry, you already turned the crank");
	}
	
	@Override
	public void turnCrack() {
		System.out.println("Turning twice doesn't get you another gumball");
	}
	
	@Override
	public void dispense() {
		gumballMachine.releaseBall();
		if (gumballMachine.getCount() > 0) {
			gumballMachine.setState(gumballMachine.getNoQuarterState());
		}else {
			System.out.println("Oops, out of gumballs !");
			gumballMachine.setState(gumballMachine.getSoldOutState());
		}
	}
	
	@Override
	public void refill(int count) {
		System.out.println("The machine is giving a gumball, now can't refill");
	}
	
	@Override
	public String toString() {
		return "soldState";
	}
}

SoldOutState.java

public class SoldOutState implements State{
	GumballMachine gumballMachine;
	
	public SoldOutState(GumballMachine gumballMachine) {
		this.gumballMachine = gumballMachine;
	}
	
	@Override
	public void insertQuarter() {
		System.out.println("Sorry,Please don't insert quarter,there is no gumball in the machine");
	}
	
	@Override
	public void ejectQuarter() {
		System.out.println("You don't insert quarter at all");
	}
	
	@Override
	public void turnCrack() {
		System.out.println("You truned , but there is no gumball in the machine");
	}
	
	@Override
	public void dispense() {
		System.out.println("No gumball in the machine can be dispense");
	}
	
	@Override
	public void refill(int count) {
		System.out.println("Adding " + count + " gumballs to the machine");
		gumballMachine.setCount(count);
		gumballMachine.setState(gumballMachine.getNoQuarterState());		
	}
	
	@Override
	public String toString() {
		return "soldState";
	}
}

HasQuarterState.java

public class HasQuarterState implements State{
	GumballMachine gumballMachine;
	public HasQuarterState(GumballMachine gumballMachine) {
		this.gumballMachine = gumballMachine;
	}
	
	@Override
	public void insertQuarter() {
		System.out.println("You can't insert another quarter");
	}
	
	@Override
	public void ejectQuarter() {
		System.out.println("Quarter returned ");
		gumballMachine.setState(gumballMachine.getNoQuarterState());
	}
	
	@Override
	public void turnCrack() {
		System.out.println("You turned ...");
		gumballMachine.setState(gumballMachine.getSoldState());
	}
	
	@Override
	public void dispense() {
		System.out.println("No gumball dispensed");
	}
	
	@Override
	public void refill(int count) {
		System.out.println("You don't need to refill");
	}
	
	@Override 
	public String toString() {
		return "HasQuarterState";
	}
}

NoQuarterState.java

public class NoQuarterState implements State{
	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 turnCrack() {
		System.out.println("You turned , but there's no quarter");
	}
	
	@Override
	public void dispense() {
		System.out.println("You need to pay first");
	}
	
	@Override
	public void refill(int count) {
		System.out.println("Machine don't need to refill");
	}
	
	@Override
	public String toString() {
		return "NoQuarterState";
	}
}

接下来是Context——GumballMachine.java

public class GumballMachine {
	private State soldOutState;
	private State noQuarterState;
	private State hasQuarterState;
	private State soldState;
	private State state = soldOutState;
	private int count = 0;
	
	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;
		}
	}
	
	public void insertQuarter() {
		state.insertQuarter();
	}
	
	public void ejectQuarter() {
		state.ejectQuarter();
	}
	
	public void turnCrank() {
		state.turnCrack();
		state.dispense();
	}
	
	public void refill(int count) {
		state.refill(count);
	}
	
	public void setState(State state) {
		this.state = state;
	}
	
	public void setCount(int count) {
		this.count = count;
	}
	
	public int getCount() {
		return count;
	}
	
	public void releaseBall() {
		System.out.println("A gumball comes rolling out the slot...");
		if (count != 0) {
			count = count - 1;
		}
	}
	
	public State getNoQuarterState() {
		return noQuarterState;
	}
	
	public State getHasQuarterState(){
		return hasQuarterState;
	}
	
	public State getSoldOutState() {
		return soldOutState;
	}
	
	public State getSoldState() {
		return soldState;
	}
	
	public String toString() {
		StringBuffer result = new StringBuffer();
		result.append("\nMighty Gumball, Inc.");
		result.append("\nJava-enabled Standing Gumball Model #2004");
		result.append("\nInventory: " + count + " gumball");
		if (count != 1) {
			result.append("s");
		}
		result.append("\n");
		result.append("Machine is " + state + "\n");
		return result.toString();
	}
}

以及测试类Client——GumballMachineTestDrive.java

public class GumballMachineTestDrive {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		GumballMachine gumballMachine = new GumballMachine(4);
		
		System.out.println(gumballMachine);
		
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		
		System.out.println(gumballMachine);
		
		gumballMachine.insertQuarter();
		gumballMachine.ejectQuarter();
		gumballMachine.turnCrank();
		
		System.out.println(gumballMachine);
		
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.ejectQuarter();
		
		System.out.println(gumballMachine);
		
		gumballMachine.insertQuarter();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		gumballMachine.turnCrank();
		gumballMachine.insertQuarter();
		gumballMachine.turnCrank();
		
		System.out.println(gumballMachine);
		
		gumballMachine.refill(10);
		System.out.println(gumballMachine);	
	}
}

以及测试结果


Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 4 gumballs
Machine is NoQuarterState

You inserted a quarter
You turned ...
A gumball comes rolling out the slot...

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 3 gumballs
Machine is NoQuarterState

You inserted a quarter
Quarter returned 
You turned , but there's no quarter
You need to pay first

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 3 gumballs
Machine is NoQuarterState

You inserted a quarter
You turned ...
A gumball comes rolling out the slot...
You inserted a quarter
You turned ...
A gumball comes rolling out the slot...
You haven't inserted a quarter

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 1 gumball
Machine is NoQuarterState

You inserted a quarter
You can't insert another quarter
You turned ...
A gumball comes rolling out the slot...
Oops, out of gumballs !
Sorry,Please don't insert quarter,there is no gumball in the machine
You truned , but there is no gumball in the machine
No gumball in the machine can be dispense
You truned , but there is no gumball in the machine
No gumball in the machine can be dispense
Sorry,Please don't insert quarter,there is no gumball in the machine
You truned , but there is no gumball in the machine
No gumball in the machine can be dispense

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 0 gumballs
Machine is soldState

Adding 10 gumballs to the machine

Mighty Gumball, Inc.
Java-enabled Standing Gumball Model #2004
Inventory: 10 gumballs
Machine is NoQuarterState

最后是UML类图:

在这里插入图片描述

11. 已知应用

State模式在TCP连接协议上有着的应用。

HotDraw和Unidraw中的绘图编辑器框架都使用了这一技术。它使得客户可以很容易地定义新类型的工具。在HotDraw中,DrawingController类将请求转发给当前的Tool对象。在Unidraw中,相应的类是Viewer和Tool。下图简要地描述了Tool和DrawingController的接口。
在这里插入图片描述

12. 相关模式

  • Flyweight模式: Flyweight解释了何时共享状态对象。
  • Singleton模式:一般状态对象都是Singleton。

13. 设计原则口袋

  • 封装变化
  • 类应该对修改关闭,对扩展开放
  • 针对接口编程,不针对实现编程
  • 为交互对象的松耦合设计而努力
  • 多用组合少用继承
  • 依赖抽象,不要依赖具体类
  • 只和密友聊天
  • 好莱坞原则——别找我,我会找你
  • 单一责任原则——类应该只有一个改变的理由。

14. 参考文献

《设计模式:可复用面向对象软件的基础》

《HeadFirst设计模式》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值