MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Objective-C 中的状态模式实现与场景分析

2022-12-041.9k 阅读

一、状态模式概述

状态模式(State Pattern)是一种行为型设计模式。它允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。在实际应用中,许多对象的行为依赖于它们的状态,并且在不同状态下执行不同的操作。传统的做法可能是通过大量的条件语句(如 if - elseswitch - case)来处理不同状态下的行为,但这样的代码往往难以维护和扩展。状态模式通过将每个状态封装成独立的类,并让对象在运行时动态切换这些状态类,使得代码更加清晰、可维护和可扩展。

(一)状态模式的角色

  1. 环境(Context)角色:持有一个状态接口的引用,定义客户端感兴趣的接口,并且根据当前状态来委托状态对象处理请求。
  2. 抽象状态(State)角色:定义一个接口以封装与环境对象的一个特定状态相关的行为。
  3. 具体状态(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 类的基类和 DogCat 等子类,它们都实现了 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(跳跃)。

  1. 定义抽象状态类
@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: 方法,用于处理角色在该状态下接收到的输入。

  1. 定义具体状态类
    • 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
  1. 定义环境类(游戏角色类)
@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: 方法将输入委托给当前状态对象处理。

  1. 使用状态模式
GameCharacter *character = [[GameCharacter alloc] init];
character.currentState = [[IdleState alloc] init];
[character handleInput:@"run"];
// 可以动态改变角色状态
character.currentState = [[RunningState alloc] init];
[character handleInput:@"jump"];

(二)以自动售货机为例

  1. 定义抽象状态类
@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
  1. 定义具体状态类
    • 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
  1. 定义环境类(自动售货机类)
@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
  1. 使用状态模式
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)的状态经常需要根据用户操作或系统事件进行切换。例如,一个登录界面可能有以下状态:初始状态(显示用户名和密码输入框及登录按钮)、正在登录状态(显示加载指示器并禁用登录按钮)、登录成功状态(跳转到主界面)、登录失败状态(显示错误提示信息)。

  1. 定义抽象状态类
@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
  1. 定义具体状态类
    • 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
  1. 定义环境类(登录界面类)
@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(请求失败)。

  1. 定义抽象状态类
@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
  1. 定义具体状态类
    • 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
  1. 定义环境类(网络请求类)
@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(关卡完成)。

  1. 定义抽象状态类
@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
  1. 定义具体状态类
    • 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
  1. 定义环境类(关卡类)
@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 中的优势与挑战

(一)优势

  1. 代码清晰易维护:通过将不同状态的行为封装到独立的类中,避免了大量复杂的条件语句。当需要添加新状态或修改现有状态的行为时,只需要在相应的状态类中进行修改,而不会影响其他状态或整体逻辑。例如,在游戏角色状态管理中,如果要添加一个新的 Crouching(蹲下)状态,只需要创建一个新的 CrouchingState 类并实现相关行为,而不需要在原有的庞大条件语句中进行插入和修改。
  2. 可扩展性强:易于扩展系统以处理新的状态。当业务需求发生变化,需要引入新的状态时,状态模式使得添加新状态的过程非常简单。在自动售货机的例子中,如果要添加一个 OutOfStock(缺货)状态,只需要创建一个 OutOfStockState 类并实现相应的插入硬币和分发产品的行为,然后在自动售货机类中进行状态切换的设置即可。
  3. 符合开闭原则:状态模式对扩展开放,对修改关闭。新状态的添加不会影响已有的状态类和环境类的代码。在网络请求状态管理中,如果要添加一个 Cancelled(取消)状态,只需要创建新的 CancelledState 类并实现相关行为,而不需要修改原有的 IdleLoadingSuccessFailure 状态类的代码。

(二)挑战

  1. 状态类数量增加:随着状态的增多,状态类的数量也会相应增加。这可能导致项目中的类文件数量增多,增加了一定的管理成本。例如,在一个复杂的游戏中,角色可能有十几种甚至几十种状态,这就需要创建相应数量的状态类。不过,通过合理的命名和目录结构可以在一定程度上缓解这个问题。
  2. 状态转换逻辑可能复杂:在某些情况下,状态之间的转换逻辑可能比较复杂。需要仔细设计状态转换的条件和顺序,以确保系统的稳定性和正确性。例如,在一个具有多种升级和降级状态的系统中,状态之间的转换可能涉及到多个条件的判断和复杂的业务规则。这就需要开发人员在设计状态模式时充分考虑各种情况,编写详细的文档以方便后续维护。

六、与其他设计模式的关系

(一)与策略模式的比较

  1. 相似性:状态模式和策略模式在结构上有一定的相似性。它们都将行为封装在不同的类中,通过组合的方式让环境对象持有这些行为类的实例,并根据不同情况调用相应的行为。在 Objective - C 代码实现上,两者都通过对象组合来实现行为的动态切换。
  2. 差异性:策略模式侧重于算法的切换,客户端可以根据不同需求选择不同的策略,策略之间通常是平行的关系,并且切换策略一般由客户端决定。而状态模式侧重于对象状态的改变,状态之间往往存在一定的转换关系,状态的切换通常由对象自身的状态和事件驱动。例如,在一个文本排版系统中,使用策略模式可以根据用户选择不同的排版算法(如左对齐、居中对齐、右对齐),而使用状态模式可能用于处理文档在不同编辑状态(如新建、编辑、保存、关闭)下的行为。

(二)与状态机模式的关系

  1. 相似性:状态模式和状态机模式都用于处理对象状态相关的逻辑。它们都关注对象在不同状态下的行为以及状态之间的转换。在实际应用中,状态模式可以看作是状态机模式的一种实现方式。
  2. 差异性:状态机模式通常更侧重于状态转换的整体描述和控制,可能使用状态转移表等方式来定义状态之间的转换规则。而状态模式更强调将每个状态的行为封装成独立的类,通过对象组合和多态性来实现状态相关的行为。在一些简单的状态管理场景中,状态模式可能更易于实现和理解,而在复杂的状态机场景中,可能需要结合状态机模式的一些概念来进行更全面的设计。

七、优化与最佳实践

(一)状态缓存

在一些情况下,状态对象的创建可能比较消耗资源。可以考虑使用状态缓存机制,避免重复创建相同的状态对象。例如,在一个频繁切换状态的游戏角色系统中,可以使用一个字典来缓存已经创建的状态对象,当需要某个状态时,先从字典中查找,如果存在则直接使用,不存在再进行创建。

@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