一、概念
1、状态模式的动机
现在年轻人喜欢玩游戏,有些游戏需要充钱才能获得更高的战斗力,每充钱到一定程度,就会达到某种会员状态,在这种状态玩家能有更好的游戏体验。在软件系统中,为了更好地对这些具有多种状态的对象进行设计,我们可以使用一种被称之为状态模式的设计模式。
2、状态模式的定义
状态模式(State Pattern):允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。其别名为状态对象(Objects for States),状态模式是一种对象行为型模式。
状态模式用于解决系统中复杂对象的状态转换以及不同状态下行为的封装问题。
3、状态模式的3个角色
1)Context(环境类):环境类又称为上下文类,它是拥有多种状态的对象。由于环境类的状态存在多样性且在不同状态下对象的行为有所不同,因此将状态独立出去形成单独的状态类。在环境类中维护一个抽象状态类State的实例,这个实例定义当前状态,在具体实现时,它是一个State子类的对象。
2)State(抽象状态类):它用于定义一个接口以封装与环境类的一个特定状态相关的行为,在抽象状态类中声明了各种不同状态对应的方法,而在其子类中实现类这些方法,由于不同状态下对象的行为可能不同,因此在不同子类中方法的实现可能存在不同,相同的方法可以写在抽象状态类中。
3)ConcreteState(具体状态类):它是抽象状态类的子类,每一个子类实现一个与环境类的一个状态相关的行为,每一个具体状态类对应环境的一个具体状态,不同的具体状态类其行为有所不同。
在状态模式中,我们将对象在不同状态下的行为封装到不同的状态类中,为了让系统具有更好的灵活性和可扩展性,同时对各状态下的共有行为进行封装,我们需要对状态进行抽象,引入了抽象状态类角色。
4、结构图
二、示例
在状态模式中引入了抽象状态类和具体状态类,它们是状态模式的核心。在状态模式中实现状态转换时,具体状态类可通过调用环境类Context的setState()方法进行状态的转换操作,也可以统一由环境类Context来实现状态的转换。
1)先创建一个MemberState协议,有个handle()方法,表示抽象状态类;
2)然后创建四个类NormalMemberState、GoldMemberState、PlatinumMemberState和DiamondMemberState,都遵循MemberState协议,表示具体状态类;
3)最后创建一个Person类,有一个state属性,表示环境类。
具体代码如下:
MemberState协议:
// 会员状态
@protocol MemberState <NSObject>
- (void)handle; //声明抽象业务方法,不同的具体状态类可以不同的实现
@end
typedef id<MemberState> MemberState;
NormalMemberState、GoldMemberState、PlatinumMemberState和DiamondMemberState类:
// NormalMemberState 普通会员
@interface NormalMemberState : NSObject<MemberState>
@end
@implementation NormalMemberState
- (void)handle {
NSLog(@"普通会员:刚出生,攻击力5,只能打败普通人");
}
@end
// GoldMemberState 黄金会员
@interface GoldMemberState : NSObject<MemberState>
@end
@implementation GoldMemberState
- (void)handle {
NSLog(@"黄金会员:长大了,攻击力200,打败比克大魔王");
}
@end
// PlatinumMemberState 白金会员
@interface PlatinumMemberState : NSObject<MemberState>
@end
@implementation PlatinumMemberState
- (void)handle {
NSLog(@"白金会员:变身超级赛亚人1,攻击力20000,击败宇宙魔王弗利萨");
}
@end
// DiamondMemberState 钻石会员
@interface DiamondMemberState : NSObject<MemberState>
@end
@implementation DiamondMemberState
- (void)handle {
NSLog(@"钻石会员:变身超级赛亚人2,攻击力200000,击杀生化人沙鲁");
}
@end
Person类:
// 环境类
@interface Person : NSObject
// 其他属性值,该属性值的变化可能会导致对象状态发生变化
@property(nonatomic, assign) NSInteger money;
@property(nonatomic, strong) MemberState state; //维持一个对抽象状态对象的引用
- (void)playGame;
@end
@implementation Person
- (void)setMoney:(NSInteger)money {
_money = money;
[self changeState];
}
// 判断属性值,根据属性值进行状态转换
- (void)changeState {
NSInteger money = self.money;
if (money <= 0) {
NSLog(@"请充值,才能有更好的游戏体验");
return;
}
if (money < 10) {
self.state = [NormalMemberState new];
} else if (money < 100) {
self.state = [GoldMemberState new];
} else if (money < 1000) {
self.state = [PlatinumMemberState new];
} else {
self.state = [DiamondMemberState new];
}
}
- (void)playGame {
[self.state handle]; //调用状态对象的业务方法
}
@end
运行代码:
- (void)viewDidLoad {
[super viewDidLoad];
// 创建孙悟空对象
Person *monkeyKing = [Person new];
monkeyKing.money = 5;
[monkeyKing playGame];
monkeyKing.money = 50;
[monkeyKing playGame];
monkeyKing.money = 500;
[monkeyKing playGame];
monkeyKing.money = 5000;
[monkeyKing playGame];
}
打印结果:
普通会员:刚出生,攻击力5,只能打败普通人
黄金会员:长大了,攻击力200,打败比克大魔王
白金会员:变身超级赛亚人1,攻击力20000,击败宇宙魔王弗利萨
钻石会员:变身超级赛亚人2,攻击力200000,击杀生化人沙鲁
三、总结
状态模式将一个对象在不同状态下的不同行为封装在一个个状态类中,通过设置不同的状态对象可以让环境对象拥有不同的行为,而状态转换的细节对于客户端而言是透明的,方便了客户端的使用。
1、优点
1、 封装了状态的转换规则,在状态模式中可以将状态的转换代码封装在环境类或者具体状态类中,可以对状态转换代码进行集中管理,而不是分散在一个个业务方法中。
2、将所有与某个状态有关的行为放到一个类中,只需要注入一个不同的状态对象即可使环境对象拥有不同的行为。
3、允许状态转换逻辑与状态对象合成一体,而不是提供一个巨大的条件语句块,状态模式可以让我们避免使用庞大的条件语句来将业务方法和状态转换代码交织在一起。
4、可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
2、缺点
1、状态模式的使用必然会增加系统中类和对象的个数,导致系统运行开销增大。
2、状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱,增加系统设计的难度。
3、状态模式对“开闭原则”的支持并不太好,增加新的状态类需要修改那些负责状态转换的源代码,否则无法转换到新增状态。
3、适用场景
1、对象的行为依赖于它的状态(如某些属性值),状态的改变将导致行为的变化。
2、在代码中包含大量与对象状态有关的条件语句,这些条件语句的出现,会导致代码的可维护性和灵活性变差,不能方便地增加和删除状态,并且导致客户类与类库之间的耦合增强。
Demo地址:iOS-Design-Patterns