超级链接: Java常用设计模式的实例学习系列-绪论

参考:《HeadFirst设计模式》


1.关于状态模式

命令模式是一种行为型模式。

命令模式:允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。

本文以射击游戏的武器状态为场景来学习命令模式

  • 射击武器的状态有:有子弹、没子弹。
  • 射击武器的操作有:射击、填充子弹。
  • 有子弹时可以[射击],射击会造成伤害(0~maxPower)。没子弹不能射击,需要[填充子弹]操作填充子弹。
  • 需求变化-可能增加的状态:致死状态(有子弹 && 100伤害)等。

2.实现方式1:if-else/switch

第一种实现方式很简单:将不同状态值设置为不同的静态常量,然后在每个操作相关的方法中,通过if-else/switch条件控制语句进行所有的状态转换。

直接上代码吧。

原始实现

/*** <p>射击武器</P>** @author hanchao*/
@Slf4j
public class Shooter {/*** 武器名称*/@NonNullprivate String name;/*** 武器下限*/@NonNullprivate Integer minPower;/*** 武器上限*/@NonNullprivate Integer maxPower;/*** 弹匣大小*/@NonNullprivate Integer maxSize;/*** 剩余子弹数*/private Integer nowSize;/*** 枪械状态*/private Integer state;public Shooter(@NonNull String name, @NonNull Integer minPower, @NonNull Integer maxPower, @NonNull Integer maxSize) {this.name = name;this.minPower = minPower;this.maxPower = maxPower;this.maxSize = maxSize;this.nowSize = maxSize;this.state = STATE_LOADED;log.info("-----------------------------------");log.info("得到一把[{}],伤害范围[{} ~ {}],弹匣大小[{}]。", name, minPower, maxPower, maxSize);}/*** 枪械状态-已装载子弹*/public static final int STATE_LOADED = 0;/*** 枪械状态-未装载子弹*/public static final int STATE_UNLOAD = 1;/*** 射击*/public void shoot() {//如果有子弹,则直接释放if (STATE_LOADED == state) {//造成伤害Integer damage = RandomUtils.nextInt(minPower, maxPower);log.info("使用[{}]进行射击,造成[{}]伤害!还剩余[{}]颗子弹。", name, damage, --nowSize);//如果剩余子弹为0,则转换为无子弹状态if (nowSize == 0) {state = STATE_UNLOAD;}} else {//如果无子弹,则提示log.info("[{}]没有子弹了,请填充!!!", name);}}/*** 填充子弹*/public void fill() {log.info("[{}]当前共[{}]发子弹,填充了[{}]发子弹。", name, nowSize, (maxSize - nowSize));nowSize = maxSize;state = STATE_LOADED;}public static void main(String[] args) {//手枪:沙漠孤鹰Shooter shooter = new Shooter("Desert-Eagle", 30, 45, 2);shooter.shoot();shooter.shoot();shooter.shoot();shooter.fill();shooter.fill();shooter.shoot();//突击步枪:AK-47shooter = new Shooter("AK-47", 80, 95, 3);shooter.shoot();shooter.shoot();shooter.shoot();shooter.shoot();shooter.shoot();shooter.fill();}
}

运行结果:

2019-07-26 13:57:16,834  INFO - -----------------------------------
2019-07-26 13:57:16,838  INFO - 得到一把[Desert-Eagle],伤害范围[30 ~ 45],弹匣大小[2]。
2019-07-26 13:57:16,841  INFO - 使用[Desert-Eagle]进行射击,造成[33]伤害!还剩余[1]颗子弹。
2019-07-26 13:57:16,841  INFO - 使用[Desert-Eagle]进行射击,造成[44]伤害!还剩余[0]颗子弹。
2019-07-26 13:57:16,841  INFO - [Desert-Eagle]没有子弹了,请填充!!!
2019-07-26 13:57:16,841  INFO - [Desert-Eagle]当前共[0]发子弹,填充了[2]发子弹。
2019-07-26 13:57:16,841  INFO - [Desert-Eagle]当前共[2]发子弹,填充了[0]发子弹。
2019-07-26 13:57:16,841  INFO - 使用[Desert-Eagle]进行射击,造成[38]伤害!还剩余[1]颗子弹。
2019-07-26 13:57:16,841  INFO - -----------------------------------
2019-07-26 13:57:16,841  INFO - 得到一把[AK-47],伤害范围[80 ~ 95],弹匣大小[3]。
2019-07-26 13:57:16,841  INFO - 使用[AK-47]进行射击,造成[92]伤害!还剩余[2]颗子弹。
2019-07-26 13:57:16,841  INFO - 使用[AK-47]进行射击,造成[86]伤害!还剩余[1]颗子弹。
2019-07-26 13:57:16,841  INFO - 使用[AK-47]进行射击,造成[94]伤害!还剩余[0]颗子弹。
2019-07-26 13:57:16,841  INFO - [AK-47]没有子弹了,请填充!!!
2019-07-26 13:57:16,842  INFO - [AK-47]没有子弹了,请填充!!!
2019-07-26 13:57:16,842  INFO - [AK-47]当前共[0]发子弹,填充了[3]发子弹。

需求变化:新增致死状态(有子弹 && 100伤害)

如果新增了状态,则需要做两件事:

