当前位置 博文首页 > 韩超的博客 (hanchao5272):设计模式-命令模式-以游戏快捷键为例

    韩超的博客 (hanchao5272):设计模式-命令模式-以游戏快捷键为例

    作者:[db:作者] 时间:2021-09-05 16:12

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

    参考:《HeadFirst设计模式》


    1.关于命令模式

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

    命令模式:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可取消的操作。

    本文以游戏快捷键为场景来学习命令模式

    • 在MMORPG游戏中,游戏操作有:角色前进、角色跳跃、释放技能:火球术、释放技能:潜行术、打开背包界面、打开技能界面等等。
    • 需求一:通过按键进行快捷操作。例如:按下空格键,则角色跳跃;按下W键,则角色前进。
    • 需求二:可以替换快捷键。例如:默认角色跳跃的快捷键是空格,用户可以自定义为回车键。
    • 需求三:可以自定义宏命令,一个宏命令可以进行多个操作。例如:按下按键Q,则依次进行:角色前进、释放技能:潜行术、释放技能:火球术。

    2.按键

    无论如何实现三个需求,我们先来实现按键本身。

    游戏按键:枚举:KeyEnum

    /**
     * <p>按键(部分)</P>
     *
     * @author hanchao
     */
    public enum KeyEnum {
        /**
         * 空格键
         */
        KEY_SPACE,
    
        /**
         * 按键:回车
         */
        KEY_ENTER,
    
        /**
         * 按键:A
         */
        KEY_A,
    
        /**
         * 按键:B
         */
        KEY_B,
    
        /**
         * 按键:V
         */
        KEY_V,
    
        /**
         * 按键:W
         */
        KEY_W,
    
        /**
         * 按键:1
         */
        KEY_1,
    
        /**
         * 按键:2
         */
        KEY_2,
    
        /**
         * 按键:无
         */
        KEY_NULL
    }
    

    2.游戏操作

    无论如何实现三个需求,我们先来实现游戏操作本身。

    游戏操作:角色相关:Role

    /**
     * <p>游戏角色</P>
     *
     * @author hanchao
     */
    @Slf4j
    public class Role {
    
        /**
         * 前进
         */
        public static void forward() {
            log.info("角色前进");
        }
    
        /**
         * 跳跃
         */
        public static void jump() {
            log.info("角色跳跃");
        }
    }
    

    游戏操作:技能相关:SkillHandler

    /**
     * <p>技能管理器</P>
     *
     * @author hanchao
     */
    @Slf4j
    public class SkillHandler {
    
        /**
         * 释放技能-五火球神术
         */
        public static void fireBall() {
            log.info("释放技能:火球术...酝酿1秒钟...砰! 砰!! 砰!!!");
        }
    
        /**
         * 释放技能-潜行
         */
        public static void sneak() {
            log.info("释放技能:潜行术...蒙面...抛出烟雾弹...不见了!");
        }
    }
    

    游戏操作:界面相关:UIHandler

    /**
     * <p>游戏界面</P>
     *
     * @author hanchao
     */
    @Slf4j
    public class UiHandler {
        /**
         * 打开背包界面
         */
        public static void showPack() {
            log.info("打开背包界面,背包中现在有:2个面包,3棵草药,5块矿石...");
        }
    
        /**
         * 打开技能界面
         */
        public static void showSkill() {
            log.info("打开技能界面,已掌握的技能有:暗影之舞,剑刃风暴,神圣之光...");
        }
    }
    

    3.实现方式:if-else/switch

    实现思路:

    • 遍历所有按键,然后进行相应的处理。这种实现方式,能够满足第一条需求。
    • 对于第二、三条需求,实现起来比较麻烦。

    4.实现方式:命令模式

    实现命令模式的关键在于:将命令/请求封装为一个对象

    在我们的例子中,控制角色向前释放技能:火球术打开背包界面等等,都是一种命令/请求

    这些命令可以抽象为一个抽象类:命令Command

    4.1.命令抽象:Command

    /**
     * <p>命令:执行、撤销</P>
     *
     * @author hanchao
     */
    public interface Command {
        /**
         * 执行命令
         */
        void execute();
    }
    

    4.2.普通命令实现

    命令实现:角色跳跃:RoleJumpCommand

    角色前进:RoleForwardCommand实现方式类似。

    /**
     * <p>命令:跳跃</P>
     *
     * @author hanchao
     */
    public class RoleJumpCommand implements Command {
    
        /**
         * 执行命令
         */
        @Override
        public void execute() {
            Role.jump();
        }
    }
    

    命令实现:释放技能:火球术:ReleaseFireBallCommand

    释放技能:潜行术:ReleaseSneakCommand实现方式类似。

    /**
     * <p>命令:释放技能:火球术</P>
     *
     * @author hanchao
     */
    public class ReleaseFireBallCommand implements Command {
    
        /**
         * 执行命令
         */
        @Override
        public void execute() {
            SkillHandler.fireBall();
        }
    }
    

    命令实现:打开背包界面:ShowPackCommand

    打开技能界面:ShowSkillCommand实现方式类似。

    /**
     * <p>命令:打开背包界面</P>
     *
     * @author hanchao
     */
    public class ShowPackCommand implements Command {
    
        /**
         * 执行命令
         */
        @Override
        public void execute() {
            UiHandler.showPack();
        }
    }
    

    4.3.默认命令实现

    命令实现:默认命令:DefaultCommand

    可能存在这种情况:某个按键并未关联任何命令,如果按下此键,应该不会产生任何效果。

    为了统一处理上述情况,定义一种默认命令,这样就不必专门去进行非空判断。

    /**
     * <p>默认命令</P>
     *
     * @author hanchao
     */
    @Slf4j
    public class DefaultCommand implements Command {
    
        /**
         * 执行命令
         */
        @Override
        public void execute() {
            log.info("什么也没有发生");
        }
    }
    

    4.4.宏命令实现

    命令实现:宏命令:MacroCommand

    所谓宏命令,就是一次按键,产生多个游戏操作。其实,宏命令本身也是一种命令。

    为了实现多个游戏操作的需求,我们可以通过集合来实现。

    /**
     * <p>宏命令</P>
     * <p>
     * 需求三:可以自定义宏命令,一个宏命令可以进行多个操作。
     *
     * @author hanchao
     */
    @AllArgsConstructor
    public class MacroCommand implements Command {
    
        /**
         * 宏命令列表
         */
        private List<Command> commandList;
    
        /**
         * 执行命令
         */
        @Override
        public void execute() {
            //依次执行命令
            for (Command command : commandList) {
                command.execute();
            }
        }
    }
    

    4.5.三个需求的实现

    下面编写客户代码。

    首先,定义按键管理器``KeyManager`,主要关注点:按键初始化:设置默认按键。

    /**
     * <p>调用者:按键管理器</P>
     *
     * @author hanchao
     */
    @Slf4j
    public class KeyManager {
    
        /**
         * 假设共计32种游戏操作
         */
        private static final int MAX_SIZE = 32;
    
        /**
         * 快捷键列表
         */
        private static Map<KeyEnum, Command> commandMap = new HashMap<>(MAX_SIZE);
    
        static {
            //按键初始化:设置默认按键
            commandMap.put(KeyEnum.KEY_W, new RoleForwardCommand());
            commandMap.put(KeyEnum.KEY_SPACE, new RoleJumpCommand());
            commandMap.put(KeyEnum.KEY_B, new ShowPackCommand());
            commandMap.put(KeyEnum.KEY_V, new ShowSkillCommand());
            commandMap.put(KeyEnum.KEY_1, new ReleaseFireBallCommand());
            commandMap.put(KeyEnum.KEY_2, new ReleaseSneakCommand());
            commandMap.put(KeyEnum.KEY_NULL, new DefaultCommand());
        }
    }
    

    4.5.1.需求一的实现

    需求一:通过按键进行快捷操作。例如:按下空格键,则角色跳跃;按下W键,则角色前进。

    在按键管理器``KeyManager中定义按键press()`方法,实现需求一。

        /**
         * 需求一:通过按键进行快捷操作。
         */
        public static void press(KeyEnum key) {
            //如果旧的按键为空,则置为空按键
            if (Objects.isNull(key)) {
                key = KeyEnum.KEY_NULL;
            }
    
            log.info("按下了「{}」", key.name());
            //执行此按键
            Command command = commandMap.get(key);
            if (Objects.isNull(command)){
                command = new DefaultCommand();
            }
            command.execute();
            log.info("----------");
        }
    

    4.5.2.需求二的实现

    需求二:可以替换快捷键。例如:默认角色跳跃的快捷键是空格,用户可以自定义为回车键。

    在按键管理器``KeyManager中定义设置自定义按键setCustomKey()`方法,实现需求二。

       /**
         * 需求二:可以替换快捷键。
         */
        public static void setCustomKey(Command command, KeyEnum oldKey, KeyEnum newKey) {
            //如果旧的按键为空,则置为空按键
            if (Objects.isNull(oldKey)) {
                oldKey = KeyEnum.KEY_NULL;
            }
    
            //如果新的按键为空,则置为空按键
            if (Objects.isNull(newKey)) {
                newKey = KeyEnum.KEY_NULL;
            }
    
    
            log.info("将「{}」操作的快捷键进行替换:{} --> {}", command.getClass().getSimpleName(), oldKey.name(), newKey.name());
    
            //将旧按键绑定到默认操作
            if (!Objects.equals(oldKey, KeyEnum.KEY_NULL)) {
                commandMap.put(oldKey, new DefaultCommand());
            }
    
            //将新按键绑定到当前操作
            commandMap.put(newKey, command);