文章目录状态模式结构实现特点状态模式场景在软件系统中有些对象也像水一样具有多种状态这些状态在某些情况下能够相互转换而且对象在同的状态下也将具有不同的行为。如果使用复杂的条件判断语句如if或switch来进行状态的判断和换操作这会导致代码的可维护性和灵活性下降特别是出现新状态的时候代码的扩展性很差客户端码也需要进行修改违反开闭原则。为了更好地对这些具有多种状态的对象进行设计我们可以使用一被称之为状态模式State Pattern的设计模式。人在幼年、童年、少年、中年、老年各个使其的形态都是不一样的工作期间上午、中午、下午、傍晚、深夜的工作状态也不一样人的心情不同时会有喜、怒、哀、乐手机在待机、通话、充电、游戏时的状态也不一样文章的发表会有草稿、审阅、发布状态⚠️状态模式和策略模式比较类似策略模式中的各个策略是独立的不关联的但是状态模式下的对象的各种状态可以是独立的也可以是相互依赖的比如上面关于文章的发布的例子普通用户的文章草稿发表之后被审阅审阅失败重新变成草稿管理用户的文章操作发布成功变成已发表状态, 发布失败重新变成草稿 状态模式就是在一个类的内部会有多种状态的变化因为状态变化从而导致其行为的改变在类的外部看上去这个类就像是自身发生了改变一样。 结构在状态模式结构图中包含如下几个角色Context环境类环境类又称为上下文类它是拥有多种状态的对象。由于环境类的状态存在多样性且在不同状态下对象的行为有所不同因此将状态独立出去形成单独的状态类。在环境类中维护一个抽象状态类State的实例这个实例定义当前状态在具体实现时它是一个State子类的对象。State抽象状态类它用于定义一个接口以封装与环境类的一个特定状态相关的行为在抽象状态类中声明了各种不同状态对应的方法而在其子类中实现类这些方法由于不同状态下对象的行为可能不同因此在不同子类中方法的实现可能存在不同相同的方法可以写在抽象状态类中。Concrete State具体状态类它是抽象状态类的子类每一个子类实现一个与环境类的一个状态相关的行为每一个具体状态类对应环境的一个具体状态不同的具体状态类其行为有所不同。在状态模式的使用过程中一个对象的状态之间还可以进行相互转换通常有两种实现状态转换的方式public:voidchangeState(){/判断属性值根据属性值进行状态转换if(value0){this-setState(newConcreteStateA());}elseif(value1){this-setState(newConcreteStateB());}}public:voidchangeState(Context*ctx){/根据环境对象中的属性值进行状态转换if(ctx-getValue()1){ctx-setState(newConcreteStateB());}elseif(ctx-getValue()2){ctx-setState(newConcreteStateC());}}实现// State.h// 抽象状态classSanji;classAbstractState{public:virtualvoidworking(Sanji*sanji)0;virtual~AbstractState(){}};// 上午状态classForenoonState:publicAbstractState{public:voidworking(Sanji*sanji)override;};// 中午状态classNoonState:publicAbstractState{public:voidworking(Sanji*sanji)override;};// 下午状态classAfternoonState:publicAbstractState{public:voidworking(Sanji*sanji)override;};// 晚上状态classEveningState:publicAbstractState{public:voidworking(Sanji*sanji)override;};#includeiostream#includeState.h#includeSanji.husingnamespacestd;voidForenoonState::working(Sanji*sanji){inttimesanji-getClock();if(time8){cout当前时间time点, 准备早餐, 布鲁克得多喝点牛奶...endl;}elseif(time8time11){cout当前时间time点, 去船头钓鱼, 储备食材...endl;}else{sanji-setState(newNoonState);sanji-working();}}voidNoonState::working(Sanji*sanji){inttimesanji-getClock();if(time13){cout当前时间time点, 去厨房做午饭, 给路飞多做点肉...endl;}else{sanji-setState(newAfternoonState);sanji-working();}}voidAfternoonState::working(Sanji*sanji){inttimesanji-getClock();if(time15){cout当前时间time点, 准备下午茶, 给罗宾和娜美制作爱心甜点...endl;}elseif(time15time18){cout当前时间time点, 和乔巴去船尾钓鱼, 储备食材...endl;}else{sanji-setState(newEveningState);sanji-working();}}voidEveningState::working(Sanji*sanji){inttimesanji-getClock();if(time19){cout当前时间time点, 去厨房做晚饭, 让索隆多喝点汤...endl;}else{cout当前时间time点, 今天过得很高兴, 累了睡觉了...endl;}}// Sanji.h#pragmaonce#includeState.hclassSanji{public:Sanji(){m_statenewForenoonState;}//工作函数在不同的时间状态下工作的内容也不同voidworking(){m_state-working(this);}//设置山治当前的状态voidsetState(AbstractState*state){if(m_state!nullptr){deletem_state;}m_statestate;}//设置当前的时间voidsetClock(inttime){m_clocktime;}//得到当前的时间intgetClock(){returnm_clock;}~Sanji(){deletem_state;}private://通过这整形的时钟变量来描述一天中当前这个时刻的时间点intm_clock0;// 时钟//通过这个状态指针来保存当前描述山治状态的对象AbstractState*m_statenullptr;};intmain(){Sanji*sanjinewSanji;// 时间点vectorintdata{7,10,12,14,16,18,22};for(constautoitem:data){sanji-setClock(item);sanji-working();}deletesanji;return0;}特点在实际开发中状态模式具有较高的使用频率在工作流和游戏开发中状态模式都得到了广泛的应用例如公文状态的转换、游戏中角色状态切换等。主要优点封装了状态的转换规则在状态模式中可以将状态的转换代码封装在环境类或者具体状态类中可以对状态转换代码进行集中管理而不是分散在一个个业务方法中。将所有与某个状态有关的行为放到一个类中只需要注入一个不同的状态对象即可使环境对象拥有不同的行为。允许状态转换逻辑与状态对象合成一体而不是提供一个巨大的条件语句块状态模式可以让我们避免使用庞大的条件语句来将业务方法和状态转换代码交织在一起。可以让多个环境对象共享一个状态对象从而减少系统中对象的个数。主要缺点状态模式的使用必然会增加系统中类和对象的个数导致系统运行开销增大。状态模式的结构与实现都较为复杂如果使用不当将导致程序结构和代码的混乱增加系统设计的难度。状态模式对“开闭原则”的支持并不太好增加新的状态类需要修改那些负责状态转换的源代码否则无法转换到新增状态修改某个状态类的行为也需修改对应类的源代码。适用环境如果对象需要根据当前自身状态进行不同的行为 同时状态的数量非常多且与状态相关的代码会频繁变更或者类对象在改变自身行为时需要使用大量的条件语句时可使用状态模式。如果对象需要根据自身当前状态进行不同行为同时状态的数量非常多且与状态相关的代码会频繁变更的话可使用状态模式。如果某个类需要根据成员变量的当前值改变自身行为从而需要使用大量的条件语句时可使用该模式。当相似状态和基于条件的状态机转换中存在许多重复代码时可使用状态模式。