  1. 新增一个静态常量作为致死状态的标记。

        /*** 枪械状态-致死状态*/public static final int STATE_DEADLY = 2;
    
  2. 修改射击操作shoot()导致的状态变化逻辑。

                 //如果有子弹,则直接释放if (STATE_LOADED == state) {//造成伤害Integer damage = RandomUtils.nextInt(minPower, maxPower);log.info("使用[{}]进行射击,造成[{}]伤害!还剩余[{}]颗子弹。", name, damage, --nowSize);//如果剩余子弹为0,则转换为无子弹状态if (nowSize == 0) {state = STATE_UNLOAD;} else {//如果还有子弹,则有30%概率进入致死状态Integer score = RandomUtils.nextInt(0, 100);if (score < 30) {log.info("进入致死状态");state = STATE_DEADLY;}}} else if (STATE_DEADLY == state) {log.info("使用[{}]进行射击,造成[100]伤害!还剩余[{}]颗子弹。", name, --nowSize);//如果剩余子弹为0,则转换为无子弹状态if (nowSize == 0) {state = STATE_UNLOAD;}else {//如果还有子弹,则有70%概率退出致死状态Integer score = RandomUtils.nextInt(0, 100);if (score < 70) {log.info("退出致死状态");state = STATE_LOADED;}}} else {//如果无子弹,则提示log.info("[{}]没有子弹了,请填充!!!", name);}
    

如果在增加几种状态呢?比方说:易失状态(有子弹 && 50%不能击中目标)、致盲状态(有子弹 && 100%不能击中目标)等等?

这样最终的结果就是:射击操作shoot()导致的状态变化逻辑可能有几千行的代码。

缺点:

  • 耦合度高:同一操作导致的所有状态变化都放在一起(同一个方法)处理。
  • 实现复杂:同一操作导致的所有状态变化都在一段代码中,需要一次性考虑全部状态的转换关系,逻辑复杂。
  • 容错性低:同一操作导致的所有状态变化都在一段代码中,当状态有增减时,修改代码出错概率较高。

3.实现方式2:状态模式

状态模式的实现方式:将不同的状态抽象成不同的,然后在不同的状态类中,在每个操作相关的方法中,通过if-else/switch条件控制语句去控制与此状态相关的状态转换。

第二种实现方式与第一种实现方式的不同在于:

