命令设计模式

作者: 给你添麻烦了 | 来源:发表于2017-02-22 15:16 被阅读17次

    什么是命令设计模式

    通过将“请求”封装成命令对象,来将操作的请求者与操作的执行者解耦,以便使用不同的请求队列或者日志来参数化其他对象,命令模式也支持可撤销的操作。

    为什么要使用命令设计模式

    试想下面一种业务场景。
    现在拥有一个中间件,名称为 cache。这个 cache 承担了异步向用户外发送邮件与短信的功能。
    系统中已经有设计好的独立的邮件与短信对象,如下:


    屏幕快照 2016-12-27 下午2.33.36.png-16.4kB屏幕快照 2016-12-27 下午2.33.36.png-16.4kB

    发送一封邮件要比发送短信复杂的多。所以,MAIL 对象相对于 SMS 对象要复杂不少。除了 title 与 body 以外,还可以设置邮件的附件。

    现在让我们开始设计中间件的具体逻辑。我的思路很清晰:首先设计一个中间件。为了能够快速的从容器中获取和添加对象,我们采用 LinkedList 对象。
    然后将发邮件与发短信的任务逐个添加进 cache 中,最后循环 cache,做出相应处理。

    /**
     * 发送邮件
     */
    public class MAIL {
        public void setMailTitle() {
            System.out.println("设置邮件标题");
        }
    
        public void setMailBody() {
            System.out.println("设置邮件内容");
        }
    
        public void setMailAttachment() {
            System.out.println("设置邮件附件");
        }
    
        public void sendMail() {
            System.out.println("发送邮件");
        }
    }
    
    /**
     *
     *
     */
    public class SMS {
    
        public void setMsg() {
            System.out.println("设置短信内容");
        }
    
        public void sendSms() {
            System.out.println("发送短信");
        }
    }
    
    
    import java.util.LinkedList;
    import java.util.List;
    
    /**
     * 命令设计模式
     * 中间件任务执行器
     */
    public class JobExecuter {
    
        final static private List<Object> caches = new LinkedList<>();
    
        /**
         * 执行中间件任务
         */
        private static void executeJobs() {
            for (Object cache : caches) {
                if (cache instanceof MAIL) {
                    ((MAIL) cache).setMailTitle();
                    ((MAIL) cache).setMailBody();
                    ((MAIL) cache).setMailAttachment();
                    ((MAIL) cache).sendMail();
                } else if (cache instanceof SMS) {
                    ((SMS) cache).setMsg();
                    ((SMS) cache).sendSms();
                }
            }
        }
    
    
        public static void main(String[] args) {
            caches.add(new MAIL());
            caches.add(new SMS());
            executeJobs();
        }
    }
    

    为了简明扼要的说明设计模式,我们省略了各种线程任务的创建执行代码。
    任务已经顺利完成了,中间件能够根据任务类型自动判断不同的业务,并顺利完成需求。

    遗憾的是,这样的设计拥有一些“重大缺陷”。

    1. 请求与执行的耦合度很高。假设需要再增加一个推送 app 消息的业务,那么你的代码很快就会陷入 if else 的海洋。
    1. 类没有对扩展开发,对修改关闭。当需求变化时我们仅仅能够通过修改代码来应对。
    2. executeJobs() 是一个典型的依赖具体对象,而不是接口或抽象的失败案例。
    3. 如果我们想要将发送给某1个用户的消息一起执行,应该怎么办?

    好消息是,命令设计模式能够帮助我们解决上面的所有问题。
    在学习命令设计模式之前,我们先试着用餐厅就餐这个日常生活中的行为,来接近命令设计模式。
    当我们在餐厅就餐时,我们往往会先向服务员点餐,然后通过服务员把点好的餐点送往后厨,并通知后厨根据新订单准备餐点。

    client=>start: 客户
    waiter=>operation: 服务员
    createOrders=>operation: 点餐(createOrders)
    notifyChef=>operation: 通知后厨
    chef=>end: 准备餐点
    
    client->createOrders->waiter->notifyChef->chef
    

    通过点餐创建的订单,屏蔽了不同的客户的不同需求。对于 waiter 来说,不需要知道订单的具体内容。只需要将客户的点餐订单传递给后厨,并通知后厨任务就算完成了。后厨通过订单内容,着手准备工作。

    从餐厅到命令设计模式

    将餐厅想象成 OO 设计模式的一种模型。客户就相当于 client、点餐的动作就相当于创建请求、服务员就相当于异步中间件、后厨则是真正 action 的执行者。
    好了,让我们根据这样的思路重新设计之前的需求。


    屏幕快照 2016-12-27 下午4.37.55.png-44.8kB屏幕快照 2016-12-27 下午4.37.55.png-44.8kB
    
    /**
     * 命令设计模式
     */
    public interface Command {
        void execute();
    }
    
    
    /**
     * 发送邮件
     */
    public class MAIL implements Command {
        public void setMailTitle() {
            System.out.println("设置邮件标题");
        }
    
        public void setMailBody() {
            System.out.println("设置邮件内容");
        }
    
        public void setMailAttachment() {
            System.out.println("设置邮件附件");
        }
    
        public void sendMail() {
            System.out.println("发送邮件");
        }
    
        @Override
        public void execute() {
            setMailAttachment();
            setMailBody();
            setMailTitle();
            sendMail();
        }
    }
    
    
    /**
     * 命令设计模式
     */
    public class SMS implements Command {
    
        public void setMsg() {
            System.out.println("设置短信内容");
        }
    
        public void sendSms() {
            System.out.println("发送短信");
        }
    
        @Override
        public void execute() {
            setMsg();
            sendSms();
        }
    }
    
    
    import java.util.LinkedList;
    import java.util.List;
    
    /**
     * 命令设计模式
     */
    public class CommandInvoker {
    
        private List<Command> caches = new LinkedList<>();
    
        /**
         * 设置Invoker所需要的命令
         *
         * @param e
         */
        public void addCommand(Command e) {
            caches.add(e);
        }
    
        /**
         * 执行中间件任务
         */
        public void executeJobs() {
            for (Command cache : caches) {
                cache.execute();
            }
        }
    
    }
    
    
    /**
     * 命令客户端
     */
    public class CommandClient {
    
        public static void main(String[] args) {
            CommandInvoker commandInvoker = new CommandInvoker();
            Command commandMail = new MAIL();
            Command commandSms = new SMS();
            commandInvoker.addCommand(commandMail);
            commandInvoker.addCommand(commandSms);
            //TODO add command in futrue on pg run
    
            commandInvoker.executeJobs();
    
        }
    }
    
    
    设置邮件附件
    设置邮件内容
    设置邮件标题
    发送邮件
    设置短信内容
    发送短信
    

    问题回顾

    总算完成了。最后再回顾一下之前出现的重大问题,看看是否这些问题都通过命令设计模式都得到了解决

    请求与执行的耦合度很高。假设需要再增加一个推送 app
    消息的业务,那么你的代码很快就会陷入 if else 的海洋。

    通过将请求封装为命令,解耦了请求者与执行者。当有新需求时,新的请求只需要实现命令接口,就能被我们的代码所复用。

    类没有对扩展开发,对修改关闭。当需求变化时我们仅仅能够通过修改代码来应对。

    需求有变化时,通过 setCommand() / addCommand() 方法实现了对扩展开放,对修改关闭。

    1. executeJobs() 是一个典型的依赖具体对象,而不是接口或抽象的失败案例。

    现在依赖的是Command接口对象。

    如果我们想要将发送给某1个用户的消息一起执行,应该怎么办?

    将所需要的任务组合成为一个新的 commandMacro 宏对象。宏对象持有一个 command 对象的数组。并在 execute 中调用数组中所有对象的 execute 方法,就能实现这样的需求。

    总结

    命令设计模式通过一个中间人/件将请求封装为统一的命令,达到了与命令的执行者之间实现解耦的目的。这样的设计使得中间人的代码尽可能的简单,并且能够与任意的对象组合。完整的命令设计模式还拥有 undo,撤销上一次命令的功能。
    你可以试着自己来实现这个功能来加深对命令设计模式的理解。

    相关文章

      网友评论

        本文标题:命令设计模式

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