设计模式 - 状态模式

状态模式(State Pattern)

一. 概念

在一个对象内部状态变化时改变其行为,使其看上去像改变自身属性一样。状态模式也可以被理解为策略模式,因为它能够切换通用的接口改变状态,来实现策略

二. 使用场合

  1. 对象的行为依赖于该对象的状态,从而进行不同行为
  2. 某个类需根据成员变量的当前值改变自身行为,从而使用大量的条件语句时

三. 优点及缺点

  • 优点:
    1. 单一职责原则,将与特定状态相关的代码放在单独类中
    2. 开闭原则,无需修改已有状态类和上下文即可引入新的状态
  • 缺点:
    1. 若状态很少时,或者很少改变,使用该模式会显得冗余

四. 结构图

五. 小试牛刀

5.1 问题

超级马里奥是笔者小时候最爱玩的游戏之一,该游戏的核心是马里奥通过层层阻碍救公主,但通过层层阻碍的时候,自身没有一些小把戏,有些关卡很难通过,所以当马里奥吃蘑菇时,会变大; 吃火焰花时,会拥有武器; 吃星星时,在星星生效期间会无敌

问题抛出:

此次马里奥拥有4个形态:分别为小型马里奥,超级马里奥,火焰马里奥,无敌马里奥,下面我阐述规则

  • 获得蘑菇,火焰花,无敌星星时;
    1. 蘑菇,火焰花每个只能获取一次,不能重复获取。无敌星星可重复获取,来刷新无敌时效;
    2. 无敌星星的时效2s;
    3. 蘑菇和火焰可叠加,蘑菇火焰星星同理;
  • 碰见怪物时
    1. 小型马里奥时,碰见怪兽会死亡,重新复活;
    2. 超级马里奥时,碰见怪兽会变回小型马里奥;
    3. 火焰马里奥时,碰见怪兽会丢失火焰形态;
    4. 无敌马里奥时,在有效期间碰见怪兽无敌,若未在无效期间时,同自身状态来采取回退策略;

5.2 uml

在这里插入图片描述

5.3 代码

⭐️MarioState

马里奥状态抽象类:用于统一封装碰见不同的物种所抽象出来的方法

package com.zhanghp.demo01;

/**
 * @author zhanghp
 * @date 2022-09-06 10:46
 */
public abstract class MarioState {

    protected MarioContext marioContext;

    public MarioState(MarioContext marioContext) {
        this.marioContext = marioContext;
    }

    protected abstract void obtainMushroon();

    protected abstract void obtainFire();

    protected abstract void obtainStar();

    protected abstract void meetingMonster();

}

⭐️MarioContext

马里奥调用类:马里奥的通用属性,更换状态及状态获取

package com.zhanghp.demo01;

import com.zhanghp.demo01.common.MarioType;
import com.zhanghp.demo01.state.SmallMario;
import lombok.Getter;
import lombok.Setter;

/**
 * @author zhanghp
 * @date 2022-09-06 10:54
 */
public class MarioContext {
    private MarioState marioState;

    @Setter
    private MarioType marioType;

    @Setter
    @Getter
    private boolean superFlag = false;

    @Setter
    @Getter
    private boolean fireFlag = false;

    public MarioContext() {
        // 初始化马里奥形态 -》小型马里奥
        this.marioState = new SmallMario(this);
    }

    /**
     * 获取mairo
     *
     * @return Mario形态类
     */
    public MarioState getMarioState() {
        return this.marioState;
    }

    /**
     * 切换马里奥形态
     *
     * @param mario 所需切换的马里奥形态
     */
    public void changeMario(MarioState mario) {
        this.marioState = mario;
    }

    /**
     * 获取马里奥的当前状态
     *
     * @return 马里奥当前状态信息
     */
    public String getMarioType(){
        return this.marioType.getState();
    }
}

⭐️MarioStrategy And MarioLambdaStrategy

策略相关:当马里奥改变自身状态时,一些通用属性也需更换,所以采用策略来实现

/**
 * @author zhanghp
 * @date 2022-09-06 15:54
 */
@FunctionalInterface
public interface MarioStrategy {
    /**
     * 马里奥形态处理策略
     *
     * @param marioContext 马里奥上下文类
     */
    void excute(MarioContext marioContext);
}

/**
 * 马里奥形态处理策略
 *
 * @author zhanghp
 * @date 2022-09-06 15:58
 */