  1. 每种状态不再仅仅是一个静态常量,而是抽象为一个类。
  2. 对每种操作导致的状态转换,不再是针对所有的状态,而是针对与当前状态类相关的状态。

射击状态抽象:AbstractState

因为有多种射击状态,所以必然需要将其抽象出来,即:AbstractState

作用在射击状态上的操作有:射击shoot和填充子弹fill

因为所有状态的填充子弹操作fill都是一样的,所以直接对其进行实现。

/*** <p>射击状态</P>** @author hanchao*/
@Slf4j
@AllArgsConstructor
public abstract class AbstractState {/*** 状态所属对象*/@Getterprivate Shooter shooter;/*** 射击*/public abstract void shoot();/*** 填充子弹*/public void fill() {log.info("[{}]当前共[{}]发子弹,填充了[{}]发子弹。", shooter.getName(), shooter.getNowSize(), (shooter.getMaxSize() - shooter.getNowSize()));shooter.setNowSize(shooter.getMaxSize());shooter.setCurrentState(shooter.getLoadedState());}
}

然后,分别实现有子弹、无子弹状态。

射击状态实现:有子弹:LoadedState

/*** <p>有子弹状态</P>** @author hanchao*/
@Slf4j
public class LoadedState extends AbstractState {public LoadedState(Shooter shooter) {super(shooter);}/*** 射击*/@Overridepublic void shoot() {//造成伤害Integer damage = RandomUtils.nextInt(super.getShooter().getMinPower(), super.getShooter().getMaxPower());super.getShooter().setNowSize(super.getShooter().getNowSize() - 1);log.info("使用[{}]进行射击,造成[{}]伤害!还剩余[{}]颗子弹。", super.getShooter().getName(), damage, super.getShooter().getNowSize());//如果剩余子弹为0,则转换为无子弹状态if (super.getShooter().getNowSize() == 0) {super.getShooter().setCurrentState(super.getShooter().getUnloadState());}}
}

射击状态实现:无子弹:UnloadState

/*** <p>无子弹状态</P>** @author hanchao*/
@Slf4j
public class UnloadState extends AbstractState {public UnloadState(Shooter shooter) {super(shooter);}/*** 射击*/@Overridepublic void shoot() {//如果无子弹,则提示log.info("[{}]没有子弹了,请填充!!!", super.getShooter().getName());}
}

客户类:Shooter

射击状态已经写完了,现在开始进行客户类的编写。这里需要注意两点:

  • 为了能够知道当前是哪种状态,客户类应该有一个变量叫做当前状态,例如:currentState
  • 为了使得状态类能够设置状态转换,一个提供当前状态的setter方法。
/*** <p>射击武器</P>** @author hanchao*/
@Getter
@Setter
@Slf4j
public class Shooter {/*** 武器名称*/@NonNullprivate String name;/*** 武器下限*/@NonNullprivate Integer minPower;/*** 武器上限*/@NonNullprivate Integer maxPower;/*** 弹匣大小*/@NonNullprivate Integer maxSize;/*** 剩余子弹数*/private Integer nowSize;/*** 当前枪械状态*/private AbstractState currentState;/*** 有子弹状态*/private AbstractState loadedState;/*** 无子弹状态*/private AbstractState unloadState;public Shooter(@NonNull String name, @NonNull Integer minPower, @NonNull Integer maxPower, @NonNull Integer maxSize) {this.name = name;this.minPower = minPower;this.maxPower = maxPower;this.maxSize = maxSize;this.nowSize = maxSize;log.info("-----------------------------------");log.info("得到一把[{}],伤害范围[{} ~ {}],弹匣大小[{}]。", name, minPower, maxPower, maxSize);//状态初始化loadedState = new LoadedState(this);unloadState = new UnloadState(this);currentState =  loadedState;}/*** 射击*/public void shoot() {currentState.shoot();}/*** 填充子弹*/public void fill() {currentState.fill();}
}

需求变化:新增致死状态(有子弹 && 100伤害)

如果新增了状态,则需要做两件事:

  1. 新增一个射击状态实现类作为致死状态的标记。

    射击状态实现:致死状态:DeadlyState

