目录
命令模式【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();
}
}
总结:命令模式通过将命令封装为对象,提供了灵活的操作处理方式,尤其在解耦请求与执行、支持撤销和重做、以及灵活扩展等方面表现出色。