Java设计模式 —— 【行为型模式】命令模式(Command Pattern) 详解

文章目录


模式介绍

有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么。此时希望用一种松耦合的方式来设计程序,使得请求发送者和请求接收者能够消除彼此之间的耦合关系

拿订餐来说,客人需要向厨师发送请求,但是完全不知道这些厨师的名字和联系方式,也不知道厨师炒菜的方式和步骤。 命令模式把客人订餐的请求封装成 command 对象,也就是订餐中的订单对象。这个对象可以在程序中被四处传递,就像订单可以从服务员手中传到厨师的手中。这样一来,客人不需要知道厨师的名字,从而解开了请求调用者和请求接收者之间的耦合关系。

模式定义:

命令模式是一个行为模式,它是将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行存储、传递、调用、增加与管理。


优缺点

优点:

  • 类间解耦: 调用者角色与接收者角色之间没有任何依赖关系,调用者实现功能时只需调用Command 抽象类的execute方法就可以,不需要了解到底是哪个接收者执行。
  • 可扩展性: Command的子类可以非常容易地扩展,而调用者Invoker和高层次的模块Client不产生严 重的代码耦合。
  • 命令模式结合其他模式会更优秀: 命令模式可以结合责任链模式,实现命令族解析任务;结合模板方法模式,则可以减少 Command子类的膨胀问题。

缺点:

  • 如果有N个命令,Command的子类就可不是几个,而是N个,这个类膨胀得非常大。

适用场景

  • 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
  • 系统需要在不同的时间指定请求、将请求排队和执行请求。
  • 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。

结构

命令模式包含以下主要角色:

  • 抽象命令类(Command)角色: 定义命令的接口,声明执行的方法。
  • 具体命令(Concrete Command)角色: 具体的命令,实现命令接口;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
  • 实现者/接收者(Receiver)角色: 接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
  • 调用者/请求者(Invoker)角色: 要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。

案例实现

以生活中的开灯关灯为例: 在小丽家装了智能家居以后,老王迫不及待的体验了一下遥控关灯开灯功能,这可比手动抹黑找开关方便多了,我们先来分析命令模式的角色在该案例中由谁来充当。

遥控器: 就是调用者角色,由她来发起命令(持有命令)。

电灯: 就是接收者角色,真正命令执行的对象(被命令持有)。

UML类图如下:

抽象命令类:

java 复制代码
public interface Command {
    void execute();
}

开灯,关灯命令【具体命令类】:

java 复制代码
public class LightOffCommand implements Command{
    private LightReceiver lightReceiver;

    public LightOffCommand(LightReceiver lightReceiver) {
        this.lightReceiver = lightReceiver;
    }

    @Override
    public void execute() {
        lightReceiver.off();
    }
}
java 复制代码
public class LightOnCommand implements Command{
    private LightReceiver lightReceiver;

    public LightOnCommand(LightReceiver lightReceiver) {
        this.lightReceiver = lightReceiver;
    }

    @Override
    public void execute() {
        lightReceiver.on();
    }
}

电灯【接收者角色】:

java 复制代码
public class LightReceiver {
    public void on(){
        System.out.println("电灯打开了...");
    }

    public void off(){
        System.out.println("电灯关闭了...");
    }
}

遥控器【请求者角色】:

java 复制代码
public class RemoteController {
    private Command command;

    //添加命令
    public void setCommand(Command command) {
        this.command = command;
    }

    public void executeCommand(){
        if (command != null){
            command.execute();
        }
    }
}

测试:

java 复制代码
public class Client {
    public static void main(String[] args) {
        //遥控器(请求者)
        RemoteController robotInvoker = new RemoteController();
        //电灯(接收者)
        LightReceiver lightReceiver = new LightReceiver();
        //开灯
        LightOnCommand lightOnCommand = new LightOnCommand(lightReceiver);
        robotInvoker.setCommand(lightOnCommand);
        robotInvoker.executeCommand();
        //关灯
        LightOffCommand lightOffCommand = new LightOffCommand(lightReceiver);
        robotInvoker.setCommand(lightOffCommand);
        robotInvoker.executeCommand();
    }
}

注意事项

  • Command 接口非常简单,通常只有一个execute方法,如果要支持撤销操作的话,再加一个unexecute方法;
  • 每个具体的命令类内部封装了实际执行命令的那个类(Recevier),或者那些类,以及执行需要的数据;
  • 每个具体命令类只完成一个请求,有多少个请求就有多少个命令;
  • Invoker类只认识接口Command,其他的都不认识;
  • 客户端类负责生成命令,并通过Invoker组装执行。
相关推荐
sin22011 分钟前
springmvc--对日期类型如何处理
java·开发语言
java熊猫11 分钟前
Ruby语言的正则表达式
开发语言·后端·golang
孇雙20 分钟前
SpringBoot之核心配置
java·spring boot·spring
guihong00429 分钟前
消息队列:原理、问题与设计全解析
java·面试·rabbitmq
monstercl31 分钟前
C# 特性
开发语言·c#·特性
芙莉莲教你写代码1 小时前
腾讯云AI代码助手编程挑战赛——贪吃蛇小游戏
java·vscode·python·学习·腾讯云ai代码助手
丁总学Java1 小时前
/src/utils/request.ts:axios 请求封装,适用于需要统一处理请求和响应的场景
开发语言·javascript·ecmascript
redemption_21 小时前
超市管理系统(javaweb+mysql+redis)
java·mysql
胡尔摩斯.1 小时前
Mybatis
java·后端·spring·mybatis