美文网首页设计模式
命令模式(封装命令)

命令模式(封装命令)

作者: 幺鹿 | 来源:发表于2017-02-09 17:51 被阅读34次

    公告

    如果您是第一次阅读我的设计模式系列文章,建议先阅读设计模式开篇,希望能得到您宝贵的建议。

    前言

    随着上文 装饰器模式 中的顾客Alice购买了机器人回家后,他开始了机器人使用之旅。

    正文

    机器人:“你好,Alice!”
    Alice:“你好!你叫什么?”
    机器人:“Alice,我是你忠臣的仆人 Samu。”
    Alice:“Samu,你有什么功能?”
    机器人:“我会唱歌,我也会跳舞。Alice,你只要说 唱歌+歌曲名称或者跳舞+舞蹈名称我就会唱歌跳舞。如果你连续的说唱歌+歌曲名称 跳舞+舞蹈名称,我会一边唱歌一边跳舞。”
    Alice:“唱歌稻香 跳舞肚皮舞”
    机器人:“对这个世界如果你有太多的抱怨”
    机器人:“跌倒了 就不敢继续往前走”
    机器人:“为什么 人要这么的脆弱 堕落”
    ……(画外音:曼妙的舞姿)……

    程序员视角

    现在要实现对机器人Samu说(发出指令) 唱歌+歌曲名称或者跳舞+舞蹈名称,机器人便会自动的唱歌或跳舞。

    代码实现

    定义命令的接口的目的是为了抽象类型,并且将命令实现分离。

    public interface ICommand {
        void excute();
    }
    

    内部命令抽象对象,用于提供命令的上下文。

    public abstract class Command implements ICommand {
    
        private String param;
    
        public Command(String param) {
            this.param = param;
        }
    
        protected String param() {
            return param;
        }
    }
    
    

    唱歌命令的实现(跳舞命令实现类似)

    
    public class SongCommandImpl extends Command {
    
        public static final String KEY_SONG = "唱歌";
    
        public SongCommandImpl(String param) {
            super(param);
        }
    
        @Override
        public void excute() {
            System.out.println("调用指令 " + KEY_SONG + param());
        }
    }
    
    

    命令的接口与实现均已准备妥当,接下去是思考如何调用命令。
    为了避免客户端与具体的命令对象耦合,所以通常建议搭配适配器模式唱歌跳舞这些指令,转化为程序可理解的SongCommandImplDanceCommandImpl

    // 适配器对象用于适配字符串到命令的执行接口
    public class StringCommandAdapter implements ICommand {
    
        private String method;
        private String param;
        private HashMap<String, Command> map = new HashMap<>();
    
        private Command pickCommand(String method, String param) {
            Command command = null;
            if (method.startsWith(DanceCommandImpl.KEY_DANCE)) {
                command = createCommand(DanceCommandImpl.KEY_DANCE, param, DanceCommandImpl.class);
            } else if (method.startsWith(SongCommandImpl.KEY_SONG)) {
                command = createCommand(SongCommandImpl.KEY_SONG, param, SongCommandImpl.class);
            }
            return command;
        }
    
        private Command createCommand(String key, String param, Class<?> clazz) {
            if (map.containsKey(key)) {
                return map.get(key);
            }
            Command command = null;
            try {
                Constructor<?> constructor = clazz.getConstructor(String.class);
                command = (Command) constructor.newInstance(param);
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
            if (command != null) {
                map.put(key, command);
                return command;
            } else {
                throw new IllegalArgumentException("NO COMMAND CREATED!!!");
            }
        }
    
        public StringCommandAdapter(String method, String param) {
            this.method = method;
            this.param = param;
        }
    
        @Override
        public void excute() {
            System.out.println("调用指令:" + toString());
            Command command = pickCommand(this.method, this.param);
            command.excute();
        }
    
        @Override
        public String toString() {
            return "StringCommandAdapter{" +
                    "method='" + method + '\'' +
                    ", param='" + param + '\'' +
                    ", map=" + map +
                    '}';
        }
    }
    

    命令如何构建已经完毕,接着是命令如何被触发。这里模拟构建机器人接收到命令在触发

    // 构建命令管理器,命令的日志跟踪都可以在这里实现。
    public class CommandManager {
    
        public void invoke(StringCommandAdapter adapter) {
            adapter.excute();
        }
    
    }
    

    考虑到通常命令都是通过观察者接收到消息后才触发调用的,所以这里模拟了观察者接收到消息的调用过程。

    public class SamuCommandReceiver {
    
        private CommandManager invoke = new CommandManager();
        private Machine machine;
    
        public SamuCommandReceiver(Machine machine) {
            this.machine = machine;
            System.out.printf("机器人%s的接收功能正常开启%n", machine);
        }
    
        public void onReceive(String command, String param) {
            System.out.printf("机器人%s接收到指令:%s,%s%n", machine, command, param);
            invoke.invoke(new StringCommandAdapter(command, param));
        }
    }
    

    客户端的调用

        public static void main(String args[]) {
    
    
            Machine machine = new Machine("Samu");
    
            SamuCommandReceiver receiver = new SamuCommandReceiver(machine);
    
            receiver.onReceive("唱歌", "稻香");
            receiver.onReceive("跳舞", "肚皮舞");
        }
    
    

    运行结果

    创建了机器人 Samu
    机器人Samu的接收功能正常开启
    机器人Samu接收到指令:唱歌,稻香
    调用指令:StringCommandAdapter{method='唱歌', param='稻香', map={}}
    调用指令 唱歌稻香
    机器人Samu接收到指令:跳舞,肚皮舞
    调用指令:StringCommandAdapter{method='跳舞', param='肚皮舞', map={}}
    调用指令 跳舞肚皮舞
    

    总结

    在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计,使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活。

    命令模式可以对发送者和接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求。这就是命令模式的模式动机。

    命令模式

    在命令模式中,将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。命令模式是一种对象行为型模式,其别名为动作模式或事务模式。

    命令模式包含四个角色:

    • 抽象命令类中声明了用于执行请求的execute()等方法,通过这些方法可以调用请求接收者的相关操作;
    • 具体命令类是抽象命令类的子类,实现了在抽象命令类中声明的方法,它对应具体的接收者对象,将接收者对象的动作绑定其中;
    • 调用者即请求的发送者,又称为请求者,它通过命令对象来执行请求;
    • 接收者执行与请求相关的操作,它具体实现对请求的业务处理。

    命令模式的本质是对命令进行封装,将发出命令的责任和执行命令的责任分割开。

    命令模式使请求本身成为一个对象,这个对象和其他对象一样可以被存储和传递。

    命令模式的主要优点在于降低系统的耦合度,增加新的命令很方便,而且可以比较容易地设计一个命令队列和宏命令,并方便地实现对请求的撤销和恢复;

    其主要缺点在于可能会导致某些系统有过多的具体命令类。

    命令模式适用情况包括:

    • 需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互;
    • 需要在不同的时间指定请求、将请求排队和执行请求;
    • 需要支持命令的撤销操作和恢复操作,需要将一组操作组合在一起,即支持宏命令。

    相关文章

      网友评论

        本文标题:命令模式(封装命令)

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