初识设计模式之状态模式

  在软件开发过程中,应用程序中的部分对象可能会根据不同的情况做出不同的行为,我们把这种对象称为有状态的对象,而把影响对象行为的一个或多个动态变化的属性称为状态。当有状态的对象与外部事件产生互动时,其内部状态就会发生改变,从而使其行为也发生改变。如人都有高兴和伤心的时候,不同的情绪有不同的行为,当然外界也会影响其情绪变化。

  对这种有状态的对象编程,传统的解决方案是:将这些所有可能发生的情况全都考虑到,然后使用 if-else 或 switch-case 语句来做状态判断,再进行不同情况的处理。但是显然这种做法对复杂的状态判断存在天然弊端,条件判断语句会过于臃肿,可读性差,且不具备扩展性,维护难度也大。且增加新的状态时要添加新的 if-else 语句,这违背了“开闭原则”,不利于程序的扩展。

  以上问题如果采用“状态模式”就能很好地得到解决。状态模式的解决思想是:当控制一个对象状态转换的条件表达式过于复杂时,把相关“判断逻辑”提取出来,用各个不同的类进行表示,系统处于哪种情况,直接使用相应的状态类对象进行处理,这样能把原来复杂的逻辑判断简单化,消除了 if-else、switch-case 等冗余语句,代码更有层次性,并且具备良好的扩展力。

什么是状态模式?

  对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。

优点

  • 结构清晰,状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”。
  • 将状态转换显示化,减少对象间的相互依赖。将不同的状态引入独立的对象中会使得状态转换变得更加明确,且减少对象间的相互依赖。
  • 状态类职责明确,有利于程序的扩展。通过定义新的子类很容易地增加新的状态和转换。

缺点

  • 状态模式的使用必然会增加系统的类与对象的个数。
  • 状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码的混乱。
  • 状态模式对开闭原则的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源码,否则无法切换到新增状态,而且修改某个状态类的行为也需要修改对应类的源码。

状态模式的结构与实现

  状态模式把受环境改变的对象行为包装在不同的状态对象里,其意图是让一个对象在其内部状态改变的时候,其行为也随之改变。

模式的结构

  状态模式包含以下主要角色。
  环境(Context)角色:也称为上下文,它定义了客户端需要的接口,内部维护一个当前状态,并负责具体状态的切换。
  状态(State)角色:定义一个接口,实现环境对象中的特定状态所对应的行为,可以有一个或多个行为,并且在需要的情况下进行状态切换。

例子:用“状态模式”设计一个学生成绩的状态转换程序。

本实例包含了“不及格”“中等”和“优秀” 3 种状态,当学生的分数小于 60 分时为“不及格”状态,当分数大于等于 60 分且小于 90 分时为“中等”状态,当分数大于等于 90 分时为“优秀”状态,我们用状态模式来实现这个程序。

首先,先定义个State状态结构体,其中包括分数等级属性grade,以及状态切换方法changestate(Context p_this)。

然后,分别定义“不及格”Low、“中等”Middle 和“优秀”High三个State结构体对象,通过函数指针赋值实现changestate(Context p_this)方法,并根据分数进行状态转换。

最后,Context环境结构体,其中包含了姓名、当前分数、当前状态对象属性,以及初始化方法init(struct Context *p_this),加减分方法changescore(struct Context *p_this,int score)和获取当前分数方法getscore(struct Context p_this),客户类通过该方法来改变成绩状态。

代码实现

1、创建State和Context结构体

//Interface.h
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct Context Context;
typedef struct State State;

struct State
{
    char grade[20];
    struct State* (*changestate)(struct Context p_this);
};
struct Context
{
	char name[20];
	int score;
    State* state;
    void (*init)(struct Context *p_this);
    void (*changescore)(struct Context *p_this,int score);
    void (*getscore)(struct Context p_this);
};

void init(Context *p_this);

2、三种不同状态的定义和状态切换方法实现

//design.c
#include "Interface.h"

State* highchangestate(Context p_this);
State* middlechangestate(Context p_this);
State* lowchangestate(Context p_this);

State High=
{
	"High",
    highchangestate,
};
State Middle=
{
	"Middle",
    middlechangestate,
};
State Low=
{
	"Low",
    lowchangestate,
};

