命令模式【Command Pattern】,什么是命令模式?作用?优缺点?主要角色?应用场景?实现案例?

目录


设计模式专栏目录(点击进入…)



什么是命令模式?

命令模式(Command Pattern)是一种行为型设计模式,它将一个请求或操作封装为对象,使得请求的发送者和执行者之间解耦。通过这种方式,命令可以被延迟执行、排队执行,甚至支持撤销和重做操作。命令模式还可以使系统支持可扩展的操作集合,而不必修改调用者的代码。


命令模式的作用

(1)解耦调用者和接收者

调用者不需要知道具体实现细节,只需要调用命令对象。

(2)延迟执行和记录日志

可以将命令放入队列中,延迟执行,或记录日志来支持撤销操作。

(3)支持撤销和重做

命令模式使得操作可以被撤销和重做,尤其在编辑器、事务系统等需要回退的场景非常有用。

(4)增加灵活性

通过封装请求,系统可以更灵活地添加新功能或修改现有功能,而不会影响已有的系统结构。


命令模式优缺点

优点

(1)降低耦合性

调用者与实际执行者之间的耦合被消除,双方都独立发展,减少系统维护难度。

(2)扩展性强

可以通过实现新的命令类来添加新功能,而不需要修改现有代码,符合开闭原则。

(3)支持撤销与重做

方便地支持撤销(undo)和重做(redo),尤其适用于事务性操作的场景。

(4)组合复杂操作

可以组合多个命令来完成复杂的操作。

缺点

(1)命令类数量增加

每个具体操作都需要定义一个命令类,可能会增加类的数量,导致系统复杂性上升。

(2)实现成本较高

如果系统不需要撤销、重做或记录操作日志等功能,使用命令模式可能显得过于复杂和冗余。


命令模式的主要角色

(1)命令接口(Command)

这是一个声明执行操作的接口或抽象类,定义了所有命令的统一方法(通常是execute()方法)。每个具体的命令类都要实现这个接口,代表某个具体的操作。

作用:定义执行操作的接口,统一命令的结构。

(2)具体命令类(ConcreteCommand)

具体命令类实现了命令接口,每一个类对应一个具体的操作,封装了命令的接收者和相关操作。具体命令类通常包含对接收者的引用。

作用:实现命令接口,定义执行操作的具体实现。持有接收者对象,并通过接收者来执行相应的操作。

(3)调用者(Invoker)

调用者是负责发出命令的对象。它持有一个命令对象,并在需要时调用命令对象的execute()方法。调用者并不直接处理操作的具体实现,而是将操作委托给命令对象。

作用:发出命令,不关心命令的具体实现,只负责调用命令的接口。

(4)接收者(Receiver)

接收者是执行命令的对象,它包含了执行具体操作的逻辑。接收者是具体业务逻辑的执行者,而调用者通过命令对象将操作转发给接收者。

作用:接收并执行具体的操作,包含实际的业务逻辑。

(5)客户端(Client)

客户端负责创建具体命令对象,并设置命令的接收者。客户端通常会将命令对象传递给调用者,让调用者通过命令来执行操作。

作用:实例化命令对象、设定接收者,并将命令传递给调用者。


角色之间的关系

调用者 调用 命令对象 的 execute() 方法。
命令对象 内部调用 接收者 的操作,接收者执行具体的业务逻辑。
客户端 负责创建 命令对象,并配置 接收者 和 调用者。


示例关系图

客户端
  ↓
调用者 → 命令接口 ← 具体命令
                 ↓
              接收者

命令模式应用场景

(1)GUI按钮与菜单操作

在图形用户界面中,每个按钮、菜单项的操作都可以封装为命令对象,方便实现撤销和重做功能。

(2)事务性系统

需要支持可撤销操作的系统,如数据库事务、文本编辑器、命令行工具等。

(3)宏命令(Macro Command)

需要将一组操作组合为一个操作时,可以使用命令模式实现宏功能,将多个命令组合为一个。

(4)任务队列系统

命令模式适用于将命令加入队列中,延迟执行或异步执行的场景。


命令模式实现案例

