Objective-C 中的状态模式实现与场景分析
一、状态模式概述
状态模式(State Pattern)是一种行为型设计模式。它允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。在实际应用中,许多对象的行为依赖于它们的状态,并且在不同状态下执行不同的操作。传统的做法可能是通过大量的条件语句(如 if - else
或 switch - case
)来处理不同状态下的行为,但这样的代码往往难以维护和扩展。状态模式通过将每个状态封装成独立的类,并让对象在运行时动态切换这些状态类,使得代码更加清晰、可维护和可扩展。
(一)状态模式的角色
- 环境(Context)角色:持有一个状态接口的引用,定义客户端感兴趣的接口,并且根据当前状态来委托状态对象处理请求。
- 抽象状态(State)角色:定义一个接口以封装与环境对象的一个特定状态相关的行为。
- 具体状态(ConcreteState)角色:实现抽象状态接口所定义的行为。每个具体状态类对应环境对象的一个具体状态。
二、Objective - C 中状态模式的实现基础
(一)类与对象
在 Objective - C 中,类是对象的蓝图,对象是类的实例。我们通过定义类来封装数据和行为。例如,定义一个简单的 Person
类:
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;
- (void)sayHello;
@end
@implementation Person
- (void)sayHello {
NSLog(@"Hello, my name is %@", self.name);
}
@end
这里 Person
类有一个属性 name
和一个方法 sayHello
。通过创建 Person
类的实例,我们可以调用 sayHello
方法。
Person *person = [[Person alloc] init];
person.name = @"John";
[person sayHello];
(二)协议(Protocol)
协议在 Objective - C 中用于定义一组方法声明,任何类都可以实现这些方法。协议类似于 Java 中的接口。例如,我们定义一个 AnimalProtocol
协议:
@protocol AnimalProtocol <NSObject>
- (void)makeSound;
@end
然后,一个类可以声明遵循这个协议并实现协议中的方法:
@interface Dog : NSObject <AnimalProtocol>
@end
@implementation Dog
- (void)makeSound {
NSLog(@"Woof!");
}
@end
(三)多态性
Objective - C 支持多态性,这意味着可以根据对象的实际类型来调用适当的方法。例如,我们有一个 Animal
类的基类和 Dog
、Cat
等子类,它们都实现了 makeSound
方法:
@interface Animal : NSObject
- (void)makeSound;
@end
@implementation Animal
- (void)makeSound {
NSLog(@"Generic animal sound");
}
@end
@interface Dog : Animal
@end
@implementation Dog
- (void)makeSound {
NSLog(@"Woof!");
}
@end
@interface Cat : Animal
@end
@implementation Cat
- (void)makeSound {
NSLog(@"Meow!");
}
@end
然后可以通过以下方式实现多态调用:
Animal *animal1 = [[Dog alloc] init];
Animal *animal2 = [[Cat alloc] init];
[animal1 makeSound]; // 输出 Woof!
[animal2 makeSound]; // 输出 Meow!
三、Objective - C 中状态模式的具体实现
(一)以游戏角色状态为例
假设我们正在开发一个游戏,游戏角色有不同的状态,如 Idle
(空闲)、Running
(奔跑)、Jumping
(跳跃)。
- 定义抽象状态类
@interface CharacterState : NSObject
- (void)handleInput:(NSString *)input forCharacter:(id)character;
@end
@implementation CharacterState
- (void)handleInput:(NSString *)input forCharacter:(id)character {
NSLog(@"Default handling of input %@ in state %@", input, NSStringFromClass([self class]));
}
@end
这里 CharacterState
类定义了一个 handleInput:forCharacter:
方法,用于处理角色在该状态下接收到的输入。
- 定义具体状态类
- Idle 状态
@interface IdleState : CharacterState
@end
@implementation IdleState
- (void)handleInput:(NSString *)input forCharacter:(id)character {
if ([input isEqualToString:@"run"]) {
NSLog(@"Character starts running from idle state.");
// 这里可以设置角色的状态转换逻辑
} else if ([input isEqualToString:@"jump"]) {
NSLog(@"Character starts jumping from idle state.");
} else {
[super handleInput:input forCharacter:character];
}
}
@end
- **Running 状态**
@interface RunningState : CharacterState
@end
@implementation RunningState
- (void)handleInput:(NSString *)input forCharacter:(id)character {
if ([input isEqualToString:@"stop"]) {
NSLog(@"Character stops running.");
} else if ([input isEqualToString:@"jump"]) {
NSLog(@"Character jumps while running.");
} else {
[super handleInput:input forCharacter:character];
}
}
@end
- **Jumping 状态**
@interface JumpingState : CharacterState
@end
@implementation JumpingState
- (void)handleInput:(NSString *)input forCharacter:(id)character {
if ([input isEqualToString:@"land"]) {
NSLog(@"Character lands from jump.");
} else {
[super handleInput:input forCharacter:character];
}
}
@end
- 定义环境类(游戏角色类)
@interface GameCharacter : NSObject
@property (nonatomic, strong) CharacterState *currentState;
- (void)handleInput:(NSString *)input;
@end
@implementation GameCharacter
- (void)handleInput:(NSString *)input {
[self.currentState handleInput:input forCharacter:self];
}
@end
这里 GameCharacter
类持有一个 currentState
属性,用于表示当前角色的状态,并且通过 handleInput:
方法将输入委托给当前状态对象处理。
- 使用状态模式
GameCharacter *character = [[GameCharacter alloc] init];
character.currentState = [[IdleState alloc] init];
[character handleInput:@"run"];
// 可以动态改变角色状态
character.currentState = [[RunningState alloc] init];
[character handleInput:@"jump"];
(二)以自动售货机为例
- 定义抽象状态类
@interface VendingMachineState : NSObject
- (void)insertCoin:(NSUInteger)coin forMachine:(id)machine;
- (void)dispenseProduct:(id)product forMachine:(id)machine;
@end
@implementation VendingMachineState
- (void)insertCoin:(NSUInteger)coin forMachine:(id)machine {
NSLog(@"Default handling of inserting coin %lu in state %@", (unsigned long)coin, NSStringFromClass([self class]));
}
- (void)dispenseProduct:(id)product forMachine:(id)machine {
NSLog(@"Default handling of dispensing product %@ in state %@", product, NSStringFromClass([self class]));
}
@end
- 定义具体状态类
- NoCoinState
@interface NoCoinState : VendingMachineState
@end
@implementation NoCoinState
- (void)insertCoin:(NSUInteger)coin forMachine:(id)machine {
NSLog(@"Coin of value %lu inserted. Machine now has enough money.", (unsigned long)coin);
// 这里可以设置状态转换逻辑,例如转换到 HasCoinState
}
@end
- **HasCoinState**
@interface HasCoinState : VendingMachineState
@end
@implementation HasCoinState
- (void)dispenseProduct:(id)product forMachine:(id)machine {
NSLog(@"Dispensing product %@. Machine is now back to no - coin state.", product);
// 这里可以设置状态转换逻辑,例如转换到 NoCoinState
}
@end
- 定义环境类(自动售货机类)
@interface VendingMachine : NSObject
@property (nonatomic, strong) VendingMachineState *currentState;
- (void)insertCoin:(NSUInteger)coin;
- (void)dispenseProduct:(id)product;
@end
@implementation VendingMachine
- (void)insertCoin:(NSUInteger)coin {
[self.currentState insertCoin:coin forMachine:self];
}
- (void)dispenseProduct:(id)product {
[self.currentState dispenseProduct:product forMachine:self];
}
@end
- 使用状态模式
VendingMachine *vendingMachine = [[VendingMachine alloc] init];
vendingMachine.currentState = [[NoCoinState alloc] init];
[vendingMachine insertCoin:10];
vendingMachine.currentState = [[HasCoinState alloc] init];
[vendingMachine dispenseProduct:@"Coca - Cola"];
四、状态模式在 Objective - C 项目中的应用场景分析
(一)用户界面状态管理
在 iOS 应用开发中,用户界面(UI)的状态经常需要根据用户操作或系统事件进行切换。例如,一个登录界面可能有以下状态:初始状态(显示用户名和密码输入框及登录按钮)、正在登录状态(显示加载指示器并禁用登录按钮)、登录成功状态(跳转到主界面)、登录失败状态(显示错误提示信息)。
- 定义抽象状态类
@interface LoginUIState : NSObject
- (void)handleEvent:(NSString *)event forLoginUI:(id)loginUI;
@end
@implementation LoginUIState
- (void)handleEvent:(NSString *)event forLoginUI:(id)loginUI {
NSLog(@"Default handling of event %@ in state %@", event, NSStringFromClass([self class]));
}
@end
- 定义具体状态类
- InitialState
@interface InitialState : LoginUIState
@end
@implementation InitialState
- (void)handleEvent:(NSString *)event forLoginUI:(id)loginUI {
if ([event isEqualToString:@"loginButtonTapped"]) {
NSLog(@"Starting login process. Changing to loggingIn state.");
// 这里可以设置界面状态转换逻辑,例如显示加载指示器,禁用登录按钮
}
}
@end
- **LoggingInState**
@interface LoggingInState : LoginUIState
@end
@implementation LoggingInState
- (void)handleEvent:(NSString *)event forLoginUI:(id)loginUI {
if ([event isEqualToString:@"loginSuccess"]) {
NSLog(@"Login successful. Changing to loggedIn state.");
// 这里可以设置界面状态转换逻辑,例如跳转到主界面
} else if ([event isEqualToString:@"loginFailure"]) {
NSLog(@"Login failed. Changing to loginFailed state.");
// 这里可以设置界面状态转换逻辑,例如显示错误提示信息
}
}
@end
- 定义环境类(登录界面类)
@interface LoginViewController : UIViewController
@property (nonatomic, strong) LoginUIState *currentState;
- (void)handleUIEvent:(NSString *)event;
@end
@implementation LoginViewController
- (void)handleUIEvent:(NSString *)event {
[self.currentState handleEvent:event forLoginUI:self];
}
@end
通过使用状态模式,登录界面的状态管理变得更加清晰和易于维护。当有新的状态或状态转换逻辑时,只需要添加新的具体状态类或修改现有状态类的方法,而不需要在复杂的条件语句中进行修改。
(二)网络请求状态管理
在进行网络请求时,请求通常有不同的状态,如 Idle
(空闲,未发起请求)、Loading
(正在加载)、Success
(请求成功)、Failure
(请求失败)。
- 定义抽象状态类
@interface NetworkRequestState : NSObject
- (void)handleNetworkEvent:(NSString *)event forRequest:(id)request;
@end
@implementation NetworkRequestState
- (void)handleNetworkEvent:(NSString *)event forRequest:(id)request {
NSLog(@"Default handling of network event %@ in state %@", event, NSStringFromClass([self class]));
}
@end
- 定义具体状态类
- IdleState
@interface IdleState : NetworkRequestState
@end
@implementation IdleState
- (void)handleNetworkEvent:(NSString *)event forRequest:(id)request {
if ([event isEqualToString:@"startRequest"]) {
NSLog(@"Starting network request. Changing to loading state.");
// 这里可以设置请求开始的逻辑,例如显示加载指示器
}
}
@end
- **LoadingState**
@interface LoadingState : NetworkRequestState
@end
@implementation LoadingState
- (void)handleNetworkEvent:(NSString *)event forRequest:(id)request {
if ([event isEqualToString:@"requestSuccess"]) {
NSLog(@"Network request successful. Changing to success state.");
// 这里可以设置处理成功响应的逻辑,例如更新 UI 数据
} else if ([event isEqualToString:@"requestFailure"]) {
NSLog(@"Network request failed. Changing to failure state.");
// 这里可以设置处理失败响应的逻辑,例如显示错误提示
}
}
@end
- 定义环境类(网络请求类)
@interface NetworkRequest : NSObject
@property (nonatomic, strong) NetworkRequestState *currentState;
- (void)handleNetworkEvent:(NSString *)event;
@end
@implementation NetworkRequest
- (void)handleNetworkEvent:(NSString *)event {
[self.currentState handleNetworkEvent:event forRequest:self];
}
@end
通过状态模式,网络请求的不同状态及其对应的行为可以得到很好的封装和管理。这使得代码在处理复杂的网络请求场景时更加健壮和可维护。
(三)游戏关卡状态管理
在游戏开发中,关卡通常有不同的状态,如 Locked
(锁定,玩家未达到解锁条件)、Unlocked
(解锁,但未开始游戏)、InProgress
(游戏进行中)、Completed
(关卡完成)。
- 定义抽象状态类
@interface LevelState : NSObject
- (void)handlePlayerAction:(NSString *)action forLevel:(id)level;
@end
@implementation LevelState
- (void)handlePlayerAction:(NSString *)action forLevel:(id)level {
NSLog(@"Default handling of player action %@ in state %@", action, NSStringFromClass([self class]));
}
@end
- 定义具体状态类
- LockedState
@interface LockedState : LevelState
@end
@implementation LockedState
- (void)handlePlayerAction:(NSString *)action forLevel:(id)level {
if ([action isEqualToString:@"attemptUnlock"]) {
NSLog(@"Player attempts to unlock the level. Checking conditions...");
// 这里可以设置检查解锁条件的逻辑,并根据结果转换状态
}
}
@end
- **UnlockedState**
@interface UnlockedState : LevelState
@end
@implementation UnlockedState
- (void)handlePlayerAction:(NSString *)action forLevel:(id)level {
if ([action isEqualToString:@"startGame"]) {
NSLog(@"Player starts the game. Changing to in - progress state.");
// 这里可以设置开始游戏的逻辑,例如初始化游戏场景
}
}
@end
- 定义环境类(关卡类)
@interface GameLevel : NSObject
@property (nonatomic, strong) LevelState *currentState;
- (void)handlePlayerAction:(NSString *)action;
@end
@implementation GameLevel
- (void)handlePlayerAction:(NSString *)action {
[self.currentState handlePlayerAction:action forLevel:self];
}
@end
通过状态模式,游戏关卡的状态管理更加灵活和清晰。可以方便地添加新的关卡状态或修改现有状态的行为,以适应不同的游戏需求。
五、状态模式在 Objective - C 中的优势与挑战
(一)优势
- 代码清晰易维护:通过将不同状态的行为封装到独立的类中,避免了大量复杂的条件语句。当需要添加新状态或修改现有状态的行为时,只需要在相应的状态类中进行修改,而不会影响其他状态或整体逻辑。例如,在游戏角色状态管理中,如果要添加一个新的
Crouching
(蹲下)状态,只需要创建一个新的CrouchingState
类并实现相关行为,而不需要在原有的庞大条件语句中进行插入和修改。 - 可扩展性强:易于扩展系统以处理新的状态。当业务需求发生变化,需要引入新的状态时,状态模式使得添加新状态的过程非常简单。在自动售货机的例子中,如果要添加一个
OutOfStock
(缺货)状态,只需要创建一个OutOfStockState
类并实现相应的插入硬币和分发产品的行为,然后在自动售货机类中进行状态切换的设置即可。 - 符合开闭原则:状态模式对扩展开放,对修改关闭。新状态的添加不会影响已有的状态类和环境类的代码。在网络请求状态管理中,如果要添加一个
Cancelled
(取消)状态,只需要创建新的CancelledState
类并实现相关行为,而不需要修改原有的Idle
、Loading
、Success
和Failure
状态类的代码。
(二)挑战
- 状态类数量增加:随着状态的增多,状态类的数量也会相应增加。这可能导致项目中的类文件数量增多,增加了一定的管理成本。例如,在一个复杂的游戏中,角色可能有十几种甚至几十种状态,这就需要创建相应数量的状态类。不过,通过合理的命名和目录结构可以在一定程度上缓解这个问题。
- 状态转换逻辑可能复杂:在某些情况下,状态之间的转换逻辑可能比较复杂。需要仔细设计状态转换的条件和顺序,以确保系统的稳定性和正确性。例如,在一个具有多种升级和降级状态的系统中,状态之间的转换可能涉及到多个条件的判断和复杂的业务规则。这就需要开发人员在设计状态模式时充分考虑各种情况,编写详细的文档以方便后续维护。
六、与其他设计模式的关系
(一)与策略模式的比较
- 相似性:状态模式和策略模式在结构上有一定的相似性。它们都将行为封装在不同的类中,通过组合的方式让环境对象持有这些行为类的实例,并根据不同情况调用相应的行为。在 Objective - C 代码实现上,两者都通过对象组合来实现行为的动态切换。
- 差异性:策略模式侧重于算法的切换,客户端可以根据不同需求选择不同的策略,策略之间通常是平行的关系,并且切换策略一般由客户端决定。而状态模式侧重于对象状态的改变,状态之间往往存在一定的转换关系,状态的切换通常由对象自身的状态和事件驱动。例如,在一个文本排版系统中,使用策略模式可以根据用户选择不同的排版算法(如左对齐、居中对齐、右对齐),而使用状态模式可能用于处理文档在不同编辑状态(如新建、编辑、保存、关闭)下的行为。
(二)与状态机模式的关系
- 相似性:状态模式和状态机模式都用于处理对象状态相关的逻辑。它们都关注对象在不同状态下的行为以及状态之间的转换。在实际应用中,状态模式可以看作是状态机模式的一种实现方式。
- 差异性:状态机模式通常更侧重于状态转换的整体描述和控制,可能使用状态转移表等方式来定义状态之间的转换规则。而状态模式更强调将每个状态的行为封装成独立的类,通过对象组合和多态性来实现状态相关的行为。在一些简单的状态管理场景中,状态模式可能更易于实现和理解,而在复杂的状态机场景中,可能需要结合状态机模式的一些概念来进行更全面的设计。
七、优化与最佳实践
(一)状态缓存
在一些情况下,状态对象的创建可能比较消耗资源。可以考虑使用状态缓存机制,避免重复创建相同的状态对象。例如,在一个频繁切换状态的游戏角色系统中,可以使用一个字典来缓存已经创建的状态对象,当需要某个状态时,先从字典中查找,如果存在则直接使用,不存在再进行创建。
@interface StateCache : NSObject
@property (nonatomic, strong) NSMutableDictionary<NSString *, CharacterState *> *stateCache;
+ (instancetype)sharedCache;
- (CharacterState *)getStateForClass:(Class)stateClass;
@end
@implementation StateCache
+ (instancetype)sharedCache {
static StateCache *sharedCache = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedCache = [[StateCache alloc] init];
sharedCache.stateCache = [NSMutableDictionary dictionary];
});
return sharedCache;
}
- (CharacterState *)getStateForClass:(Class)stateClass {
NSString *className = NSStringFromClass(stateClass);
CharacterState *state = self.stateCache[className];
if (!state) {
state = [[stateClass alloc] init];
self.stateCache[className] = state;
}
return state;
}
@end
然后在游戏角色类中使用缓存:
@implementation GameCharacter
- (void)setCurrentStateWithClass:(Class)stateClass {
StateCache *cache = [StateCache sharedCache];
self.currentState = [cache getStateForClass:stateClass];
}
@end
(二)状态转换日志记录
为了便于调试和分析系统行为,可以在状态转换时记录日志。记录每次状态转换的时间、原因以及相关参数等信息。
@interface StateTransitionLogger : NSObject
+ (void)logTransitionFromState:(Class)fromState toState:(Class)toState withReason:(NSString *)reason;
@end
@implementation StateTransitionLogger
+ (void)logTransitionFromState:(Class)fromState toState:(Class)toState withReason:(NSString *)reason {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
formatter.dateFormat = @"yyyy - MM - dd HH:mm:ss";
NSString *dateString = [formatter stringFromDate:[NSDate date]];
NSLog(@"[%@] Transition from %@ to %@. Reason: %@", dateString, NSStringFromClass(fromState), NSStringFromClass(toState), reason);
}
@end
在状态转换的地方调用日志记录方法:
@implementation IdleState
- (void)handleInput:(NSString *)input forCharacter:(id)character {
if ([input isEqualToString:@"run"]) {
[StateTransitionLogger logTransitionFromState:[self class] toState:[RunningState class] withReason:@"User input 'run'"];
// 设置角色状态转换逻辑
}
}
@end
(三)状态模式与其他设计模式结合使用
状态模式可以与其他设计模式结合使用,以满足更复杂的需求。例如,与工厂模式结合,可以更方便地创建状态对象。可以创建一个状态工厂类,根据不同的条件创建不同的状态对象。
@interface StateFactory : NSObject
+ (CharacterState *)createStateWithType:(NSString *)type;
@end
@implementation StateFactory
+ (CharacterState *)createStateWithType:(NSString *)type {
if ([type isEqualToString:@"idle"]) {
return [[IdleState alloc] init];
} else if ([type isEqualToString:@"running"]) {
return [[RunningState alloc] init];
} else if ([type isEqualToString:@"jumping"]) {
return [[JumpingState alloc] init];
}
return nil;
}
@end
然后在游戏角色类中使用工厂模式创建状态对象:
@implementation GameCharacter
- (void)setCurrentStateWithType:(NSString *)type {
self.currentState = [StateFactory createStateWithType:type];
}
@end