State* highchangestate(Context p_this)
{
	if(p_this.score<60)
		return &Low;
    else if(p_this.score<90)
		return &Middle;
    else
		return &High;
}
State* middlechangestate(Context p_this)
{
	if(p_this.score<60)
		return &Low;
    else if(p_this.score<90)
		return &Middle;
    else
		return &High;
}
State* lowchangestate(Context p_this)
{
	if(p_this.score<60)
		return &Low;
    else if(p_this.score<90)
		return &Middle;
    else
		return &High;
}

void changescore(Context *p_this,int score)
{
	p_this->score+=score;
	if(p_this->score<0)
		p_this->score=0;
	else if(p_this->score>100)
		p_this->score=100;
    p_this->state=p_this->state->changestate(*p_this);
    p_this->getscore(*p_this);
}

void getscore(Context p_this)
{
	printf("name:%s  grade:%s  score:%d\n",p_this.name,p_this.state->grade,p_this.score);
}

void init(Context *p_this)
{
    p_this->score=60;
    p_this->state=&Middle;
    p_this->changescore=changescore;
    p_this->getscore=getscore;
}

3、StatePatternDemo定义并初始化Context对象,修改分数进行状态切换

//StatePatternDemo.c
#include "Interface.h"

Context XiaoMing={
	.name="XiaoMing",
	.init=init,
};
Context XiaoLi={
	.name="XiaoLi",
	.init=init,
};

int main()
{
	XiaoMing.init(&XiaoMing);
    XiaoLi.init(&XiaoLi);
    XiaoMing.changescore(&XiaoMing,30);
    XiaoMing.changescore(&XiaoMing,-70);
    XiaoMing.changescore(&XiaoMing,40);
    XiaoLi.changescore(&XiaoLi,32);
    XiaoLi.changescore(&XiaoLi,-54);
    XiaoLi.changescore(&XiaoLi,36);
	return 0;
}

Makefile

MainPro : StatePatternDemo.c design.o Interface.h
	gcc StatePatternDemo.c design.o -o MainPro
design.o : design.c Interface.h
	gcc -c design.c -o design.o
.PHONY:clean
clean :
	rm -rf *.o

运行

root@instance-3xw0fccv:~/DesignMode/state# clear
root@instance-3xw0fccv:~/DesignMode/state# make
gcc -c design.c -o design.o
gcc StatePatternDemo.c design.o -o MainPro
root@instance-3xw0fccv:~/DesignMode/state# ./MainPro
name:XiaoMing  grade:High  score:90
name:XiaoMing  grade:Low  score:20
name:XiaoMing  grade:Middle  score:60
name:XiaoLi  grade:High  score:92
name:XiaoLi  grade:Low  score:38
name:XiaoLi  grade:Middle  score:74

作业

设计一个简单的MP3播放器,要求两个按键(播放/暂停、停止)分别控制MP3的播放/停止功能。
如下表所示:

按键功能
play/pause播放/暂停
stop停止

状态迁移图如下图所示:
在这里插入图片描述

参考代码

#include <stdio.h>
#include <stdlib.h>

typedef struct State
{
    void (*pauseorplay)();
    void (*stop)();
    void (*gettype)();
}State;

void dopause();
void doplay();
void dostop();
void replay();
void ignore();

State* pCurrentState;

State IDLE=
{
    doplay,
    ignore
};

State PAUSE=
{
    replay,
    dostop
};

State PLAY=
{
    dopause,
    dostop
};

void dopause()
{
	printf("do pause!\n");
    pCurrentState=&PAUSE;
}

void doplay()
{
	printf("do play!\n");
    pCurrentState=&PLAY;
}

void dostop()
{
	printf("do stop!\n");
    pCurrentState=&IDLE;
}
void replay()
{
	printf("play again!\n");
    pCurrentState=&PLAY;
}
void ignore(State* state)
{
	//什么都不做
}

void onStop();
void onPlayOrPause();
 
State context = {
  onPlayOrPause,
  onStop
};
 
void onStop()
{
  pCurrentState->stop();
}
 
void onPlayOrPause()
{
  pCurrentState->pauseorplay();
}

void init()
{
  pCurrentState = &IDLE;
}

int main()
{
	init();
	context.pauseorplay();//播放
	context.pauseorplay();//暂停
	context.pauseorplay();//播放
	context.stop();//停止
	system("pause");
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一盆电子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值