首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >面向对象设计的设计模式(十六):状态模式

面向对象设计的设计模式(十六):状态模式

作者头像
用户2932962
发布2019-05-28 19:38:10
5320
发布2019-05-28 19:38:10
举报

定义

在状态模式(State Pattern):允许一个对象在其内部状态改变时,改变它的行为。

适用场景

一个对象存在多个状态,不同状态下的行为会有不同,而且状态之间可以相互转换。

如果我们通过if else来判断对象的状态,那么代码中会包含大量与对象状态有关的条件语句,而且在添加,删除和更改这些状态的时候回比较麻烦;而如果使用状态模式。将状态对象分散到不同的类中,则可以消除 if...else等条件选择语句。

现在我们清楚了状态模式的适用场景,下面看一下状态模式的成员和类图。

成员与类图

成员

状态模式一共只有四个成员:

  • 环境类(Context):环境类引用了具体状态的实例。环境类持有的具体状态就是当前的状态,可以通过 set 方法将状态实例注入到环境类中。
  • 抽象状态类(State):抽象状态类声明具体状态类需要实现的接口。
  • 具体状态类(Concrete State):具体状态类实现抽象状态类声明的接口。

下面通过类图来看一下各个成员之间的关系:

模式类图

状态模式类图

代码示例

场景概述

模拟一个程序员一天的生活,他有四个状态:

  1. 醒着
  2. 睡觉中
  3. 写代码中
  4. 吃饭中

看这几个状态应该是个非常爱写代码的程序员 ^ ^

场景分析

这个程序员有四个状态,但是有些状态之间是无法切换的:比如从睡觉是无法切换到写代码的(因为需要切换到醒着,然后才能到写代码);从吃饭中是无法切换到醒着的,因为已经醒着了。

如果我们不使用状态模式,在切换状态的时候可能会写不少if-else判断,而且随着状态的增多,这些分支会变得更多,难以维护。

而如果我们使用状态模式,则可以将每个状态封装到一个类中,便于管理;而且在增加或减少状态时也会很方便。

下面我们看一下如何用代码来模拟该场景。

代码实现

首先我们定义状态类:

//================== State.h ==================

@interface State : NSObject<ActionProtocol>
{
    @protected Coder *_coder;
}

- (instancetype)initWithCoder:(Coder *)coder;

@end



//================== State.m ==================

@implementation State

- (instancetype)initWithCoder:(Coder *)coder{

    self = [super init];
    if (self) {
        _coder = coder;
    }
    return self;
}

@end

状态类持有一个coder,也就是程序员的实例,并遵循了ActionProtocol

//================== ActionProtocol.h ==================

@protocol ActionProtocol <NSObject>

@optional;

- (void)wakeUp;

- (void)fallAsleep;

- (void)startCoding;

- (void)startEating;

@end

ActionProtocol定义了程序员的一些动作,这些动作是程序员的日常活动,也是触发状态切换的动作,因此State也需要遵循这个协议,因为它的子类需要实现这些操作。

接下来我们看一下State的子类,根据上面说的四种状态,我们定义下面四个状态子类:

StateAwake:

//================== StateAwake.h ==================

@interface StateAwake : State

@end

@implementation StateAwake

- (void)wakeUp{

    NSLog(@"Already awake, can not change state to awake again");
}

- (void)startCoding{

    NSLog(@"Change state from awake to coding");
    [_coder setState:(State *)[_coder stateCoding]];
}

- (void)startEating{

    NSLog(@"Change state from awake to eating");
    [_coder setState:(State *)[_coder stateEating]];
}


- (void)fallAsleep{

    NSLog(@"Change state from awake to sleeping");
    [_coder setState:(State *)[_coder stateSleeping]];
}

@end

StateSleeping:

//================== StateSleeping.h ==================

@interface StateSleeping : State

@end



//================== StateSleeping.m ==================

@implementation StateSleeping

- (void)wakeUp{

    NSLog(@"Change state from sleeping to awake");
    [_coder setState:(State *)[_coder stateAwake]];
}


- (void)startCoding{

    NSLog(@"Already sleeping, can not change state to coding");
}

- (void)startEating{

    NSLog(@"Already sleeping, can change state to eating");
}


- (void)fallAsleep{

    NSLog(@"Already sleeping, can not change state to sleeping again");
}

@end

StateEating:

//================== StateEating.h ==================

@interface StateEating : State

@end



//================== StateEating.m ==================

@implementation StateEating

- (void)wakeUp{

    NSLog(@"Already awake, can not change state to awake again");
}


- (void)startCoding{

    NSLog(@"New idea came out! change state from eating to coding");
    [_coder setState:(State *)[_coder stateCoding]];
}

- (void)startEating{

    NSLog(@"Already eating, can not change state to eating again");
}


- (void)fallAsleep{

    NSLog(@"Too tired, change state from eating to sleeping");
    [_coder setState:(State *)[_coder stateSleeping]];
}



@end

"StateCoding":

//================== StateCoding.h ==================

@interface StateCoding : State

@end



//================== StateCoding.m ==================

@implementation StateCoding

- (void)wakeUp{

    NSLog(@"Already awake, can not change state to awake again");
}