这个模式从名字上看就很简单,命令嘛,老大发命令,小兵执行就是了,确实是这个意思,但是更深化了,用模式来描述真是是世界的命令情况。

在中国做项目,项目经理就是什么都要懂,什么都要管;目前讲的是为一家旅行社建立一套内部管理系统,管理他的客户、旅游资源、票务以及内部管理,整体上类似一个小型的 ERP 系统。

这个项目的成员分工也是采用了常规的分工方式,分为需求组(Requirement Group,简称 RG)、美工组(Page Group,简称 PG)、代码组(Code Group,简称 CG),总共加上我这个项目经理正好十个人,刚开始的时候客户(也就是旅行社,甲方)还是很乐意和我们每个组探讨,比如和需求组讨论需求,和美工讨论页面,和代码组讨论实现,告诉他们修改这里,删除这里,增加这些等等,这是一种比较常见的甲乙方合作模式,甲方深入到乙方的项目开发中。

模式用类图表示一下:

在这里插入图片描述

1、项目组抽象

package com.uhhe.common.design.command.group;

/**
 * 项目组分成了三个组,每个组还是要接受增删改的命令
 *
 * @author nizhihao
 * @version 1.0.0
 * @date 2023/2/28 14:45
 */
public abstract class Group {

    /**
     * 甲乙双方分开办公,你要和那个组讨论,你首先要找到这个组
     */
    public abstract void find();

    /**
     * 被要求增加功能
     */
    public abstract void add();

    /**
     * 被要求删除功能
     */
    public abstract void delete();

    /**
     * 被要求修改功能
     */
    public abstract void change();

    /**
     * 被要求给出所有的变更计划
     */
    public abstract void plan();

}

2、具体的项目组

需求组

package com.uhhe.common.design.command.group;

/**
 * 需求组的职责是和客户谈定需求,这个组的人应该都是业务领域专家
 *
 * @author nizhihao
 * @version 1.0.0
 * @date 2023/2/28 14:46
 */
public class RequirementGroup extends Group {

    @Override
    public void find() {
        System.out.println("找到需求组...");
    }

    @Override

    public void add() {
        System.out.println("客户要求增加一项需求...");
    }

    @Override

    public void change() {
        System.out.println("客户要求修改一项需求...");
    }

    @Override

    public void delete() {
        System.out.println("客户要求删除一项需求...");
    }

    @Override
    public void plan() {
        System.out.println("客户要求需求变更计划...");
    }

}

美工组

package com.uhhe.common.design.command.group;

/**
 * 美工组的职责是设计出一套漂亮、简单、便捷的界面
 *
 * @author nizhihao
 * @version 1.0.0
 * @date 2023/2/28 14:46
 */
public class PageGroup extends Group {

    @Override
    public void find() {
        System.out.println("找到美工组...");
    }

    @Override
    public void add() {
        System.out.println("客户要求增加一个页面...");
    }

    @Override
    public void change() {
        System.out.println("客户要求修改一个页面...");
    }

    @Override
    public void delete() {
        System.out.println("客户要求删除一个页面...");
    }

    @Override
    public void plan() {
        System.out.println("客户要求页面变更计划...");
    }

}

代码组

package com.uhhe.common.design.command.group;

/**
 * 代码组的职责是实现业务逻辑,当然包括数据库设计了
 *
 * @author nizhihao
 * @version 1.0.0
 * @date 2023/2/28 14:49
 */
public class CodeGroup extends Group {

    @Override
    public void find() {
        System.out.println("找到代码组...");
    }

    @Override
    public void add() {
        System.out.println("客户要求增加一项功能...");
    }

    @Override
    public void change() {
        System.out.println("客户要求修改一项功能...");
    }

    @Override
    public void delete() {
        System.out.println("客户要求删除一项功能...");
    }

    @Override
    public void plan() {
        System.out.println("客户要求代码变更计划...");
    }

}

3、命令Command 抽象类

package com.uhhe.common.design.command;

import com.uhhe.common.design.command.group.CodeGroup;
import com.uhhe.common.design.command.group.PageGroup;
import com.uhhe.common.design.command.group.RequirementGroup;