public enum MarioLambdaStrategy {
    SMALL(it -> {
        // 切换为小型马里奥
        it.changeMario(new SmallMario(it));
        // 关闭超级形态
        it.setSuperFlag(false);
        // 关闭火焰状态
        it.setFireFlag(false);
    }),
    SUPER(it -> {
        // 切换为超级马里奥
        it.changeMario(new SuperMario(it));
        // 激活超级形态
        it.setSuperFlag(true);
        // 关闭火焰形态
        it.setFireFlag(false);
    }),
    FIRE(it -> {
        // 切换为火焰马里奥
        it.changeMario(new FireMario(it));
        // 激活火焰形态
        it.setFireFlag(true);
    }),
    INVINCIBLE(it -> it.changeMario(new InvincibleMario(it)));

    private final MarioStrategy marioStrategy;

    MarioLambdaStrategy(MarioStrategy marioStrategy) {
        this.marioStrategy = marioStrategy;
    }

    public void excute(MarioContext marioContext){
        this.marioStrategy.excute(marioContext);
    }
}

⭐️状态实现类

  • SmallMario 小型马里奥

    package com.zhanghp.demo01.state;
    
    import com.zhanghp.demo01.MarioState;
    import com.zhanghp.demo01.MarioContext;
    import com.zhanghp.demo01.common.MarioMessage;
    import com.zhanghp.demo01.common.MarioType;
    import com.zhanghp.demo01.strategy.MarioLambdaStrategy;
    import lombok.extern.slf4j.Slf4j;
    
    /**
     * 小型马里奥
     *
     * @author zhanghp
     * @date 2022-09-06 10:48
     */
    @Slf4j
    public class SmallMario extends MarioState {
    
        public SmallMario(MarioContext marioContext) {
            super(marioContext);
            // 马里奥形态
            super.marioContext.setMarioType(MarioType.SMALL);
        }
    
        @Override
        protected void obtainMushroon() {
            // 获取蘑菇 -》 超级马里奥
            MarioLambdaStrategy.SUPER.excute(super.marioContext);
            log.info(MarioMessage.OBTAIN_MUSHROOM.getMsg());
        }
    
        @Override
        protected void obtainFire() {
            // 获取火焰 -》 火焰马里奥
            MarioLambdaStrategy.FIRE.excute(super.marioContext);
            log.info(MarioMessage.OBTAIN_FIRE.getMsg());
        }
    
        @Override
        protected void obtainStar() {
            // 获取星星 -》 无敌马里奥
            MarioLambdaStrategy.INVINCIBLE.excute(super.marioContext);
            log.info(MarioMessage.OBTAIN_STAR.getMsg());
        }
    
        @Override
        protected void meetingMonster() {
            log.info(MarioMessage.MEETING_MONSTER_RESTARTING.getMsg());
        }
    }
    
    
  • SuperMario 超级马里奥

    package com.zhanghp.demo01.state;
    
    import com.zhanghp.demo01.MarioState;
    import com.zhanghp.demo01.common.MarioMessage;
    import com.zhanghp.demo01.common.MarioType;
    import com.zhanghp.demo01.MarioContext;
    import com.zhanghp.demo01.strategy.MarioLambdaStrategy;
    import lombok.extern.slf4j.Slf4j;
    
    /**
     * 超级马里奥
     *
     * @author zhanghp
     * @date 2022-09-06 10:48
     */
    @Slf4j
    public class SuperMario extends MarioState {
    
        public SuperMario(MarioContext marioContext) {
            super(marioContext);
            // 激活超级形态
            super.marioContext.setSuperFlag(true);
            // 马里奥状态
            super.marioContext.setMarioType(MarioType.SUPER);
        }
    
        @Override
        protected void obtainMushroon() {
        }
    
        @Override
        protected void obtainFire() {
            // 获取火焰 -》 火焰马里奥
            MarioLambdaStrategy.FIRE.excute(super.marioContext);
            log.info(MarioMessage.OBTAIN_FIRE.getMsg());
        }
    
        @Override
        protected void obtainStar() {
            // 获取星星 -》 无敌马里奥
            MarioLambdaStrategy.INVINCIBLE.excute(super.marioContext);
            log.info(MarioMessage.OBTAIN_STAR.getMsg());
        }
    
        @Override
        protected void meetingMonster() {
            // 形态回退到马里奥
            MarioLambdaStrategy.SMALL.excute(super.marioContext);
            log.info(MarioMessage.MEETING_MONSTER_BACK_TO_SMALL.getMsg());
        }
    }
    
    
  • FireMario 火焰马里奥

    package com.zhanghp.demo01.state;
    
    import com.zhanghp.demo01.MarioState;
    import com.zhanghp.demo01.MarioContext;
    import com.zhanghp.demo01.common.MarioMessage;
    import com.zhanghp.demo01.common.MarioType;
    import com.zhanghp.demo01.strategy.MarioLambdaStrategy;
    import lombok.extern.slf4j.Slf4j;
    
    /**
     * 火焰马里奥
     *
     * @author zhanghp
     * @date 2022-09-06 10:48
     */
    @Slf4j
    public class FireMario extends MarioState {
    
        public FireMario(MarioContext marioContext) {
            super(marioContext);
            // 激活火焰形态
            super.marioContext.setFireFlag(true);
            // 马里奥状态
            super.marioContext.setMarioType(MarioType.FIRE);
        }
    
        @Override
        protected void obtainMushroon() {
            // 是否为超级马里奥
            if (!super.marioContext.isSuperFlag()) {
                super.marioContext.setSuperFlag(true);
                log.info(MarioMessage.OBTAIN_MUSHROOM.getMsg());
            }
        }
    
        @Override
        protected void obtainFire() {}
    
        @Override
        protected void obtainStar() {
            // 获取星星 -》 无敌马里奥
            MarioLambdaStrategy.INVINCIBLE.excute(super.marioContext);
            log.info(MarioMessage.OBTAIN_STAR.getMsg());
        }
    
        @Override
        protected void meetingMonster() {
            // 马里奥形态判断,采取遇见怪兽的处理
            if (super.marioContext.isSuperFlag()) {
                MarioLambdaStrategy.SUPER.excute(super.marioContext);
                log.info(MarioMessage.MEETING_MONSTER_BACK_TO_SUPER.getMsg());
            }else{
                MarioLambdaStrategy.SMALL.excute(super.marioContext);
                log.info(MarioMessage.MEETING_MONSTER_BACK_TO_SMALL.getMsg());
            }
        }
    }
    
  • InvincibleMario 无敌马里奥

    package com.zhanghp.demo01.state;
    
    import com.zhanghp.demo01.MarioState;
    import com.zhanghp.demo01.MarioContext;
    import com.zhanghp.demo01.common.MarioMessage;
    import com.zhanghp.demo01.common.MarioEnglishMessage;
    import com.zhanghp.demo01.common.MarioType;
    import com.zhanghp.demo01.strategy.MarioLambdaStrategy;
    import lombok.extern.slf4j.Slf4j;
    
    /**
     * 无敌马里奥
     *
     * @author zhanghp
     * @date 2022-09-06 10:48
     */
    @Slf4j
    public class InvincibleMario extends MarioState {
    
        /**
         * 当前时间戳,用于校验无敌状态
         */
        private long currentTimeStamp;
    
        /**
         * 无敌状态持续时间2s
         */
        private final int VALID_TIME = 2_000;
    
        public InvincibleMario(MarioContext marioContext) {
            super(marioContext);
            // 设置获取星星的时间戳
            currentTimeStamp = System.currentTimeMillis();
            // 马里奥状态
            super.marioContext.setMarioType(MarioType.INVINCIBLE);
        }
    
        @Override
        protected void obtainMushroon() {
            // 未获得蘑菇
            if (!super.marioContext.isSuperFlag()) {
                super.marioContext.setSuperFlag(true);
                log.info(MarioMessage.OBTAIN_MUSHROOM.getMsg());
            }
        }
    
        @Override
        protected void obtainFire() {
            // 未获得火焰子弹
            if (!super.marioContext.isFireFlag()) {
                super.marioContext.setFireFlag(true);
                log.info(MarioMessage.OBTAIN_FIRE.getMsg());
            }
        }
    
        @Override
        protected void obtainStar() {
            // 重置无敌状态
            MarioLambdaStrategy.INVINCIBLE.excute(super.marioContext);
            log.info(MarioMessage.OBTAIN_STAR_AGAIN.getMsg());
        }
    
        @Override
        protected void meetingMonster() {
            // 无敌状态时效校验
            boolean flag = (currentTimeStamp + VALID_TIME) > System.currentTimeMillis();
            // 是否激活超级形态
            final boolean SUPER_FLAG = super.marioContext.isSuperFlag();
            // 是否激活火焰形态
            final boolean FIRE_FLAG = super.marioContext.isFireFlag();
    
            // 无敌状态时效校验
            if (flag) {
                log.info(MarioMessage.MEETING_MONSTER_INVINCIBLE.getMsg());
                return;
            }
            log.info(MarioEnglishMessage.INVINCIBLE_EXPIRED.getMsg());
    
            // 无敌过期后,形态回退处理
            if(FIRE_FLAG && SUPER_FLAG){
                MarioLambdaStrategy.SUPER.excute(super.marioContext);
                log.info(MarioMessage.MEETING_MONSTER_BACK_TO_SUPER.getMsg());
            }else {
                MarioLambdaStrategy.SMALL.excute(super.marioContext);
                if (FIRE_FLAG || SUPER_FLAG) {
                    log.info(MarioMessage.MEETING_MONSTER_BACK_TO_SMALL.getMsg());
                }else {
                    log.info(MarioMessage.MEETING_MONSTER_RESTARTING.getMsg());
                }
            }
        }
    }
    

