今天学习了状态模式,做个总结。 本文多出摘选自《设计模式之禅》,只留作学习复习只用。
为了更好地了解学习状态模式,先认识一个小例子——电梯。
举个例子
电梯大家应该都很熟悉,日常生活用得到,电梯的日常上下路逻辑大家应该也很熟悉,就比如我们每个人去坐电梯,一个最简单最基础的流程是:
一个人来到电梯前按按钮上下楼——>电梯开门——>进去关上门按按钮——>电梯运行向上向下——>到达目的楼层,电梯停止。
在上边的一个基本流程中,包含了开门,关门,停止,运行四个基本动作,包含了敞开着门的状态,闭上门状态,停止不动的状态,运行状态,四个基础状态,电梯的一个基本工作逻辑就是在作出行为之后,状态不断地进行变换。
接下来,如果我们按照电梯的这个简单运行逻辑设计一下电梯模型。
模型设计
模型设计,最简单的就是设计一个电梯的抽象接口ILift,有四个基本动作,开门,关门,停止,运行,再来几个具体实现类Lift,再加一个实现类Client。
这样具体类去实现抽象接口的抽象方法就可以最简单的实现该模型。
但是我们知道,电梯运行的话不是只有这几个方法这么简单,他应该包含着更复杂一点的逻辑,至少在上边 基础流程中,包含着四个状态之间的不停转换,以及夹杂在四个状态变换中的电梯的基础动作行为。 因此我们给电梯模型设计一下更复杂的逻辑。
更具体的模型
在开始设计更具体的模型的时候,我们先把四个状态和和四个动作之间的关系理顺。
如下表:
开门 | 关门 | 运行 | 停止 | |
开门状态 | x | √ | × | × |
闭门状态 | √ | × | √ | √ |
运行状态 | × | × | × | √ |
停止状态 | √ | × | √ | × |
(上表主对角线全部都是错号,例如: 开门状态下不允许有开门这个动作,因为门已经是开着的)
这样就比较清晰的来看出来他们之间的关系逻辑。
我们将这些逻辑丰富我们的电梯模型。
加个图-状态转换图
基于上边的表格我们作出状态变换图
定义四个状态常数表示电梯状态,电梯的具体实现类中,定义四个函数来表示电梯的在不同状态下的行为。
具体代码
抽象电梯接口ILift
interface ILift{
public final static int open_state=1;
public final static int close_state=2;
public final static int run_state=3;
public final static int stop_state=4;
public void open();
public void close();
public void run();
public void stop();
}
具体电梯实现类
class Lift implements ILift{
private int state;
Lift(int s)
{
state=s;
}
@Override
public void open() {
switch (state)
{
case open_state: break;
case close_state: state=open_state;
OpenD(); break;
case run_state: break;
case stop_state: state=open_state; OpenD(); break;
}
}
@Override
public void close() {
switch (state)
{
case open_state: state=close_state;CloseD(); break;
case close_state: break;
case run_state: break;
case stop_state: break;
}
}
@Override
public void run() {
switch (state)
{
case open_state: break;
case close_state: state=run_state; RunL(); break;
case run_state: break;
case stop_state: state=run_state; RunL();break;
}
}
@Override
public void stop() {
switch (state)
{
case open_state: break;
case close_state: state=stop_state; StopL(); break;
case run_state: state=stop_state;StopL(); break;
case stop_state: break;
}
}
public void OpenD()
{
System.out.println("电梯开门了!");
}
public void CloseD()
{
System.out.println("电梯关门了!");
}
public void RunL()
{
System.out.println("电梯开始运行!");
}
public void StopL()
{
System.out.println("电梯停下了!");
}
}
场景测试类
public class Client {
public static void main(String[] args) {
ILift lift=new Lift(2);
lift.open();
lift.close();
lift.run();
lift.stop();
}
}
效果图
基于上述的我们改进的电梯类,我们基本上实现了开头所讲的那个基本电梯流程,包括四个状态在四个行为下的不同切换。
但是我们仔细思考一下,如果我们在想将上述的模型设计的更具体一点,比如添加一个电梯维修,电梯故障之类的功能,又要怎么去添加呢?
比如电梯故障这一方面,假如出现火灾之类的情况,那我们应该怎么修改上述模型? 是不是要再添加一个电梯故障状态? 或是在电梯四个动作里再加上判断条件来判断电梯故障从而作出相应动作来处理他?无论选择哪种决策,我们上边实现的代码就要大改一遍,这明显不符合开闭原则。这还仅仅只是一个模块的加入,若是之后再加上电梯的通电模式,断电模式等等,是不是要继续不停修改代码? 毫无疑问,这样的工程开发是低级的。
缺陷与更多的问题
我们简单地总结一下上述模型所展现出来的缺陷。
1. 扩展性与维护性较差
就上述模型来讲,无论是添加新状态还是修改现有状态的逻辑,无疑,我们至少要修改上述open(),close()。。。之类方法内容,这样无论是扩展性还是维护性都是极差的。
2. 判断条件太多
上述代码还有的一个问题是判断条件太多,只不过我们使用了switch来代替判断条件,但很容易看出来我们代码的冗余太多,判断条件太多。
那是不是有一种方法来更好的实现电梯例子呢?
于是我们就引出了设计模式中的状态模式。
状态模式
定义:当一个对象内在状态改变时允许其改变行为,这个对象看起来像改变了其类。
是不是感觉上述定义有些抽象?
我们把上述电梯例子加入进去,电梯模型有四个状态,四个动作,每个状态下有些行为允许去做,有些行为不被允许去做(具体参见上边表格),就比如说开门状态,当电梯处于开门状态下,她被允许的动作仅仅只有关门,可是当他通过关门去改变其状态变为闭门状态时,他被允许的动作反而变成了开门,运行,停止三个动作,也就是说随着电梯对象他的开门状态与闭门状态切换时,他的行为也发生了变化,这就是上述定义中的对象内在状态改变时允许其改变行为,更通俗一点就是每个状态下都有其特殊的行为动作,状态发生变化,行为也就变得不同。
OK 了解了定义之后我们继续看状态模式。
状态模式的核心就是封装,状态发生变更引起了行为变化,从外部看就像是对象对应的类发生了变化一样。通用类图如下:
解释一下这三个角色:
State :抽象状态对象,负责对象状态定义,并且封装环境角色来实现状态转换。一般为抽象类或者接口。
ConcreteState:具体状态角色, 需要完成两个职责,本状态的行为管理和趋向状态管理,就是本状态下啊该干的事情,与本状态该如何过渡到其他状态。
Context : 环境角色,定义客户端所需要的接口,并且负责具体状态的切换。
了解了上述基本定义之后,我们应该可以使用状态模式来设计我们的电梯模型了。
改进电梯模型
1.我们把电梯模型的状态抽象成一个抽象类LiftState,每个具体的状态类就是这个抽象类的子类,要是再有新的状态加入进来就可以再继续添加子类,而不需要修改原有的状态类,这样就解决了可扩展性问题。
2. 在使用一个环境角色串联起来这些状态,实现电梯状态的切换。每个电梯状态都有其特殊的行为动作和不同的状态转换机制,这样我们就可以通过切换环境状态来实现每个电梯状态,这样就减少了判断条件的产生。
电梯类图
代码设计
Liftstate 类:
package Statement;
public abstract class LiftState {
protected Context context;
public abstract void open();
public abstract void close();
public abstract void run();
public abstract void stop();
public Context getContext() {
return context;
}
public void setContext(Context context) {
this.context = context;
}
}
OpeningState
package Statement;
public class OpeningState extends LiftState{
@Override
public void open() {
System.out.println("电梯开门了!");
}
@Override
public void close() {
context.setCurstate(Context.close);
context.getCurstate().close();
}
@Override
public void run() {
}
@Override
public void stop() {
}
}
Closingstate
package Statement;
import javafx.scene.paint.Stop;
public class ClosingState extends LiftState{
@Override
public void open() {
context.setCurstate(Context.open);
context.open();
}
@Override
public void close() {
System.out.println("电梯关门了!");
}
@Override
public void run() {
context.setCurstate(Context.run);
context.run();
}
@Override
public void stop() {
context.setCurstate(Context.stop);
context.stop();
}
}
RunningState
package Statement;
public class RunningState extends LiftState {
@Override
public void open() {
}
@Override
public void close() {
}
@Override
public void run() {
System.out.println("电梯开始运行了!");
}
@Override
public void stop() {
context.setCurstate(Context.stop);
context.getCurstate().stop();
}
}
StoppingState
package Statement;
public class StoppingState extends LiftState {
@Override
public void open() {
context.setCurstate(Context.open);
context.getCurstate().open();
}
@Override
public void close() {
}
@Override
public void run() {
context.setCurstate(Context.run);
context.getCurstate().run();
}
@Override
public void stop() {
System.out.println("电梯停止了!");
}
}
Context环境角色类:
package Statement;
public class Context {
public final static OpeningState open=new OpeningState();
public static final ClosingState close=new ClosingState();
public static final RunningState run=new RunningState();
public static final StoppingState stop=new StoppingState();
private LiftState curstate;
public LiftState getCurstate() {
return curstate;
}
public void setCurstate(LiftState curstate) {
this.curstate = curstate;
this.curstate.setContext(this);
}
public void open() {
curstate.open();
}
public void close() {
curstate.close();
}
public void run() {
curstate.run();
}
public void stop() {
curstate.stop();
}
}
Client测试类
package Statement;
public class Client {
public static void main(String[] args) {
Context con=new Context();
con.setCurstate(Context.close);
con.open();
con.close();
con.run();
con.stop();
}
}
效果图:
状态模式优点:
1. 结构清晰,避免可判断语句的使用,避免了程序的复杂性,提高了可维护性。
2.遵循设计原则,体现了开闭原则与单一职责原则。
3.封装性很好,这是状态模式的基本要求,状态变换放置在类的内部来实现,外部的调用不知道如何实现状态与行为的变换。
缺点:
若一个事物有多个状态,就会导致子类太多了,产生类膨胀。
适用场景
1. 行为随状态而改变的场景
这是状态模式的根本出发点,例如权限设计,比如视频网站的普通会员,Vip,超级Vip,权限不同,能看的东西就不同。
2.条件,分支判断语句的替换者
程序中有大量判断语句, 可以通过扩展子类来实现条件的判断处理。
匆匆写完,有问题欢迎指正!