- (void)startCoding{

    NSLog(@"Already coding, can not change state to coding again");
}

- (void)startEating{

    NSLog(@"Too hungry, change state from coding to eating");
    [_coder setState:(State *)[_coder stateEating]];
}


- (void)fallAsleep{

    NSLog(@"Too tired, change state from coding to sleeping");
    [_coder setState:(State *)[_coder stateSleeping]];
}

@end

从上面的类可以看出,在有些状态之间的转换是失效的,有些是可以的。 比如相同状态的切换是无效的;从 sleeping无法切换到coding,但是反过来可以,因为可能写代码累了就直接睡了。

下面我们看一下程序员类的实现:

//================== Coder.h ==================

@interface Coder : NSObject<ActionProtocol>

@property (nonatomic, strong) StateAwake *stateAwake;
@property (nonatomic, strong) StateCoding *stateCoding;
@property (nonatomic, strong) StateEating *stateEating;
@property (nonatomic, strong) StateSleeping *stateSleeping;

- (void)setState:(State *)state;

@end



//================== Coder.m ==================

@implementation Coder
{
    State *_currentState;
}

- (instancetype)init{

    self = [super init];
    if (self) {

        _stateAwake = [[StateAwake alloc] initWithCoder:self];
        _stateCoding = [[StateCoding alloc] initWithCoder:self];
        _stateEating = [[StateEating alloc] initWithCoder:self];
        _stateSleeping = [[StateSleeping alloc] initWithCoder:self];

        _currentState = _stateAwake;
    }
    return self;
}

- (void)setState:(State *)state{

    _currentState = state;
}

- (void)wakeUp{

    [_currentState wakeUp];
}

- (void)startCoding{

    [_currentState startCoding];
}

- (void)startEating{

    [_currentState startEating];
}


- (void)fallAsleep{

    [_currentState fallAsleep];
}

@end

从上面的代码我们可以看到,程序员类持有一个当前的状态的实例,在初始化后默认的状态为awake,并对外提供一个setState:的方法来切换状态。而且在初始化方法里,我们实例化了所有的状态,目的是在切换状态中时使用,详见具体状态类的方法:

- (void)startEating{

    NSLog(@"Too hungry, change state from coding to eating");
    [_coder setState:(State *)[_coder stateEating]];
}

上面这段代码有点绕,可能需要多看几遍源码才能理解(这里面[_coder stateEating]是调用了coder的一个get方法,返回了stateEating这个实例)。

最后,在程序员的动作方法里面,实际上调用的是当前状态对应的方法(这也就是为何程序员类和状态类都要遵循ActionProtocol的原因)。

这样,我们的状态类,状态子类,程序员类都声明好了。我们看一下如何使用:

Coder *coder = [[Coder alloc] init];

//change to awake.. failed
[coder wakeUp];//Already awake, can not change state to awake again

//change to coding
[coder startCoding];//Change state from awake to coding

//change to sleep
[coder fallAsleep];//Too tired, change state from coding to sleeping

//change to eat...failed
[coder startEating];//Already sleeping, can change state to eating

//change to wake up
[coder wakeUp];//Change state from sleeping to awake

//change wake up...failed
[coder wakeUp];//Already awake, can not change state to awake again

//change to eating
[coder startEating];//Change state from awake to eating

//change to coding
[coder startCoding];//New idea came out! change state from eating to coding

//change to sleep
[coder fallAsleep];//Too tired, change state from coding to sleeping

在上面的代码里,我们实例化了一个程序员类,接着不断调用一些触发状态改变的方法。我们把每次状态切换的日至输出注释到了代码右侧,可以看到在一些状态的切换是不允许的:

  • 比如从上到下的第一个[coder wakeUp]:因为程序员对象初始化后默认是awake状态,所以无法切换到相同的状态
  • 比如从上到下的第一个[coder startEating]:在睡觉时是无法直接切换到eating状态;而在后面wake以后,再执行[coder startEating]就成功了。

从上面的例子可以看出,使用状态模式不需要去写if-else,而且如果今后想添加一个状态,只需要再创建一个状态子类,并在新的状态子类添加好对所有状态的处理,并在之前的状态子类中添加上对新状态的处理即可。即便我们修改了之前定义好的状态子类,但是这样也总比使用庞大的if-else要方便多。

下面看一下上面代码对应的类图。

代码对应的类图

状态模式代码示例类图

优点

  1. 把各种状态的转换逻辑,分布到不同的类中,减少相互间的依赖。

缺点

  1. 增加新的状态类需要修改状态转换的源码,而且增加新的行为也要修改原来的状态类(前提是新的行为和原来的状态有关系)。
  2. 过多的状态会增加系统中的类的个数,增加系统的复杂性。

iOS SDK 和 JDK中的应用

  • javax包下的LifyCycle是状态模式的一种实现
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-05-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员维他命 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 适用场景
  • 成员与类图
    • 成员
      • 模式类图
      • 代码示例
        • 场景概述
          • 场景分析
            • 代码实现
              • 代码对应的类图
              • 优点
              • 缺点
              • iOS SDK 和 JDK中的应用
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档