    /*** <p>致死状态</P>** @author hanchao*/
    @Slf4j
    public class DeadlyState extends AbstractState {public DeadlyState(Shooter shooter) {super(shooter);}/*** 射击*/@Overridepublic void shoot() {super.getShooter().setNowSize(super.getShooter().getNowSize() - 1);log.info("使用[{}]进行射击,造成[100]伤害!还剩余[{}]颗子弹。", super.getShooter().getName(), super.getShooter().getNowSize());//如果剩余子弹为0,则转换为无子弹状态if (super.getShooter().getNowSize() == 0) {super.getShooter().setCurrentState(super.getShooter().getUnloadState());} else {//如果还有子弹,则有70%概率退出致死状态Integer score = RandomUtils.nextInt(0, 100);if (score < 70) {log.info("退出致死状态");super.getShooter().setCurrentState(super.getShooter().getLoadedState());}}}
    }
    
  2. 修改与致死状态相关的状态类的射击操作shoot()导致的状态变化逻辑。

    普通有子弹状态与致死状态有转换关系,所以需要修改其shoot方法:

        @Overridepublic void shoot() {//造成伤害Integer damage = RandomUtils.nextInt(super.getShooter().getMinPower(), super.getShooter().getMaxPower());super.getShooter().setNowSize(super.getShooter().getNowSize() - 1);log.info("使用[{}]进行射击,造成[{}]伤害!还剩余[{}]颗子弹。", super.getShooter().getName(), damage, super.getShooter().getNowSize());//如果剩余子弹为0,则转换为无子弹状态if (super.getShooter().getNowSize() == 0) {super.getShooter().setCurrentState(super.getShooter().getUnloadState());} else {//如果还有子弹,则有30%概率进入致死状态Integer score = RandomUtils.nextInt(0, 100);if (score < 30) {log.info("进入致死状态");super.getShooter().setCurrentState(super.getShooter().getDeadlyState());}}}
    

    无子弹状态与致死状态无转换关系,所以不需要修改其代码。

如果在增加几种状态呢?比方说:易失状态(有子弹 && 50%不能击中目标)、致盲状态(有子弹 && 100%不能击中目标)等等?

那么,我们需要增减多个状态类,然后分别考虑其状态转换关系。

优点:

  • 耦合度低:同一操作导致的状态变化按照不同的状态分别(多个方法)处理。
  • 实现复杂:同一操作导致的所有状态变化分布处于不同的状态类,只需要考虑与本状态相关的转换关系,逻辑相对简单。
  • 容错性低:同一操作导致的所有状态变化分布处于不同的状态类,当状态有增减时,只需要修改与本状态相关的状态类,修改代码出错概率相对较低。

4.总结

最后以UML类图来总结本文的射击游戏的武器状态场景以及状态模式。

设计模式-状态模式-以射击游戏的武器状态为例相关推荐

  1. 状态模式设计程序:游戏中英雄根据不同的体力值可以进行休息、防御、普通攻击、技能攻击。

    资源下载地址:https://download.csdn.net/download/sheziqiong/85639034 一.应用场景与案例描述 我们经常在课余时间玩游戏以放松身心,缓解压力.在很多 ...

  2. 市面上几款第一人称射击游戏的武器后坐力效果观察

    前言 后坐力和子弹散射不是一回事,后坐力是枪械开火时准心和视角的跑动情况,子弹散射是子弹出了枪口以后的偏移. 另外,我们讨论的游戏只是部分游戏,不代表市场全貌.并且由于后续游戏的更新,可能会和本文中的 ...

  3. 策略模式的应用——游戏中武器的选择

    策略模式(Strategy Pattern):定义一系列算法类,将每一个算法封装起来,并让它们可以相互替换,策略模式让算法独立于使用它的客户而变化,也称为政策模式(Policy).策略模式是一种对象行 ...

  4. 设计模式 策略模式 以角色游戏为背景

    今天不想写代码,给大家带来一篇设计模式的文章,帮助大家可以把系统组织成容易了解.容易维护.具有弹性的架构. 先来看看策略模式的定义: 策略模式(Strategy Pattern):定义了算法族,分别封 ...

  5. Java状态模式实现工作流_关于使用“状态模式”做工作流概要。

    usingSystem;usingSystem.Collections.Generic;usingSystem.Text;namespaceWF {//工程名称:通过工作流状态进行工作流管理///Au ...

  6. 多人联机射击游戏中的设计模式应用

    转:http://blog.csdn.net/lovelion/article/details/8262987 反恐精英(Counter-Strike, CS).三角洲部队.战地等多人联机射击游戏广受 ...

  7. 多人联机射击游戏中的设计模式应用(二)

          (6) 观察者模式      联机射击游戏可以实时显示队友和敌人的存活信息,如果有队友或敌人阵亡,所有在线游戏玩家将收到相应的消息,可以提供一个统一的中央角色控制类(CenterContr ...

  8. C#常用设计模式(Unity)——游戏场景的转换——状态模式(State)

    此文章原文来源于<设计模式与完美游戏开发>(蔡升达著),笔者只是在学习过程中受益颇多,从而进行了总结,有兴趣的读者可以去阅读原书. 1.场景的转换 当游戏比较复杂的时候,通常会设计多个场景 ...

  9. 在游戏中看状态机与状态模式

    状态机与状态模式 状态机 最早接触状态机这个词来自编译原理的学习,在词法分析中,通过有限状态机来进行单词识别.状态机在里面被定义为一个数学模型,一个五元组. 截图来自维基百科 对于Android开发者 ...

最新文章

  1. leetcode算法题--两数相加
  2. 【BZOJ-3156】防御准备 DP + 斜率优化
  3. Linux下Shell文件内容替换(sed)(转)
  4. 关于“我的藏书阁:.NET/数据库应用开发”的几点看法。
  5. 苹果应用审核走进中国!
  6. springboot2.4+nettyWebServerApplicationContext@15f51c50 has been closed already问题解决
  7. 塔设备设计手册_强烈推荐必备资料—化工设备设计手册 (上、下卷全)
  8. 中国象棋棋谱棋书链接
  9. 宿舍校园网路由器配置原理及指南
  10. php 屏蔽deprecated,php7.2.8 Deprecated错误不能隐藏处理
  11. CSS超链接标记大全
  12. 74HC161异步计数器仿真示例
  13. 【读点论文】EfficientFormer: Vision Transformers at MobileNet Speed,运用纯transformer架构对比卷积模型在终端上部署的推理速度
  14. 怎么设置路由器当交换机用
  15. Linux安装tar软件教程,tar的简单实用及linux常见软件的安装
  16. 对龙邱科技TC264库的理解
  17. RGB 和 CYMK 的区别
  18. Django Linux环境安装
  19. AM5728开发深度学习之安装 caffe-jacinto
  20. 微软明年关闭诺基亚功能手机应用商店

热门文章

  1. 计算机控制音响阵列,线阵列音响的原理及其应用特点浅析
  2. 我的世界java材质包推荐下载_我的世界高清修复32x材质包下载大全【1.6.x-1.8.x】...
  3. 凌思微-LE5010蓝牙开发(一)
  4. 基础理论知识复习(下)
  5. php开源协同办公系统-信呼,信呼协同办公系统 全开源v1.6.7版
  6. inotify-tools-3.14+rsync3.1.3实时同步安装部署
  7. 免杀Backdoor-factory
  8. 操作系统from清华大学向勇,陈渝 笔记(二)操作系统的启动、中断、异常、系统调用
  9. 超构透镜专栏(2)——超构透镜的原理与进展
  10. Java 8备受宠爱,HarmonyOS冲刺全球第三大操作系统,全民热议元宇宙|2021十大技术热词...