首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >命令模式(封装命令)

命令模式(封装命令)

作者头像
幺鹿
发布2018-08-21 15:35:26
1.3K0
发布2018-08-21 15:35:26
举报
文章被收录于专栏:Java呓语Java呓语

公告

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

前言

随着上文 装饰器模式 中的顾客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()等方法,通过这些方法可以调用请求接收者的相关操作;
  • 具体命令类是抽象命令类的子类,实现了在抽象命令类中声明的方法,它对应具体的接收者对象,将接收者对象的动作绑定其中;
  • 调用者即请求的发送者,又称为请求者,它通过命令对象来执行请求;
  • 接收者执行与请求相关的操作,它具体实现对请求的业务处理。

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

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

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

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

命令模式适用情况包括:

  • 需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互;
  • 需要在不同的时间指定请求、将请求排队和执行请求;
  • 需要支持命令的撤销操作和恢复操作,需要将一组操作组合在一起,即支持宏命令。
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017.02.09 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 公告
  • 前言
  • 正文
  • 程序员视角
    • 代码实现
    • 总结
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档