/**
 * 命令的抽象类,把客户发出的命令定义成一个一个的对象
 * 把三个组都定义好,子类可以直接使用
 *
 * @author nizhihao
 * @version 1.0.0
 * @date 2023/2/28 14:42
 */
public abstract class Command {

    /**
     * 需求组
     */
    protected RequirementGroup requirementGroup = new RequirementGroup();

    /**
     * 美工组
     */
    protected PageGroup pageGroup = new PageGroup();

    /**
     * 代码组
     */
    protected CodeGroup codeGroup = new CodeGroup();

    /**
     * 只要一个方法,你要我做什么事情
     */
    public abstract void execute();

}

4、具体命令抽象类

增加一项需求

package com.uhhe.common.design.command;

/**
 * 增加一项需求
 *
 * @author nizhihao
 * @version 1.0.0
 * @date 2023/2/28 15:41
 */
public class AddRequirementCommand extends Command {

    /**
     * 执行增加一项需求的命令
     */
    @Override
    public void execute() {
        // 找到需求组
        super.requirementGroup.find();
        // 增加一份需求
        super.requirementGroup.add();
        // 页面也要增加
        super.pageGroup.add();
        // 功能也要增加
        super.codeGroup.add();
        // 给出计划
        super.requirementGroup.plan();
    }

}

删除一个页面的命令

package com.uhhe.common.design.command;

/**
 * 删除一个页面的命令
 *
 * @author nizhihao
 * @version 1.0.0
 * @date 2023/2/28 15:42
 */
public class DeletePageCommand extends Command {

    /**
     * 执行删除一个页面的命令
     */
    @Override
    public void execute() {
        // 找到页面组
        super.pageGroup.find();
        // 删除一个页面
        super.pageGroup.delete();
        // 给出计划
        super.pageGroup.plan();
    }

}

5、Invoker 接头人(接收命令,并执行)

package com.uhhe.common.design.command;

/**
 * 接头人的职责就是接收命令,并执行
 *
 * @author nizhihao
 * @version 1.0.0
 * @date 2023/2/28 15:44
 */
public class Invoker {

    /**
     * 什么命令
     */
    private Command command;

    /**
     * 客户发出命令
     *
     * @param command 命令
     */
    public void setCommand(Command command) {
        this.command = command;
    }

    /**
     * 执行客户的命令
     */
    public void action() {
        this.command.execute();
    }

}

6、命令模式使用

package com.uhhe.common.design.command;

/**
 * 命令模式使用
 * 
 * @author nizhihao
 * @version 1.0.0
 * @date 2023/2/28 15:45
 */
public class Client {

    /**
     * 命令模式【Command Pattern】
     * <p>
     * 命令模式比较简单,但是在项目中使用是非常频繁的,封装性非常好,因为它把请求方(Invoker)和执行方(Receiver)分开了,
     * 扩展性也有很好的保障。但是,命令模式也是有缺点的,你看 Command 的子类没有,那个如果我要写下去的可不是几个,而是几十个,这个类膨胀的非常多。
     * <p>
     * ①Receiver角色:这个就是干活的角色,命令传递到这里是应该被执行的,具体到上面我们的例子中就是Group 的三个实现类
     * ②Command 角色:就是命令,需要我执行的所有命令都这里声明
     * ③Invoker 角色:调用者,接收到命令,并执行命令,例子中我这里项目经理就是这个角色
     */
    public static void main(String[] args) {
        // 定义接头人
        Invoker xiaoSan = new Invoker();
        // 客户要求增加一项需求
        System.out.println("-------------客户要求增加一项需求-----------------");
        // 客户给我们下命令来
        // Command command = new AddRequirementCommand();
        Command command = new DeletePageCommand();
        // 接头人接收到命令
        xiaoSan.setCommand(command);
        // 接头人执行命令
        xiaoSan.action();
    }

}

总结:命令模式通过将命令封装为对象,提供了灵活的操作处理方式,尤其在解耦请求与执行、支持撤销和重做、以及灵活扩展等方面表现出色。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

未禾

您的支持是我最宝贵的财富!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值