⭐️常量信息相关枚举

  • **MarioType **马里奥类型

    package com.zhanghp.demo01.common;
    
    /**
     * 马里奥形态种类
     *
     * @author zhanghp
     * @date 2022-09-06 11:00
     */
    public enum MarioType {
        SMALL("小型马里奥"),
        SUPER("超级马里奥"),
        FIRE("火焰马里奥"),
        INVINCIBLE("无敌马里奥");
    
        private final String state;
    
        MarioType(String state) {
            this.state = state;
        }
    
        public String getState(){
            return "----->" + "现在的状态:" +this.state + "<-----";
        }
    
    }
    
  • MarioMessage 马里奥相关信息

    package com.zhanghp.demo01.common;
    
    import lombok.Getter;
    
    /**
     * @author zhanghp
     * @date 2022-09-06 16:15
     */
    public enum MarioMessage {
        OBTAIN_MUSHROOM("获得蘑菇"),
        OBTAIN_FIRE("获得火焰"),
        OBTAIN_STAR("获得星星"),
        OBTAIN_STAR_AGAIN("再次获得星星"),
        MEETING_MONSTER_RESTARTING("遇见怪兽,重新开始游戏"),
        MEETING_MONSTER_INVINCIBLE("遇见怪兽,无敌状态中...."),
        MEETING_MONSTER_BACK_TO_SMALL("遇见怪兽,变成小型马里奥"),
        MEETING_MONSTER_BACK_TO_SUPER("遇见怪兽,变成超级马里奥"),
        INVINCIBLE_EXPIRED("无敌状态失效...");
    
        @Getter
        private final String msg;
    
        MarioMessage(String msg) {
            this.msg = msg;
        }
    
    }
    

