👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD
🔥 2025本人正在沉淀中... 博客更新速度++
📫 欢迎+V: flzjcsg2,我们共同讨论Java深渊的奥秘
🎵 当你的天空突然下了大雨,那是我在为你炸乌云
文章目录
- 一、入门
- 二、命令模式在源码中的运用
- 2.1、JDK的Runnable接口
- [2.2、Spring 的 CommandLineRunner 接口](#2.2、Spring 的 CommandLineRunner 接口)
- 三、总结
- 参考
一、入门
什么是命令模式?
命令模式是一种行为设计模式,它将请求或操作封装为对象,从而使你可以用不同的请求对客户进行参数化,并支持请求的排队、记录、撤销等操作。
命令模式的核心 是将"请求"封装为独立的对象,包含执行操作所需的所有信息。这样,你可以将请求与执行者解耦,并通过参数化、队列或日志等方式管理请求。
为什么需要命令模式?
在没有使用命令模式的情况下,代码可能会遇到以下问题:
- 紧耦合 :
- 调用者(Invoker)直接依赖接收者(Receiver)的具体实现。如果接收者的接口或行为发生变化,调用者也需要修改。
- 例如,一个按钮直接调用某个对象的特定方法,导致按钮代码与具体逻辑紧密耦合。
- 难以扩展 :
- 如果需要添加新的操作,必须修改调用者的代码,违反了开闭原则(对扩展开放,对修改关闭)。
- 例如,一个遥控器需要支持新的设备时,必须修改遥控器的代码。
- 不支持撤销、重做或事务操作 :
- 如果系统需要支持撤销、重做或事务操作,直接调用方法的方式难以实现这些功能。
- 例如,一个文本编辑器需要支持撤销操作,直接调用方法的方式无法记录历史状态。
- 难以实现请求的队列或日志 :
- 如果需要对请求进行排队、延迟执行或记录日志,直接调用方法的方式无法实现这些功能。
怎样实现命令模式?
命令模式的组成:
● 命令(Command) :定义执行操作的接口,通常包含一个 execute() 方法。
● 具体命令(Concrete Command) :实现命令接口,负责调用接收者的操作。
● 接收者(Receiver) :实际执行操作的对象。
● 调用者(Invoker) :持有命令对象,并触发命令的执行。
● 客户端(Client):创建命令对象并设置其接收者。
【案例】 开关灯
Light
(接收者) :实际执行操作的对象。包含 on()
和off()
方法。'
java
class Light {
public void on() {
System.out.println("Light is ON");
}
public void off() {
System.out.println("Light is OFF");
}
}
Command
(命令接口) :定义执行操作的接口,包含 execute()
方法。
java
interface Command {
void execute();
}
LightOnCommand
和 LightOffCommand
(具体命令) :实现 Command
接口,封装了对 Light
的操作。持有 Light
对象的引用,并在 execute()
方法中调用 Light
的方法。
java
// 具体命令:开灯
class LightOnCommand implements Command {
private Light light;
public LightOnCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.on();
}
}
// 具体命令:关灯
class LightOffCommand implements Command {
private Light light;
public LightOffCommand(Light light) {
this.light = light;
}
@Override
public void execute() {
light.off();
}
}
RemoteControl
(调用者) :持有 Command
对象的引用。通过pressButton()
方法触发命令的执行。
java
class RemoteControl {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void pressButton() {
command.execute();
}
}
CommandPatternDemo
(客户端):创建接收者、命令对象和调用者,并将它们组装在一起。
java
// 客户端
public class CommandPatternDemo {
public static void main(String[] args) {
// 创建接收者
Light light = new Light();
// 创建命令对象
Command lightOn = new LightOnCommand(light);
Command lightOff = new LightOffCommand(light);
// 创建调用者
RemoteControl remote = new RemoteControl();
// 执行开灯命令
remote.setCommand(lightOn);
remote.pressButton();
// 执行关灯命令
remote.setCommand(lightOff);
remote.pressButton();
}
}
二、命令模式在源码中的运用
2.1、JDK的Runnable接口
Java 中的 Runnable
接口是命令模式的一个典型例子。
命令接口(类似于 Command 接口):Runnable
接口。
java
public interface Runnable {
void run();
}
具体命令(类似于 ConcreteCommand) :我们自己实现的task,MyTask
。
java
public class MyTask implements Runnable {
// 在这里可以加接收这
@Override
public void run() {
System.out.println("Task is running");
}
}
调用者(类似于 Invoker) : Thead
类,下面是简化版
java
public class Thread {
private Runnable target;
public Thread(Runnable target) {
this.target = target;
}
public void start() {
task.run();
}
}
客户端
java
public class Main {
public static void main(String[] args) {
Runnable task = new MyTask(); // 创建具体命令
Thread thread = new Thread(task); // 设置命令
thread.start(); // 执行命令
}
}
2.2、Spring 的 CommandLineRunner 接口
CommandLineRunner
是 Spring 框架中一个非常有用的接口,通常用于在 Spring Boot 应用启动后执行一些初始化任务或自定义逻辑。它本质上是命令模式的一个典型应用,将"启动时需要执行的任务"封装为一个命令对象,并由 Spring Boot 在合适的时机统一执行。
CommandLineRunner
的作用:CommandLineRunner
接口的主要作用是在 Spring Boot 应用启动完成后,执行一些额外的逻辑。例如:初始化数据、加载配置文件、启动后台任务、执行一些检查或测试逻辑。
命令接口(类似于 Command 接口) :CommandLineRunner
java
@FunctionalInterface
public interface CommandLineRunner {
void run(String... args) throws Exception;
}
具体命令(类似于 ConcreteCommand) :MyStartupTask
java
@Component // 将类注册为 Spring Bean
public class MyStartupTask implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("Executing startup task...");
// 打印命令行参数
System.out.println("Command line arguments:");
for (String arg : args) {
System.out.println(arg);
}
// 执行自定义逻辑
initializeDatabase();
loadConfiguration();
}
private void initializeDatabase() {
System.out.println("Initializing database...");
// 初始化数据库的逻辑
}
private void loadConfiguration() {
System.out.println("Loading configuration...");
// 加载配置文件的逻辑
}
}
调用者(类似于 Invoker) : SpringApplication
,下面的代码是简化版
java
public class SpringApplication {
public void run(String... args) {
// 初始化 Spring 上下文
ConfigurableApplicationContext context = createApplicationContext();
refreshContext(context);
// 调用 CommandLineRunner
callRunners(context, args);
}
private void callRunners(ApplicationContext context, String[] args) {
// 获取所有 CommandLineRunner 的 Bean
Map<String, CommandLineRunner> runners = context.getBeansOfType(CommandLineRunner.class);
// 按顺序执行
List<CommandLineRunner> sortedRunners = new ArrayList<>(runners.values());
AnnotationAwareOrderComparator.sort(sortedRunners);
// 调用每个 CommandLineRunner 的 run() 方法
for (CommandLineRunner runner : sortedRunners) {
runner.run(args);
}
}
}
客户端
java
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
三、总结
命令模式的优点
- 解耦调用者和接收者:
- 调用者(Invoker)不需要知道具体的接收者(Receiver)是谁,只需要调用命令对象的 execute() 方法。
- 降低了系统的耦合度,使得调用者和接收者可以独立变化。
- 支持扩展:
- 可以轻松添加新的命令类,而不需要修改调用者的代码。
- 符合开闭原则(对扩展开放,对修改关闭)。
- 支持撤销和重做:
- 命令对象可以记录状态,从而支持撤销(undo)和重做(redo)操作。
- 例如,文本编辑器可以通过命令对象记录每次操作的状态,从而实现撤销功能。
- 支持请求的队列或日志:
- 命令对象可以被排队、延迟执行或记录日志。
- 例如,可以将命令对象放入队列中,按顺序执行,或者将命令对象记录到日志中以便后续重放。
- 支持事务操作:
- 可以将多个命令组合成一个复合命令,实现事务操作。
- 例如,在数据库操作中,可以将多个更新操作封装为一个事务。
命令模式的缺点
- 类的数量增加:
- 每个命令都需要一个具体的类,可能导致类的数量增多。
- 对于简单的操作,使用命令模式可能会显得过于繁琐。
- 复杂性增加:
- 对于简单的请求,直接调用方法可能更直观,使用命令模式会增加额外的复杂性。
- 需要额外的代码来管理命令对象(如队列、日志等)。
命令模式的使用场景
- 需要解耦调用者和接收者:
- 当调用者不需要知道接收者的具体实现时,可以使用命令模式。
- 例如,GUI 中的按钮点击事件、远程调用的请求处理等。
- 需要支持撤销、重做或事务操作:
- 当系统需要支持撤销、重做或事务操作时,命令模式是一个很好的选择。
- 例如,文本编辑器、绘图软件、数据库事务等。
- 需要将请求排队或记录日志:
- 当需要对请求进行排队、延迟执行或记录日志时,可以使用命令模式。
- 例如,任务调度系统、消息队列、操作日志等。
- 需要支持扩展:
- 当系统需要支持新的操作,而不希望修改现有代码时,可以使用命令模式。
- 例如,遥控器支持新的设备、插件系统等。