[关闭]
@w1992wishes 2018-03-13T14:35:13.000000Z 字数 7059 阅读 960

设计模式--状态模式

设计模式 行为型模式


目录

本文的结构如下:

一、引言

要说现在的生活真的是蛮方便的,就拿现在的共享单车来说,下班时间,主要线路都堵爆了,以往只能龟行在车海中,现在只需找一辆单车骑回去,省时还健身。

但现在的共享单车基本上都需要先扫码开锁。如果找到一辆单车,但是却坏掉了,会显示等待维修之类的状态,扫不开,也骑不了;然后要换下一辆,刚好有一辆上面挂着塑料袋,旁边还有人扶着,你过去扫码,肯定是显示已占用之类的状态,同样是扫不开,骑不了;只有正常的单车才能扫开,然后让你骑行。这里面有几种状态,正常等待扫码状态,损坏等待维修状态,正在骑行状态......

在软件开发中,同样有这样的情况,有些对象也具有多种状态,这些状态在某些情况下能够相互转换,而且对象在不同的状态下也将具有不同的行为。为了更好地对这些具有多种状态的对象进行设计,可以使用一种被称之为状态模式的设计模式。

二、什么是状态模式

状态模式用于解决系统中复杂对象的状态转换以及不同状态下行为的封装问题。当系统中某个对象存在多个状态,这些状态之间可以进行转换,而且对象在不同状态下行为不相同时可以使用状态模式。

状态模式将一个对象的状态从该对象中分离出来,封装到专门的状态类中,使得对象状态可以灵活变化,对于客户端而言,无须关心对象状态的转换以及对象所处的当前状态,无论对于何种状态的对象,客户端都可以一致处理。

状态模式定义如下:

状态模式(State Pattern):允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。其别名为状态对象(Objects for States),状态模式是一种对象行为型模式。

“允许一个对象在其内部状态改变时改变它的行为”应该比较好理解,“对象看起来似乎修改了它的类”表达不是很清楚,大概的意思是对象的状态转变对客户端是不可知的,但却表现出不同的行为,就像修改了类一样。

三、模式的结构

状态模式的UML类图如下:

可以发现状态模式的类图和策略模式是一模一样的,那这两个模式有什么区别,这个后面再说,先继续看状态模式的结构。

在状态模式结构图中包含如下几个角色:

四、典型代码

状态模式将对象在不同状态下的行为封装到不同的状态类中,为了让系统具有更好的灵活性和可扩展性,同时对各状态下的共有行为进行封装,需要对状态进行抽象,引入了抽象状态类角色,其典型代码如下:

  1. public abstract class State {
  2. //不同的状态有不同的实现,也可是是不同的状态有不同的方法
  3. public abstract void handle();
  4. }

不同的具体状态类可以提供完全不同的方法实现,也可能包含多个业务方法,如果需要,应该将通用的方法放到抽象层,其典型代码如下:

  1. public class ConcreteStateA extends State {
  2. public void handle() {
  3. //todo
  4. }
  5. }
  6. public class ConcreteStateB extends State {
  7. public void handle() {
  8. //todo
  9. }
  10. }

环境类中需维持一个对抽象状态类的引用,可以通过调用setState()方法改变其状态(即不同的状态实现),再在环境类的业务方法中调用状态对象的方法,典型代码如下所示:

  1. public class Context {
  2. private State state;
  3. private int value; //某个属性值,对象状态发生变化由该值引发
  4. public void setState(State state){
  5. this.state = state;
  6. }
  7. public void request(){
  8. this.state.handle();
  9. }
  10. }

环境类实际上是真正拥有状态的对象,状态模式遵循“封装变化”的指导思想,将环境类中与状态有关的代码提取出来封装到专门的状态类中,之所以说是变化的,因为一个对象的状态之间可以进行相互转换。

这种转换是通过改变state的具体指向达到的,多是通过暴露的公有setState()方法实现,一般有两种方式,一种是在Context类中,根据value值的变化,改变状态;另一种就是在具体状态类中根据value值变化,改变状态,Context应该提供一个getValue()的方法。

五、代码示例

当今社会,论坛贴吧很多,我们也会加入感兴趣的论坛,偶尔进行发言,但有时却会发现不能发帖了,原来是昨天的某个帖子引发了口水战,被举报了。这里就用论坛发帖为例,简单用代码描述一下:

假设有三种状态,normal(正常),restricted(受限),closed(封号),判断依据是一个健康值(这里只是假设)。

5.1、不用状态模式

  1. public class Account {
  2. public static final int NORMAL = 1;
  3. public static final int RESTRICTED = 2;
  4. public static final int CLOSED = 3;
  5. private int healthValue;
  6. private int state;
  7. /**
  8. * 看帖
  9. */
  10. public void view(){
  11. System.out.println("正常看帖");
  12. //todo healthValue改变算法
  13. changeState();
  14. }
  15. /**
  16. * 评论
  17. */
  18. public void comment(){
  19. if (state == NORMAL || state == RESTRICTED){
  20. System.out.println("正常评论");
  21. //todo healthValue改变算法
  22. changeState();
  23. }else if (state == CLOSED){
  24. System.out.println("抱歉,你的健康值小于-10,不能评论");
  25. }
  26. }
  27. /**
  28. * 发帖
  29. */
  30. public void post(){
  31. if (state == NORMAL){
  32. //todo 一些health值改变算法
  33. changeState();
  34. System.out.println("正常发帖");
  35. }else if (state == RESTRICTED || state == CLOSED){
  36. System.out.println("抱歉,你的健康值小于0,不能发帖");
  37. }
  38. }
  39. public void changeState(){
  40. if (healthValue <= -10){
  41. state = CLOSED;
  42. }else if (-10 < healthValue && healthValue<= 0){
  43. state = RESTRICTED;
  44. }else if (healthValue > 0){
  45. state = CLOSED;
  46. }
  47. }
  48. public int getHealthValue() {
  49. return healthValue;
  50. }
  51. public void setState(int state) {
  52. this.state = state;
  53. }
  54. }

上面的代码很简单,能够实现需要的功能,但是却有几个问题:

5.2、使用状态模式

状态模式可以在一定程度上解决上述问题,在状态模式中将对象在每一个状态下的行为和状态转移语句封装在一个个状态类中,通过这些状态类来分散冗长的条件转移语句,让系统具有更好的灵活性和可扩展性。

抽象状态和具体状态类:

  1. public abstract class State {
  2. protected Account account;
  3. public void view(){
  4. System.out.println("正常看帖");
  5. //todo healthValue改变算法
  6. changeState();
  7. }
  8. public abstract void comment();
  9. public abstract void post();
  10. public void changeState(){
  11. if (account.getHealthValue() <= -10){
  12. account.setState(account.getClosedState());
  13. }else if (account.getHealthValue() > -10 && account.getHealthValue() <= 0){
  14. account.setState(account.getRestrictedState());
  15. }else if (account.getHealthValue()>0){
  16. account.setState(account.getNormalState());
  17. }
  18. }
  19. }
  20. public class NormalState extends State{
  21. public NormalState(Account account){
  22. this.account = account;
  23. }
  24. public void comment(){
  25. System.out.println("正常评论");
  26. //todo healthValue改变算法
  27. changeState();
  28. }
  29. public void post() {
  30. System.out.println("正常发帖");
  31. //todo healthValue改变算法
  32. changeState();
  33. }
  34. }
  35. public class RestrictedState extends State {
  36. public RestrictedState(Account account){
  37. this.account = account;
  38. }
  39. public void comment(){
  40. System.out.println("正常评论");
  41. //todo healthValue改变算法
  42. changeState();
  43. }
  44. public void post() {
  45. System.out.println("抱歉,你的健康值小于0,不能发帖");
  46. }
  47. }
  48. public class ClosedState extends State {
  49. public ClosedState(Account account){
  50. this.account = account;
  51. }
  52. public void comment(){
  53. System.out.println("抱歉,你的健康值小于-10,不能评论");
  54. }
  55. public void post() {
  56. System.out.println("抱歉,你的健康值小于-10,不能发帖");
  57. }
  58. public void changeState() {
  59. if (account.getHealthValue() <= -10){
  60. account.setState(account.getClosedState());
  61. }else if (account.getHealthValue() > -10 && account.getHealthValue() <= 0){
  62. account.setState(account.getRestrictedState());
  63. }
  64. }
  65. }

环境类(账户):

  1. public class Account {
  2. private State normalState, restrictedState, closedState;
  3. private String name;
  4. private int healthValue;
  5. private State state;
  6. public Account(String name){
  7. normalState = new NormalState(this);
  8. restrictedState = new RestrictedState(this);
  9. closedState = new ClosedState(this);
  10. this.healthValue = 1;//新账号默认为1
  11. this.state = normalState;
  12. this.name = name;
  13. }
  14. /**
  15. * 看帖
  16. */
  17. public void view(){
  18. System.out.println(name + "正在看帖");
  19. state.view();
  20. System.out.println("当前账户状态为:" + state.getClass().getName());
  21. }
  22. /**
  23. * 评论
  24. */
  25. public void comment(){
  26. System.out.println(name + "正在评论");
  27. state.comment();
  28. System.out.println("当前账户状态为:" + state.getClass().getName());
  29. }
  30. /**
  31. * 发帖
  32. */
  33. public void post(){
  34. System.out.println(name + "正在发帖");
  35. state.post();
  36. System.out.println("当前账户状态为:" + state.getClass().getName());
  37. }
  38. public int getHealthValue() {
  39. return healthValue;
  40. }
  41. public void setState(State state) {
  42. this.state = state;
  43. }
  44. public State getNormalState() {
  45. return normalState;
  46. }
  47. public State getRestrictedState() {
  48. return restrictedState;
  49. }
  50. public State getClosedState() {
  51. return closedState;
  52. }
  53. }

六、状态模式和策略模式的区别

前面说了,它们的UML类图一模一样,但却是两种设计模式,这里面肯定是有原因的。

七、优点和缺点

7.1、优点

状态模式的主要优点如下:

7.2、缺点

状态模式的主要缺点如下:

八、适用环境

在以下情况下可以使用状态模式:

九、模式应用

状态模式在工作流或游戏等类型的软件中得以广泛使用,甚至可以用于这些系统的核心功能设计,如在政府OA办公系统中,一个批文的状态有多种:尚未办理;正在办理;正在批示;正在审核;已经完成等各种状态,而且批文状态不同时对批文的操作也有所差异。使用状态模式可以描述工作流对象(如批文)的状态转换以及不同状态下它所具有的行为。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注