美文网首页
iOS设计模式之状态模式上

iOS设计模式之状态模式上

作者: 点滴86 | 来源:发表于2024-06-26 01:02 被阅读0次

    状态模式

    状态模式并不是很常用,但是在能够用到的场景里,它可以发挥很大的作用。状态模式一般用来实现状态机,而状态机常用在游戏、工作流引擎等系统开发中。不过状态机的实现方式有多种,除了状态模式,比较常用的还有分支逻辑法和查表法。

    什么是有限状态机

    有限状态机,英文翻译是Finite State Machine,缩写为FSM,简称为状态机。状态机有3个组成部分:状态(State)、事件(Event)、动作(Action)。其中,事件也称为转移条件(Transition Condition)。事件触发状态的转移及动作的执行。不过,动作不是必须的,也可能只转移状态,不执行任何动作。
    ”超级马里奥“游戏中,马里奥可以变身为多种形态,比如小马里奥(Small Mario)、超级马里奥(Super Mario)、火焰马里奥(Fire Mario)、斗篷马里奥(Cape Mario)等等。在不同的游戏请接下,各个状态会互相转化,并相应的增减积分。比如,初始形态是小马里奥,吃了蘑菇之后就会变成超级马里奥,并且增加积分。
    实际上,马里奥形态的转变就是一个状态机。其中,马里奥的不同形态就是状态机中的”状态“,游戏情节(比如吃了蘑菇)就是状态机中的”事件“,加减积分就是状态机中的”动作“。比如,吃蘑菇这个事件,会触发状态的转移;从小马里奥转移到超级马里奥,以及触发动作的执行(增加100积分)。
    为了方便讲解,对游戏背景做了简化,只保留了部分状态和事件。简化之后的状态转移如下图所示:

    状态机实现方式一:分支逻辑法

    对于如何实现状态机,其中最简单直接的实现方式是,参照状态转移图,将每一个状态转移,原模原样地直译成代码。这样编写的代码会包含大量的if-else或switch-case分支判断逻辑,甚至是嵌套的分支判断逻辑,所以,把这种方法暂且命名为分支逻辑法。代码实现如下所示:

    typedef enum : NSInteger {
        DMMarioStateSmall = 0,
        DMMarioStateSuper,
        DMMarioStateCape,
        DMMarioStateFire,
    } DMMarioState;
    
    @interface DMMarioStateMachine : NSObject
    
    - (void)obtainMushRoom;
    
    - (void)obtainCape;
    
    - (void)obtainFireFlower;
    
    - (void)meetMonster;
    
    - (NSInteger)getScore;
    
    - (DMMarioState)getCurrentState;
    
    @end
    
    @interface DMMarioStateMachine ()
    
    @property (nonatomic, assign) NSInteger mScore;
    
    @property (nonatomic, assign) DMMarioState mMarioState;
    
    @end
    
    @implementation DMMarioStateMachine
    
    - (instancetype)init
    {
        if (self = [super init]) {
            self.mScore = 0;
            self.mMarioState = DMMarioStateSmall;
        }
        return self;
    }
    
    // 分支法
    - (void)obtainMushRoom
    {
        if (self.mMarioState == DMMarioStateSmall) {
            self.mScore += 100;
            self.mMarioState = DMMarioStateSuper;
        }
    }
    
    - (void)obtainCape
    {
        if (self.mMarioState == DMMarioStateSmall ||
            self.mMarioState == DMMarioStateSuper) {
            self.mScore += 200;
            self.mMarioState = DMMarioStateCape;
        }
    }
    
    - (void)obtainFireFlower
    {
        if (self.mMarioState == DMMarioStateSmall ||
            self.mMarioState == DMMarioStateSuper) {
            self.mScore += 300;
            self.mMarioState = DMMarioStateFire;
        }
    }
    
    - (void)meetMonster
    {
        if (self.mMarioState == DMMarioStateSuper) {
            self.mScore -= 100;
            self.mMarioState = DMMarioStateSmall;
        } else if (self.mMarioState == DMMarioStateCape) {
            self.mScore -= 200;
            self.mMarioState = DMMarioStateSmall;
        } else if (self.mMarioState == DMMarioStateFire) {
            self.mScore -= 300;
            self.mMarioState = DMMarioStateSmall;
        }
    }
    
    - (NSInteger)getScore
    {
        return self.mScore;
    }
    
    - (DMMarioState)getCurrentState
    {
        return self.mMarioState;
    }
    
    @end
    

    对于简单的状态机来说,分支逻辑这种实现方式是可以接受的。但是对于复杂的状态机来说,这种实现方式极易漏写或者错写某个状态转移。除此之外,代码中充斥着大量的if-else或者switch-case分支判断逻辑,可读性和可维护性都很差。如果哪天修改了状态机中的某个状态转移,需要在冗长的分支逻辑中找到对应的代码进行修改,很容易改错,引入bug。

    状态机实现方式二:查表法

    实际上,除了用状态转移图来表示之外,状态机还可以用二维表来表示,如下所示。在这个二维表中,第一维表示当前状态,第二维表示事件,值表示当前状态经过事件之后,转移到的新状态及其执行的动作。

    备注:表中的/表示不存在这种状态转移。

    相对于分支逻辑的实现方式,查表法的代码实现更加清晰,可读性和可维护性更好。当修改状态机时,只需要修改mTransitionTable和mActionTable两个二位数组即可。实际上,如果把这两个二维数组存储在配置文件中,当需要修改状态机时,甚至可以不修改任何代码,只需要修改配置文件就可以了。具体代码如下所示:

    typedef enum : NSInteger {
        DMMarioStateSmall = 0,
        DMMarioStateSuper,
        DMMarioStateCape,
        DMMarioStateFire,
    } DMMarioState;
    
    typedef enum : NSInteger {
        DMMarioEventGotMushRoom = 0,
        DMMarioEventGotCape,
        DMMarioEventGotFire,
        DMMarioEventMeetMonster,
    } DMMarioEvent;
    
    @interface DMMarioStateMachine : NSObject
    
    - (void)obtainMushRoom;
    
    - (void)obtainCape;
    
    - (void)obtainFireFlower;
    
    - (void)meetMonster;
    
    - (NSInteger)getScore;
    
    - (DMMarioState)getCurrentState;
    
    @end
    
    @interface DMMarioStateMachine ()
    
    @property (nonatomic, assign) NSInteger mScore;
    
    @property (nonatomic, assign) DMMarioState mMarioState;
    
    @property (nonatomic, strong) NSArray *mTransitionTable;
    
    @property (nonatomic, strong) NSArray *mActionTable;
    
    @end
    
    @implementation DMMarioStateMachine
    
    - (instancetype)init
    {
        if (self = [super init]) {
            self.mScore = 0;
            self.mMarioState = DMMarioStateSmall;
        }
        return self;
    }
    
    // 查表法
    - (void)obtainMushRoom
    {
        [self executeEvent:DMMarioEventGotMushRoom];
    }
    
    - (void)obtainCape
    {
        [self executeEvent:DMMarioEventGotCape];
    }
    
    - (void)obtainFireFlower
    {
        [self executeEvent:DMMarioEventGotFire];
    }
    
    - (void)meetMonster
    {
        [self executeEvent:DMMarioEventMeetMonster];
    }
    
    - (void)executeEvent:(DMMarioEvent)event
    {
        {
            NSArray *tmpArray = [self.mTransitionTable objectAtIndex:self.mMarioState];
            self.mMarioState = (DMMarioState)[tmpArray objectAtIndex:event];
        }
        
        {
            NSArray *tmpArray = [self.mActionTable objectAtIndex:self.mMarioState];
            NSNumber *num = [tmpArray objectAtIndex:event];
            self.mScore += [num integerValue];
        }
    }
    
    - (NSInteger)getScore
    {
        return self.mScore;
    }
    
    - (DMMarioState)getCurrentState
    {
        return self.mMarioState;
    }
    
    - (NSArray *)mTransitionTable {
        if (_mTransitionTable == nil) {
            NSMutableArray *tmpArray = [[NSMutableArray alloc] init];
            {
                NSMutableArray *subArray = [[NSMutableArray alloc] init];
                [subArray addObject:[NSNumber numberWithInteger:DMMarioStateSuper]];
                [subArray addObject:[NSNumber numberWithInteger:DMMarioStateCape]];
                [subArray addObject:[NSNumber numberWithInteger:DMMarioStateFire]];
                [subArray addObject:[NSNumber numberWithInteger:DMMarioStateSmall]];
                [tmpArray addObject:subArray];
            }
            {
                NSMutableArray *subArray = [[NSMutableArray alloc] init];
                [subArray addObject:[NSNumber numberWithInteger:DMMarioStateSuper]];
                [subArray addObject:[NSNumber numberWithInteger:DMMarioStateCape]];
                [subArray addObject:[NSNumber numberWithInteger:DMMarioStateFire]];
                [subArray addObject:[NSNumber numberWithInteger:DMMarioStateSmall]];
                [tmpArray addObject:subArray];
            }
            {
                NSMutableArray *subArray = [[NSMutableArray alloc] init];
                [subArray addObject:[NSNumber numberWithInteger:DMMarioStateCape]];
                [subArray addObject:[NSNumber numberWithInteger:DMMarioStateCape]];
                [subArray addObject:[NSNumber numberWithInteger:DMMarioStateCape]];
                [subArray addObject:[NSNumber numberWithInteger:DMMarioStateSmall]];
                [tmpArray addObject:subArray];
            }
            {
                NSMutableArray *subArray = [[NSMutableArray alloc] init];
                [subArray addObject:[NSNumber numberWithInteger:DMMarioStateFire]];
                [subArray addObject:[NSNumber numberWithInteger:DMMarioStateFire]];
                [subArray addObject:[NSNumber numberWithInteger:DMMarioStateFire]];
                [subArray addObject:[NSNumber numberWithInteger:DMMarioStateSmall]];
                [tmpArray addObject:subArray];
            }
            _mTransitionTable = [tmpArray copy];
        }
        
        return _mTransitionTable;
    }
    
    - (NSArray *)mActionTable {
        if (_mActionTable == nil) {
            NSMutableArray *tmpArray = [[NSMutableArray alloc] init];
            {
                NSMutableArray *subArray = [[NSMutableArray alloc] init];
                [subArray addObject:[NSNumber numberWithInteger:100]];
                [subArray addObject:[NSNumber numberWithInteger:200]];
                [subArray addObject:[NSNumber numberWithInteger:300]];
                [subArray addObject:[NSNumber numberWithInteger:0]];
                [tmpArray addObject:subArray];
            }
            {
                NSMutableArray *subArray = [[NSMutableArray alloc] init];
                [subArray addObject:[NSNumber numberWithInteger:0]];
                [subArray addObject:[NSNumber numberWithInteger:200]];
                [subArray addObject:[NSNumber numberWithInteger:300]];
                [subArray addObject:[NSNumber numberWithInteger:-100]];
                [tmpArray addObject:subArray];
            }
            {
                NSMutableArray *subArray = [[NSMutableArray alloc] init];
                [subArray addObject:[NSNumber numberWithInteger:0]];
                [subArray addObject:[NSNumber numberWithInteger:0]];
                [subArray addObject:[NSNumber numberWithInteger:0]];
                [subArray addObject:[NSNumber numberWithInteger:-200]];
                [tmpArray addObject:subArray];
            }
            {
                NSMutableArray *subArray = [[NSMutableArray alloc] init];
                [subArray addObject:[NSNumber numberWithInteger:0]];
                [subArray addObject:[NSNumber numberWithInteger:0]];
                [subArray addObject:[NSNumber numberWithInteger:0]];
                [subArray addObject:[NSNumber numberWithInteger:-300]];
                [tmpArray addObject:subArray];
            }
            _mActionTable = [tmpArray copy];
        }
        
        return _mActionTable;
    }
    
    @end
    

    相关文章

      网友评论

          本文标题:iOS设计模式之状态模式上

          本文链接:https://www.haomeiwen.com/subject/izvjcjtx.html