状态模式(State Pattern)
一. 概念
在一个对象内部状态变化时改变其行为,使其看上去像改变自身属性一样。状态模式也可以被理解为策略模式,因为它能够切换通用的接口改变状态,来实现策略
二. 使用场合
- 对象的行为依赖于该对象的状态,从而进行不同行为
- 某个类需根据成员变量的当前值改变自身行为,从而使用大量的条件语句时
三. 优点及缺点
- 优点:
- 单一职责原则,将与特定状态相关的代码放在单独类中
- 开闭原则,无需修改已有状态类和上下文即可引入新的状态
- 缺点:
- 若状态很少时,或者很少改变,使用该模式会显得冗余
四. 结构图
五. 小试牛刀
5.1 问题
超级马里奥是笔者小时候最爱玩的游戏之一,该游戏的核心是马里奥通过层层阻碍救公主,但通过层层阻碍的时候,自身没有一些小把戏,有些关卡很难通过,所以当马里奥吃蘑菇时,会变大; 吃火焰花时,会拥有武器; 吃星星时,在星星生效期间会无敌
问题抛出:
此次马里奥拥有4个形态:分别为小型马里奥,超级马里奥,火焰马里奥,无敌马里奥,下面我阐述规则
- 获得蘑菇,火焰花,无敌星星时;
- 蘑菇,火焰花每个只能获取一次,不能重复获取。无敌星星可重复获取,来刷新无敌时效;
- 无敌星星的时效2s;
- 蘑菇和火焰可叠加,蘑菇火焰星星同理;
- 碰见怪物时
- 小型马里奥时,碰见怪兽会死亡,重新复活;
- 超级马里奥时,碰见怪兽会变回小型马里奥;
- 火焰马里奥时,碰见怪兽会丢失火焰形态;
- 无敌马里奥时,在有效期间碰见怪兽无敌,若未在无效期间时,同自身状态来采取回退策略;
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版权协议,转载请附上原文出处链接和本声明。