Java命令模式以及来自lambda的优化(一)

2017-11-04  本文已影响0人  java部落

前言

设计模式是软件工程中一些问题的统一解决方案的模型,它的出现是为了解决一些普遍存在的,却不能被语言特性直接解决的问题,随着软件工程的发展,设计模式也会不断的进行更新,本文介绍的是经典设计模式-命令模式以及来自java8的lambda的对它的优化。

什么是命令模式

命令模式把一个请求或者操作封装到一个对象中。命令模式允许系统使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能。 (摘自<大话设计模式>)

我不想把问题弄的特别复杂,我的理解是,命令模式就是对一段命令的封装,而命令,就是行为,行为在java语言里可以理解为方法,所以说命令模式就是对一些行为或者说是一些方法的封装。下面我举一个简单的例子来描述这种模式,然后再讲解它的特点。

例子

场景描述

有一家路边的小摊,做的小本经营,只有一个做饭的师傅,师傅饭做的还不错,可来吃饭的人们总是抱怨老板记性不好,有时候订单一多就给忘了,路人甲:"老板!来份牛肉饭!",老板:"好勒!马上给您做!",路人乙:"老板!来份啤酒鸭!",老板:"没问题!",路人丙:"老板!一份西红柿炒鸡蛋!",老板:"ok!",路人丁:"老板!两份啤酒鸭!",老板:"好的好的!"(内心:我晕,怎么有点记不过来了...)

基础实现

老板在这里同时扮演了做饭的角色 对于一个餐馆来说也就是厨房类

public class Kitchen {
    public void beefRice(){
        System.out.println("一份牛肉饭做好了!");
    }

    public void scrambledEggsWithTomatoes(){
        System.out.println("一份西红柿炒鸡蛋做好了!");
    }

    public void beerDuck(){
        System.out.println("一份啤酒鸭做完啦!");
    }
}

客户端代码

public static void main(String[] args){
    Kitchen boss = new Kitchen();
    boss.beefRice();
    boss.beefRice();
    boss.beerDuck();
    boss.beerDuck();
    boss.beerDuck();
    boss.beefRice();
}

代码十分的简单,可是通过观察发现,客户和厨房直接交互了,如果一旦请求多了起来,就如同上面老板说的,怎么感觉头有点晕那...从代码的角度来说,这样的耦合度也太高了,举个例子,如果现在有一个客户不想点了,那应该怎么办呢?取消订单代码写在Kitchen类里,显然不现实,写在客户端里,似乎又不太靠谱.... 再举一个例子,鸡蛋已经用完了,不能给客户提供西红柿炒鸡蛋了,那这个拒绝的代码又写在哪里呢?写在Kitchen?可以是可以,但是你把业务逻辑和基础领域模型混在一起真的好吗?写在客户端里?所有的逻辑都丢在客户端第一层显然是不明智的。
废话了半天,终于有一个顾客受不了了:"老板你这样也太累了吧!不如招个服务员给你记我们要点的饭的订单,记完了送到厨房给你,你按照这个订单做就行了,这样你全身心的投入做饭,这样能提高做饭的效率,也不会出现先点的顾客您忘记做等了半天嚷嚷着要退钱了!至于取消修改订单还是拒绝订单,都交给服务员去处理,处理好了送过来给您,这样各司其职,效率变高了,大家不是都开心嘛!"
那么很显然故事中的这个顾客就很有软件工程的天赋 :)

使用传统命令模式实现

命令模式,就是上文中的顾客所提出的建议,下面就是具体的实现类。
首先要添加服务员类,负责添加,拒绝,修改或者删除订单,或者增加订单的日志相关信息,其实非常简单,简单的增加逻辑即可,这里为了演示,只演示拒绝订单和输出日志
其次既然是命令模式,那肯定要有命令类,这里将厨房做饭的三个方法分别构造成三个命令对象,将他们的共同部分抽象成公共的抽象命令类,方便后面向上转型。

以下为代码抽象命令类,包含一个执行命令的对象和方法,子类继承该类,对抽象的实现方法进行不同的实现

public abstract class BaseCommand {
    protected Kitchen kitchen;

    public BaseCommand(Kitchen kitchen) {
        this.kitchen = kitchen;
    }

    public abstract void executeCommand();
}

下面是三个继承抽象命令类的具体命令,也就是将来客户端要添加的具体的独立的订单命令,结构完全一样
做牛肉饭命令类

public class beefRiceCommand extends BaseCommand {

    public beefRiceCommand(Kitchen kitchen) {
        super(kitchen);
    }

    @Override
    public void executeCommand() {
       kitchen.beefRice();
    }
}

做啤酒鸭命令类

public class beerDuckCommand extends BaseCommand {

    public beerDuckCommand(Kitchen kitchen) {
        super(kitchen);
    }

    @Override
    public void executeCommand() {
       kitchen.beerDuck();
    }
}

做西红柿炒鸡蛋命令类

public class EggsWithTomatoesCommand extends BaseCommand {

    public EggsWithTomatoesCommand(Kitchen kitchen) {
        super(kitchen);
    }

    @Override
    public void executeCommand() {
        kitchen.scrambledEggsWithTomatoes();
    }
}

下面是服务员类,这里为了演示使用队列实现了增加订单与拒绝订单和添加日志,删除中间订单也很简单,数据结构换成linkedList或者arrayList即可,拒绝订单为了演示只是简单的通过类名来判断

public class Waiter {
    /** 用于存储订单命令的队列 */
    private final Queue<BaseCommand> orders ;

    public  Waiter() {
        orders = new ArrayDeque<>();
    }

    /**
     * 添加订单
     * @param baseCommand 客户端传来的命令类,向上转型
     */
    public final void setOrders(BaseCommand baseCommand){
        if (baseCommand.getClass().getName().equals(EggsWithTomatoesCommand.class.getName())) {
            System.out.println("啤酒鸭卖完了,换一个点点吧!");
        } else {
            String[] names = baseCommand.getClass().getName().split("\\.");
            System.out.printf("添加订单: %s 订单时间: %s \n", names[names.length - 1], LocalDateTime.now());
            orders.add(baseCommand);
        }
    }

    /**
     * 通知厨房开始做饭,遍历队列,做完了的订单移出队列
     */
    public final void notifyKitchen(){
        while (orders.peek() != null) {
            orders.peek().executeCommand();
            orders.remove();
        }
    }
}

客户端类

public class Client {
    public static void main(String[] args) {
        //准备厨房,服务员,菜单命令工作
        Kitchen kitchen = new Kitchen();
        Waiter waiter = new Waiter();
        BaseCommand beefRiceCommand = new beefRiceCommand(kitchen);
        BaseCommand beerDuckCommand = new beerDuckCommand(kitchen);
        BaseCommand eggsWithTomatoesCommand = new EggsWithTomatoesCommand(kitchen);

        //开始营业
        System.out.println("=======================添加订单环节=======================");
        // 顾客:服务员 一份牛肉饭!
        waiter.setOrders(beefRiceCommand);
        // 顾客:服务员 一份啤酒鸭!
        waiter.setOrders(beerDuckCommand);
        // 顾客:服务员 一份西红柿炒鸡蛋!
        waiter.setOrders(eggsWithTomatoesCommand);
        // 顾客:服务员 两份啤酒鸭!
        waiter.setOrders(beerDuckCommand);
        waiter.setOrders(beerDuckCommand);

        System.out.println("==========服务员将订单送至厨房,厨房按照订单顺序开始做饭=========");
        //服务员通知厨房按照订单顺序开始做
        waiter.notifyKitchen();

    }
}

运行结果

=======================添加订单环节=======================
添加订单: beefRiceCommand 订单时间: 2017-10-16T02:44:13.631 
添加订单: beerDuckCommand 订单时间: 2017-10-16T02:44:13.650 
啤酒鸭卖完了,换一个点点吧!
添加订单: beerDuckCommand 订单时间: 2017-10-16T02:44:13.650 
添加订单: beerDuckCommand 订单时间: 2017-10-16T02:44:13.650 
==========服务员将订单送至厨房,厨房按照订单顺序开始做饭=========
一份牛肉饭做好了!
一份啤酒鸭做完啦!
一份啤酒鸭做完啦!
一份啤酒鸭做完啦!

Process finished with exit code 0

总结与思考

总结

上面的例子应该并不难理解,这里列出命令模式的uml图<来源于《head first》>

命令模式涉及到五个角色,它们分别是:

下面谈一谈命令模式的优缺点

优点

缺点

思考

命令模式的优点是显而易见的,解耦复合易扩展动态控制,简直棒!可他的这个缺点似乎有时候也挺头疼的,那么具体的进行分析与思考,命令模式的优点几乎全部集中于这个服务员类,也就是invoke角色里,而剩下的1个抽象接口与它下面的n个子类只是为了将厨房类(receiver角色)里的每一个行为(方法)给抽象出来。问题来了,为什么要用类去包装这个行为才能抽象呢?事实上,行为抽象是函数式语言的特性之一,java此前并没有这个语言特性,所以没办法,只能用单独新增一个类来包裹这个行为来代替,可是这一点自从Java8出来之后就不一样了,Java8的函数式特性完全可以将命令模式的这一缺点给优化掉!因此,我们只需要保留服务员类,剩余的行为命令包装类使用尝试使用函数接口来代替,这样既保持了优点,又规避了缺点!

欢迎加入学习交流群569772982,大家一起学习交流。

上一篇下一篇

猜你喜欢

热点阅读