5.4 UT测试用例

  • 代码
@Slf4j
public class App {
    public static void main(String[] args) throws InterruptedException {
        MarioContext marioManuplate = new MarioContext();

        // 获取蘑菇 -》 超级马里奥
        marioManuplate.getMarioState().obtainMushroon();

        // 获取火焰 -》火焰马里奥
        marioManuplate.getMarioState().obtainFire();

        // 遇见怪物 -》退回超级马里奥
        marioManuplate.getMarioState().meetingMonster();

        // 获取星星 -》无敌马里奥
        marioManuplate.getMarioState().obtainStar();

        // 遇见怪物 -》无敌状态
        marioManuplate.getMarioState().meetingMonster();

        // 等待无敌状态过期
        Thread.sleep(3_000);

        // 遇见怪物 -》无敌状态失效 -》 退回小型马里奥
        marioManuplate.getMarioState().meetingMonster();

        // 马里奥的当前状态
        log.info(marioManuplate.getMarioType());
    }
}


public class TestMario {

    @Test
    void testMario(){
        Assertions.assertDoesNotThrow(() -> App.main(new String[]{}));
    }
}
  • 结果输出
- 获得蘑菇
- 获得火焰
- 遇见怪兽,变成超级马里奥
- 获得星星
- 遇见怪兽,无敌状态中....
- 无敌状态失效...
- 遇见怪兽,变成小型马里奥
- ----->现在的状态:小型马里奥<-----

六. 参考资料及源码

Refactoring GURU:https://refactoring.guru/design-patterns/state


⭐️源码地址https://gitee.com/zhp1221/design_pattern/tree/master/lab_07_state



版权声明:本文为zhangHP_123原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。