状态模式

54 阅读4分钟

定义

一个对象在其内部状态改变时改变其行为,使得对象在不同状态下有不同的行为表现。通过将每个状态封装成独立的类,可以避免使用大量的条件语句来实现状态切换。

状态模式的特点

  • 为什么要使用状态模式

    当程序中有个对象希望根据不同的状态有着不同的行为,对于这个需求,我们使用 if...else 语句,很简单就能实现,但是一旦对象的行为多了以后,仅仅只使用 if...else 语句会让情况变得很复杂。
    假设一个角色拥有站立,跳跃,行走,卧倒,攻击等行为,我们在执行某一个角色行为的时候,通常要考虑这个角色在不同状态下的不同反应,比如角色在跳跃的时候执行跳跃是无效的(除非有二段跳),又或者在执行卧倒的时候也要考虑是否在空中(空中一般是不给卧倒的),且还需要考虑角色是否正在攻击,行走等等状态,是否要打断当前状态还是给出新的状态,比如说卧倒攻击?应对以上问题的时候,使用传统的 if...else 语句显然会让整个代码变大庞大而复杂(判断语句多且重复),且不利于维护何任何扩展。

  • 解决问题

    首先解决代码的依赖问题,我们可以把行为逻辑单独抽离出来的话作为一个行为类(单独抽离出来利于维护),但是任然还面临着庞大的状态逻辑判断;回看上文,角色在执行某个行为的时候总是要判断角色的状态,且一个行为通常需要判断其他所有的状态,这很明显冗余了,于是我们便可以把状态提取出来单独作为一个字段,并把之前的行为类改造成一个状态类,然后在执行某个行为的时候,仅仅是让角色切换到这个行为的状态,由这个状态来决定做什么,其他不管;这样的话我们就只需要管理角色的状态了,一下逻辑就轻便了许多,这就是状态模式的雏形了。

  • 有限状态机

    但是还有一个问题需要解决,就是我只切换一个状态,却要考虑很多不相关的状态,比如我们在跳跃的时候还要去防止玩家执行卧倒,本来在跳跃状态中是不需要接受卧倒的输入指令,但为什么还需要额外去判断呢?因为现在的接受输入指令的逻辑还是角色类处理的,角色在接收到卧倒输入指令时,此时角色虽然在跳跃状态,但还是需要加个判断来屏蔽卧倒输入指令的;解决的办法是我们可以把接收输入指令和切换状态逻辑也放入到状态类中去,让角色类什么都不管,一切交给当前的状态来处理,因为不同的状态不单单指能做不同事情,而是完全的独立的状态,它应该有自己的一套行为逻辑,包括接收输入指令。 此刻我们再次整理所有逻辑

    1. 角色生成,初始化状态为站立状态;
    2. 站立状态可以接收跳跃指令,行走指令,卧倒指令;
    3. 接收到跳跃指令,角色切换到跳跃状态,跳跃状态可以接收攻击指令,并且在下落到地面时切换到站立状态;
    4. 下落到地面,角色切换为站立状态;
    5. 接收到卧倒指令,切换到卧倒状态,卧倒状态可以接收站立指令,攻击指令; 以上就是有限状态机的流程,相比于之前的庞大且复杂的判断逻辑,是不是就变得很清晰明了,这就是有限状态机解决的问题。

状态模式结构

  • Context:上下文环境,维护当前的状态
  • IState:抽象状态角色
  • ConcreteState:具体状态角色,实现状态所对应的行为
  • 其状态模式 UML 类图如下:

状态模式UML类图.png

状态模式具体实现

  • IState.cs

    public interface IState {
    	void Handle(Context context);
    }
    
  • ConcreteState.cs

    public class ConcreteStateA : IState{
    	public void Handle(){
    		Debug.Log("State A");
    	}
    }
    
    
    
    public class ConcreteStateB : IState{
    	public void Handle(){
    		Debug.Log("State B");
    	}
    }
    
  • Context.cs

    public class Context{
    	IState _state;
    	
    	public Context(){
    	}
    	
    	public void SetState(IState state){
    		_state = state;
    	}
    	
    	public IState GetState(){
    		return _state;
    	}
    
    	public void Handle(){
    		_state.Handle();
    	}
    }
    
  • Main.cs

    public class Main{
    	public static void main(String[] args){
    		Context context = new Context();
    		
    		context.SetState(new ConcreteStateA());
    		context.Handle(); // Output: State A
    
    		context.SetState(new ConcreteStateB());
    		context.Handle(); // Output: State B
    	}
    }