怎么申请做网站,成都网络推广运营公司,wordpress媒体库外链,网站开发参考资料#x1f448;️上一篇:模板方法模式 | 下一篇:职责链模式#x1f449;️ 设计模式-专栏#x1f448;️ 文章目录 命令模式定义英文原话直译如何理解呢#xff1f; 四个角色1. Command#xff08;命令接口#xff09;2. ConcreteCommand#xff08;具体命令类… ️上一篇:模板方法模式 | 下一篇:职责链模式️ 设计模式-专栏️ 文章目录 命令模式定义英文原话直译如何理解呢 四个角色1. Command命令接口2. ConcreteCommand具体命令类3. Client客户端4. Invoker调用者5. Receiver接收者类图类图分析代码示例命令接口Command具体命令ConcreteCommand客户端Client调用者Invoker接收者Receiver 应用命令模式的应用命令模式的优点命令模式的缺点命令模式的使用场景 示例解析1菜单按钮UI 控件- 打开文件按钮类图类图分析代码示例 示例解析2宏命令组合多个命令类图类图分析代码示例 示例解析3图形移动/撤销(重做功能)丐版类图类图分析代码示例 示例解析4图形移动/撤销(重做功能)类图类图分析代码示例 命令模式
命令模式Command Pattern又称为行动Action模式或交易Transaction模式。 命令模式就像是一个魔法卷轴。在这个奇幻的世界里魔法师请求者不需要亲自施展复杂的法术接收者他们可以将法术的咒语和步骤记录在魔法卷轴上命令对象。 当魔法师需要施展某个法术时他们只需挥动魔法卷轴卷轴上的咒语就会自动激活释放出强大的魔法力量。这样魔法师就可以轻松地使用各种法术而无需每次都记住复杂的咒语和步骤。 魔法卷轴的存在让魔法师的技能得以扩展和保存。他们可以将自己创造的独特法术记录在卷轴上与他人分享或传授。此外魔法卷轴还具有撤销和重做的能力如果魔法师在施展法术时出现了错误他们可以简单地收回卷轴并重新施展。 因此命令模式就像是一个魔法卷轴它让魔法的施展变得更加简单、高效和可控为魔法师们带来了无尽的便利和乐趣。 本文源码点击查看️
定义
英文原话
Encapsulate a request as an object, allowing you to parameterize clients with different requests, queue or log requests, and support undoable operations.
直译
将请求封装为一个对象使得你可以使用不同的请求对客户端进行参数化可以对请求进行排队或记录请求日志并支持可撤销的操作。 在这个定义中“Encapsulate a request as an object”指的是将请求或操作封装为对象的形式 这样做的好处是可以将请求与其执行者解耦提高系统的灵活性和可维护性。同时通过封装请求为对象还可以实现诸如请求队列、日志记录、撤销/重做等功能。 如何理解呢
理解命令模式Command Pattern的关键在于认识到它如何帮助我们将请求或操作封装为对象并将请求的发送者和接收者解耦。以下是对命令模式的详细理解 请求封装为对象 在命令模式中一个请求被封装为一个对象即命令对象。这个命令对象包含了执行特定操作的必要信息。通过封装请求为对象我们可以将请求视为与其他对象一样的实体并对其进行操作如传递、存储和组合。 发送者与接收者解耦 命令模式允许我们将请求的发送者客户端与请求的接收者执行者解耦。发送者不需要直接调用接收者的方法而是通过命令对象来间接地触发接收者的操作。这种解耦提高了系统的灵活性和可扩展性。 例如我们可以轻松地更换接收者执行不同的操作或添加新的命令对象执行新的操作而无需修改发送者的代码。 支持撤销、重做和日志记录 由于命令对象封装了请求的信息我们可以轻松地实现撤销、重做和日志记录等功能。通过存储之前执行的命令对象我们可以在需要时重新执行它们实现重做或反向执行它们实现撤销。此外我们还可以记录命令的执行日志以便后续的分析和调试。 排队和调度 由于命令对象是可存储和可传递的我们可以将它们放入队列中并按顺序执行如命令队列或者根据一定的调度策略来执行它们如时间调度器。这允许我们更灵活地控制操作的执行顺序和频率。 简化客户端代码 通过引入命令对象作为中间层我们可以简化客户端代码。客户端只需要知道如何创建和发送命令对象而无需关心命令的具体执行细节。这使得客户端代码更加简洁和易于维护。 命令模式通过封装请求为对象并将发送者与接收者解耦提供了一种灵活的方式来处理请求和操作。它支持撤销、重做、日志记录、排队和调度等功能并简化了客户端代码。这些特性使得命令模式在许多场景中都非常有用如GUI操作、数据库事务、游戏开发等。 四个角色
命令模式涉及以下几个主要角色
1. Command命令接口
声明了一个执行操作的接口。
2. ConcreteCommand具体命令类 将一个接收者对象绑定于一个动作 通过调用接收者相应的操作来实现Commond接口的抽象方法。 实现了Command接口持有接收者Receiver对象并调用接收者的功能来完成命令要执行的操作。
3. Client客户端
创建一个具体命令对象并设定它的接收者。
4. Invoker调用者
要求该命令执行这个请求。
5. Receiver接收者 知道如何实施与执行一个请求的相关的操作。 真正执行命令的对象。任何类都可能成为一个接收者只要它能够实现命令要求的功能。
类图 类图分析
从类图中可以分析出
调用者Invoker持有命令对象ConcreteCommand具体命令持有接收者Receiver对象。
因此调用者Invoker对象就可以间接执行接收者Receiver对象的动作通过调用具体命令的执行方法execute()而该方法实现方式是通过调用接收者Receiver对象的动作action()方法。
通过Command接口将调用者与接收者解耦发送者不需要直接调用接收者的方法而是通过命令对象来间接地触发接收者的操作。这种解耦提高了系统的灵活性和可扩展性。
代码示例
命令接口Command
package com.polaris.designpattern.list3.behavioral.pattern02.command.proto;/*** 命令接口Command命令Command角色*/
public interface Command {// 执行命令的方法void execute();
}
具体命令ConcreteCommand
package com.polaris.designpattern.list3.behavioral.pattern02.command.proto;/*** 具体命令ConcreteCommand具体命令Concrete Command角色*/
public class ConcreteCommand implements Command {private Receiver receiver;public ConcreteCommand(Receiver receiver) {this.receiver receiver;}// 执行方法Overridepublic void execute() {this.receiver.action();}
}
客户端Client
package com.polaris.designpattern.list3.behavioral.pattern02.command.proto;/*** 创建一个具体命令对象并设定它的接收者*/
public class Client {public static void main(String[] args) {// 接受者Receiver receiver new Receiver();// 将一个接收者对象绑定于一个动作Command command new ConcreteCommand(receiver);// 调用者要求该命令执行这个请求Invoker invoker new Invoker();invoker.setCommand(command);invoker.callCommand();}
}
调用者Invoker
package com.polaris.designpattern.list3.behavioral.pattern02.command.proto;/*** 调用者Invoker*/
public class Invoker {private Command command;// 构造函数初始化该命令public void setCommand(Command command) {this.command command;}// 执行命令public void callCommand() {this.command.execute();}
}
接收者Receiver
package com.polaris.designpattern.list3.behavioral.pattern02.command.proto;/*** 接收者Receiver*/
public class Receiver {// 行动方法public void action() {System.out.println(Command executed.);}
}输出打印
Command executed.在这个示例中 Receiver 是知道如何执行请求的对象ConcreteCommand 是实现了 Command 接口的具体命令类它持有一个 Receiver 对象并在 execute 方法中调用其 action 方法。 Invoker 是调用者它持有 Command 对象并在需要时调用其 execute 方法。 Client 是客户端负责创建和设置命令对象及其接收者。 应用
命令模式的应用
命令模式广泛应用于许多需要执行命令、撤销命令、记录命令历史或实现宏命令的系统中。以下是几个具体的应用场景
图形用户界面 (GUI) 中的按钮每个按钮可以关联一个命令对象当按钮被点击时执行该命令。文本编辑器撤销和重做功能可以通过命令模式实现每个编辑操作如插入、删除、剪切、粘贴等都可以是一个命令。请求队列系统可以接收多个命令请求并将它们存储在一个队列中然后按照先进先出FIFO的顺序执行这些命令。事务处理在金融系统中交易可以被封装为命令从而可以轻松地回滚撤销事务。宏录制用户可以录制一系列命令然后在需要时重新执行这些命令以实现自动化操作。
命令模式的优点
解耦命令模式将请求者与执行者解耦使得请求者不需要知道如何执行命令只需要知道如何发送命令。易于扩展由于请求者和执行者之间的解耦可以在不修改现有代码的情况下添加新的命令。支持撤销/重做通过保存命令的历史记录可以很容易地实现撤销和重做功能。易于实现事务可以将多个命令组合成一个事务从而确保所有命令都成功执行或全部回滚。易于记录日志和监控可以轻松地记录每个命令的执行情况和结果以便于后续的分析和监控。
命令模式的缺点
可能导致过多的具体命令类对于复杂的系统可能需要大量的具体命令类来实现各种功能这可能会增加系统的复杂性。可能增加系统的开销由于命令对象通常包含对接收者的引用并且在执行过程中可能需要额外的内存来存储命令历史记录因此可能会增加系统的内存开销。
命令模式的使用场景
当需要满足以下条件时可以考虑使用命令模式
请求者和执行者需要解耦当请求者不需要知道如何执行请求或者执行者不需要知道请求的来源时可以使用命令模式。支持撤销/重做如果系统需要支持撤销和重做功能那么命令模式是一个很好的选择。需要记录请求的历史如果系统需要记录每个请求的执行情况和结果以便于后续的分析和监控那么可以使用命令模式。需要实现事务如果需要将多个操作组合成一个事务以确保它们要么全部成功执行要么全部回滚那么命令模式是一个很好的选择。
示例解析1菜单按钮UI 控件- 打开文件按钮
在这个示例中我们有一个 UI 控件比如一个按钮当用户点击这个按钮时它应该执行某个操作打开文件。
我们可以使用命令模式来解耦按钮和它所执行的操作。
类图 类图分析
这个类图可以对比之前的角色分析时候的类图没错可以发现是一致的结构是一种场景的具化为的是通过该具体示例进一步理解命令模式的各个角色。
Button调用者角色持有一个命令对象至于什么具体的命令对象看用户给他传入什么命令对象这里是要给它的构造器传入OpenFileCommand用来初始化持有的命令对象即该按钮的意图是要打开文件。而如何打开打开哪个文件由命令决定调用者只需要负责按照自己的需要传入相应的命令即可OpenFileCommand具体命令构造器初始化了它持有的接收者FileManager对象以及要打开的文件名即该命令的目的就是为了打开该指定的文件。该命令的操作方法execute()通过调用接收者打开文件的方法openFile(String)来实现的从上面两步分析可知接收者FileManager并不知道谁会用到它不知道谁是调用者而调用者Button它只持有命令具体谁哪个接收者来处理这个命令它不直接关心或者说它并不关心他只直接知道我使用这个命令能完成这个事情而具体这个命令如何操作的由具体命令进行了封装调用相应的接收者处理调用者的需求。因此通过命令接口解耦了调用者与接收者。客户端就是按照上面分析过程中调用者需要持有一个具体命令对象具体命令封装了调用哪个接收者的方法来处理调用者的需求因此客户端Client实例化接收者作为参数构造出具体命令然后将具体命令对象作为参数构造出调用者对象然后就是执行调用者要求该命令执行这个请求。
代码示例
看完类图然后把类图分析完下面的代码就了然了。
package com.polaris.designpattern.list3.behavioral.pattern02.command.openfileuidemo;// 命令接口
interface Command { void execute();
} // 打开文件的具体命令
class OpenFileCommand implements Command { private FileManager fileManager; private String fileName; public OpenFileCommand(FileManager fileManager, String fileName) { this.fileManager fileManager; this.fileName fileName; } Override public void execute() { fileManager.openFile(fileName); }
} // 文件管理器接收者
class FileManager { public void openFile(String fileName) { System.out.println(打开文件: fileName); } // ... 其他文件管理操作
} // UI 控件调用者
class Button { private Command command; public Button(Command command) { this.command command; } public void onClick() { command.execute(); }
} // 客户端代码
public class Client { public static void main(String[] args) { FileManager fileManager new FileManager(); Command openCommand new OpenFileCommand(fileManager, example.txt); Button openButton new Button(openCommand); // 假设这是用户点击按钮的事件 openButton.onClick(); // 输出打开文件: example.txt }
}/* Output
打开文件: example.txt
*///~示例解析2宏命令组合多个命令
在这个示例中我们将展示如何使用命令模式来组合多个命令形成一个宏命令。
注意一点即可宏命令是组合了多个命令它是要一次执行一组操作的。
类图 类图分析
除了接收者宏命令MacroCommand持有多个命令对象外和示例1结构无差别。重点理解一组操作的概念从类图也可以反映出来。
该示例与示例1的区别我们模拟宏命令即组合多个命令可以看到调用者MacroCommand持有一个命令列表通过addCommand(Command)方法可以添加具体命令进去它的需求就是一次将这一组命令依次执行因此需要遍历执行每个命令。
代码示例
主要关注组合对象的实现即可当然也可以再次重复理解一下命令模式的各个角色及他们间的关系。
package com.polaris.designpattern.list3.behavioral.pattern02.command.macrocommand;import com.polaris.designpattern.list3.behavioral.pattern02.command.proto.Command;import java.util.ArrayList;
import java.util.List;// 命令接口
interface Command {// 执行命令的方法void execute();
}// 宏命令组合多个命令
// 宏命令也是命令的一种可以包含多个子命令
class MacroCommand implements Command {private ListCommand commands new ArrayList();public void addCommand(Command command) {commands.add(command);}Overridepublic void execute() {for (Command command : commands) {command.execute();}}
}// 接收者 TextEditor 类用于编辑文本
class TextEditor {private String text; // 假设这是编辑器中的文本内容public TextEditor() {this.text ;}// 获取编辑器中的文本public String getText() {return text;}// 设置编辑器中的文本通常这不会是一个直接设置的方法但为了简化示例public void setText(String text) {this.text text;}// 调整字体大小的方法这里只是模拟实际上可能需要更复杂的逻辑public void adjustFontSize(int size) {// 在真实应用中这里可能会更新编辑器的UI或状态来反映新的字体大小// 但在这个示例中我们只是简单地打印一条消息System.out.println(字体大小已调整为: size);}// 其他可能的文本编辑方法...
}// 具体的命令比如调整字体大小
class AdjustFontSizeCommand implements Command {private TextEditor textEditor;private int size;public AdjustFontSizeCommand(TextEditor textEditor, int size) {this.textEditor textEditor;this.size size;}Overridepublic void execute() {textEditor.adjustFontSize(size);}
}// 客户端代码
public class Client {public static void main(String[] args) {TextEditor textEditor new TextEditor(); // 假设 TextEditor 是接收者用于编辑文本 // 创建具体的命令 Command fontSizeUp new AdjustFontSizeCommand(textEditor, 12); // 增大字体 Command fontSizeDown new AdjustFontSizeCommand(textEditor, 10); // 减小字体 // 创建宏命令并添加具体的命令 MacroCommand macroCommand new MacroCommand();macroCommand.addCommand(fontSizeUp);macroCommand.addCommand(fontSizeDown); // 注意这里只是示例通常不会连续执行增大和减小字体 // 执行宏命令 macroCommand.execute(); // 首先执行 fontSizeUp然后执行 fontSizeDown }
}/* Output:
字体大小已调整为: 12
字体大小已调整为: 10
*///~示例解析3图形移动/撤销(重做功能)丐版
命令模式也可以用来实现撤销和重做功能。每个命令对象都可以存储其执行前的状态以便在需要时撤销操作。
这里是一个简化的示例图形的状态信息这里主要关注图形的位置在这里给简化省略了我们只是通过打印文字模拟了一下。因此省略掉了接收者角色直接在具体命令的方法中进行打印。
带有图形位置状态信息且由接收者处理请求的示例见示例解析4
在这个示例适应一下一个简单的变体缺少接收者。
类图 类图分析
撤销重做需要记录之前的状态信息在这个示例我们简化了状态信息的记录执行命令与撤销命令通过在具体命令中进行打印因此省略了接收者处理请求逻辑因此在类图就省略了接收者角色
UndoManager调用者持有一个命令执行记录栈当需要撤销从栈中弹出一个上一次操作的命令执行该命令的撤销方法。
这里的命令接口的定义分了两层一层是基础命令用来执行命令另外扩展的带撤销功能的命令接口定义了撤销方法具体命令实现接口也就实现了执行/撤销的逻辑。
代码示例
package com.polaris.designpattern.list3.behavioral.pattern02.command.undodemo.simple;import java.util.Stack;// 基础的命令接口
interface Command {void execute(); // 执行命令
}// 带有撤销功能的命令接口
interface UndoableCommand extends Command {void undo(); // 撤销操作
}// 具体命令具体的撤销命令比如移动图形
class MoveShapeCommand implements UndoableCommand {// 假设这里包含移动图形所需的状态信息 // 例如起始位置、目标位置等 Overridepublic void execute() {// 执行移动操作 System.out.println(执行移动操作);// ... 实际移动图形的代码 ... }Overridepublic void undo() {// 撤销移动操作将图形移回原来的位置 System.out.println(撤销移动操作);// ... 实际撤销移动的代码 ... }
}// 撤销管理器调用者
class UndoManager {private StackUndoableCommand undoStack new Stack();public void executeCommand(UndoableCommand command) {command.execute();undoStack.push(command); // 将命令添加到撤销栈中 }public void undo() {if (!undoStack.isEmpty()) {UndoableCommand command undoStack.pop();command.undo(); // 调用命令的撤销方法 System.out.println(撤销成功);} else {System.out.println(没有可撤销的命令);}}// 可能还需要一个检查是否有可撤销命令的方法 public boolean canUndo() {return !undoStack.isEmpty();}// 如果需要重做功能可以添加相应的栈和逻辑
}// 示例使用
public class Client {public static void main(String[] args) {UndoManager undoManager new UndoManager();UndoableCommand moveCommand new MoveShapeCommand();// 执行命令 undoManager.executeCommand(moveCommand);// 撤销命令 if (undoManager.canUndo()) {undoManager.undo();}}
}
/* Output:
执行移动操作
撤销移动操作
撤销成功
*///~示例解析4图形移动/撤销(重做功能)
相较于上一个示例示例3通过增加接收者Shape类用来处理图形移动逻辑与Point类用来记录图形的位置信息。
类图 类图分析
UndoManager调用者在执行命令时候会将执行的命令压栈到栈中当需要撤销操作时候出栈弹出上一次的命令执行撤销动作由于需要支持撤销因此调用者必须持有的具体命令必须带有可撤销能力因此需要持有 UndoableCommand 类型对象接收者 Shape 构造函数需要指定 Point对象用来记录它的位置它有一个获取图形当前所在位置的方法 getPositon() 和 移动到指定位置的方法 moveTo(Point)该方法需要传入一个目标位置具体命令构造器需要参数接收者 Shape 对象用来完成移动/撤销移动操作同时获取该图形的当前位置信息即原始位置记录下来 用于撤销时候使用具体命令构造器需要参数接收者 Point 对象 targetPosition 用来表示需要移动到的位置
代码示例
package com.polaris.designpattern.list3.behavioral.pattern02.command.undodemo.complex;import java.util.Stack;// 基础的命令接口如果不需要可以省略
interface Command {void execute(); // 执行命令
}// 带有撤销功能的命令接口
interface UndoableCommand extends Command {void undo(); // 撤销操作
}// 图形类用于记录移动图形所需的状态信息
class Shape {Point position; // 假设有一个Point类表示位置 Shape(Point position) {this.position position;System.out.println(图形原位置 position);}// ... 其他图形相关的方法 ... void moveTo(Point newPosition) {this.position newPosition;System.out.println(图形移动到: newPosition);}Point getPosition() {return position;}
}// 点类表示位置用于记录移动图形所需的状态信息
class Point {int x, y;Point(int x, int y) {this.x x;this.y y;}Overridepublic String toString() {return ( x , y );}
}// 具体命令具体的撤销命令比如移动图形
class MoveShapeCommand implements UndoableCommand {private Shape shape;private Point originalPosition; // 原始位置 private Point targetPosition; // 目标位置 public MoveShapeCommand(Shape shape, Point targetPosition) {this.shape shape;this.originalPosition new Point(shape.getPosition().x, shape.getPosition().y); // 保存原始位置 this.targetPosition targetPosition;}Overridepublic void execute() {// 执行移动操作 System.out.println(执行移动操作);shape.moveTo(targetPosition); // 实际移动图形的代码 }Overridepublic void undo() {// 撤销移动操作将图形移回原来的位置 System.out.println(撤销移动操作);shape.moveTo(originalPosition); // 实际撤销移动的代码 }
}// 撤销管理器调用者
class UndoManager {private StackUndoableCommand undoStack new Stack();public void executeCommand(UndoableCommand command) {command.execute();undoStack.push(command); // 将命令添加到撤销栈中}public void undo() {if (!undoStack.isEmpty()) {UndoableCommand command undoStack.pop();command.undo(); // 调用命令的撤销方法System.out.println(撤销成功);} else {System.out.println(没有可撤销的命令);}}// 可能还需要一个检查是否有可撤销命令的方法public boolean canUndo() {return !undoStack.isEmpty();}// 如果需要重做功能可以添加相应的栈和逻辑
}// 示例使用
public class Client {public static void main(String[] args) {UndoManager undoManager new UndoManager();Shape shape new Shape(new Point(0, 0)); // 创建一个在(0, 0)位置的图形 UndoableCommand moveCommand new MoveShapeCommand(shape, new Point(10, 10)); // 移动到(10, 10)位置 // 执行命令undoManager.executeCommand(moveCommand);// 撤销命令if (undoManager.canUndo()) {undoManager.undo();}}
}
/* Output:
图形原位置(0, 0)
执行移动操作
图形移动到: (10, 10)
撤销移动操作
图形移动到: (0, 0)
撤销成功
*///~在这个示例中我们添加了一个Shape类来表示图形以及一个Point类来表示位置。MoveShapeCommand现在保存了图形的原始位置和目标位置以便在execute和undo方法中使用。在Main类的示例中我们创建了一个在(0, 0)位置的图形并创建了一个命令来将其移动到(10, 10)位置。然后我们通过UndoManager执行该命令并撤销它。 ️上一篇:模板方法模式 | 下一篇:职责链模式️ 设计模式-专栏️