目录
前言
java设计模式分类:
1、介绍
1.1、命令模式定义
命令模式(Command Pattern)是一种行为型设计模式,又叫动作模式或事务模式。
它将请求(命令)封装成对象,使得可以用不同的请求对客户端进行参数化,具体的请求可以在运行时更改、排队或记录,它将发出者和接收者解耦(顺序:发出者-->命令-->接收者)。
总结:
把请求的动作和请求的接收者进行解耦,让你能将动作封装成对象,再传递、存储、撤销或排队等。
1.2、对比
状态模式:
Context(电梯)-- 持有当前状态 --> State(状态接口)-- 实现 --> OpenState, RunState等
命令模式:
1.3、典型应用场景
1、Runnable接口:
Java中的Runnable接口就是一个典型的命令模式的应用。Runnable接口封装了需要执行的任务,然后可以交给线程去执行。
2、Timer和TimerTask类:
这两个类用于定时任务调度,TimerTask类封装了要执行的任务,然后由Timer类作为调用者执行这些任务。
3、Statement接口:
在Java中与数据库交互时,SQL语句被封装成Statement对象,然后由数据库驱动程序执行相应的命令。
生活示例:
1、 餐厅点餐:
在一家餐厅中,服务员充当调用者,厨师充当接收者,菜品可以作为具体命令。当顾客想点菜时,服务员会将顾客的需求封装成一个命令对象,并传递给厨师。厨师根据命令对象中的信息来完成相应的烹饪工作。这样,顾客和厨师之间不需要直接沟通,而是通过命令对象来实现点餐和烹饪的解耦。
2、遥控器控制家电:
拿电视遥控器举例,遥控器是调用者,电视是接收者。每个按键都可以看作是一个具体命令,例如音量加、音量减、切换频道等。
当用户按下某个按键时,遥控器会发送相应的命令给电视,然后电视根据命令执行相应的操作,如增加音量、减小音量或切换频道。
2、命令模式的结构
2.1、组成部分:
- Command(命令接口/抽象类): 声明
execute()
方法。 - ConcreteCommand(具体命令): 实现
Command
,并持有Receiver
引用,把请求操作的接收者封装进来,调用接收者的方法。 - Receiver(接收者): 真正执行业务逻辑的对象。
- Invoker(调用者): 有个成员变量保存Command,并执行
command.execute()
。 - Client(客户端): 组装命令、接收者、并给调用者下令。
2.2、整体流程
所有的“做某件事”,都变成订单(命令对象),Waiter只“发订单”,Chef只“实现订单”:
如下图所示:
命令模式,把“执行动作”的代码封装成对象。Waiter(服务员)不需要知道每个命令详情,只要保存一份“命令单子”,然后调用它的执行方法即可。
3、实现
假设你在饭店点菜。
- 你(Client 客户端):我要点“宫保鸡丁”。
- 服务员(Invoker 调用者):拿到菜单,把点菜单(订单)交给厨房。
- 订单(Command 命令对象):订单上写着“做宫保鸡丁”。
- 厨师(Receiver 接收者):收到订单,立刻开始炒菜。
这样,服务员并不需要会做菜,他只要拿订单就行了。
3.1、没有命令模式
你直接告诉服务员 “去让张师傅做宫保鸡丁”:
代码如下所示:
// 直接耦合
public class Waiter {
private Chef chef = new Chef();
public void orderGongBaoChicken() {
chef.makeGongBaoChicken();
}
}
public class Chef {
public void makeGongBaoChicken() {
System.out.println("炒宫保鸡丁!");
}
}
// 调用
Waiter waiter = new Waiter();
waiter.orderGongBaoChicken();
3.2、命令模式写法
1. 命令接口
public interface Command {
void execute(); // 执行命令
void undo(); // 撤销命令(可选)
}
- 这里的
Command
相当于“点菜单的模板”,所有的点菜单都要有一个execute方法。
2. 接收者(Receiver)——厨师
public class Chef {
public void makeGongBaoChicken() {
System.out.println("厨师:炒宫保鸡丁!");
}
public void makeFriedRice() {
System.out.println("厨师:炒蛋炒饭!");
}
}
- Chef能够真的炒不同的菜。
3. 具体命令(ConcreteCommand)——不同的订单
public class GongBaoChickenCommand implements Command {
private Chef chef;
public GongBaoChickenCommand(Chef chef) {
this.chef = chef;
}
@Override
public void execute() {
chef.makeGongBaoChicken();
}
@Override
public void undo() {
System.out.println("取消宫保鸡丁!");
}
}
public class FriedRiceCommand implements Command {
private Chef chef;
public FriedRiceCommand(Chef chef) {
this.chef = chef;
}
@Override
public void execute() {
chef.makeFriedRice();
}
@Override
public void undo() {
System.out.println("取消蛋炒饭!");
}
}
- 每个命令就是一张“点菜单”,知道自己要交给哪个厨师(Receiver)。
4、调用者(Invoker)服务员
public class Waiter {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void placeOrder() {
// 点菜
command.execute();
}
public void cancelOrder() {
// 撤销点菜
command.undo();
}
}
5、客户端组装
public class Customer {
public static void main(String[] args) {
Chef chef = new Chef();
Command gongBaoChicken = new GongBaoChickenCommand(chef);
Command friedRice = new FriedRiceCommand(chef);
Waiter waiter = new Waiter();
// 点宫保鸡丁
waiter.setCommand(gongBaoChicken);
waiter.placeOrder();
// 点蛋炒饭
waiter.setCommand(friedRice);
waiter.placeOrder();
// 撤销刚才的蛋炒饭
waiter.cancelOrder();
}
}
输出:
厨师:炒宫保鸡丁!
厨师:炒蛋炒饭!
取消蛋炒饭!
有上述示例可知命令模式的功能:
- 解耦:Waiter只需要认识命令Command,不用知道Chef做什么。
- 追加功能:要新增一道菜,只要做一个新的Command类即可,Waiter不用改。
- 支持队列、日志、撤销:命令对象能被存储、排队,可以撤销。
4、命令模式的优缺点
1、优点:
彻底解耦“请求者”与“执行者”,调用者无需知道接收者是啥。
请求可以参数化(每个对象带不同接收者和参数)。
请求可以放队列、做日志,如事务撤销与恢复、任务队列。
增加新命令类非常方便。
2、缺点:
类的数量可能会增多(每种命令都要实现 Command 接口)。
总结
命令模式适用于需要将请求封装成对象,实现请求发出者和接收者之间的解耦,并支持撤销、队列化和延迟执行的场景。它虽然可以提高系统的可扩展性和灵活性,但是需要权衡额外的开销和复杂性。
参考文章: