文章目录
🙊 前言:本文章为瑞_系列专栏之《23种设计模式》的命令模式篇。本文中的部分图和概念等资料,来源于博主学习设计模式的相关网站《菜鸟教程 | 设计模式》和《黑马程序员Java设计模式详解》,特此注明。本文中涉及到的软件设计模式的概念、背景、优点、分类、以及UML图的基本知识和设计模式的6大法则等知识,建议阅读 《瑞_23种设计模式_概述》
本系列 - 设计模式 - 链接:《瑞_23种设计模式_概述》
⬇️本系列 - 创建型模式 - 链接🔗单例模式:《瑞_23种设计模式_单例模式》
工厂模式:《瑞_23种设计模式_工厂模式》
原型模式:《瑞_23种设计模式_原型模式》
抽象工厂模式:《瑞_23种设计模式_抽象工厂模式》
建造者模式:《瑞_23种设计模式_建造者模式》⬇️本系列 - 结构型模式 - 链接🔗
代理模式:《瑞_23种设计模式_代理模式》
适配器模式:《瑞_23种设计模式_适配器模式》
装饰者模式:《瑞_23种设计模式_装饰者模式》
桥接模式:《瑞_23种设计模式_桥接模式》
外观模式:《瑞_23种设计模式_外观模式》
组合模式:《瑞_23种设计模式_组合模式》
享元模式:《瑞_23种设计模式_享元模式》⬇️本系列 - 行为型模式 - 链接🔗
模板方法模式:《瑞_23种设计模式_模板方法模式》
策略模式:《瑞_23种设计模式_策略模式》
命令模式:《瑞_23种设计模式_命令模式》
职责链模式:《瑞_23种设计模式_职责链模式》
状态模式:《瑞_23种设计模式_状态模式》
观察者模式:《瑞_23种设计模式_观察者模式》
中介者模式:《瑞_23种设计模式_中介者模式》
迭代器模式:《瑞_23种设计模式_迭代器模式》
访问者模式:《瑞_23种设计模式_访问者模式》
备忘录模式:《瑞_23种设计模式_备忘录模式》
解释器模式:《瑞_23种设计模式_解释器模式》
1 命令模式(Command Pattern)
瑞:常见的命令模式使用到的接口方法
execute()
。典型命令模式:Runable
,Runnable担当命令的角色,Thread充当的是调用者,start方法就是其执行方法。
命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。
瑞:行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。
瑞:行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。
命令模式属于:对象行为模式
1.1 介绍
-
意图:将一个请求封装成一个对象,从而使您可以用不同的请求对客户进行参数化。
-
主要解决:在软件系统中,行为请求者与行为实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行记录、撤销或重做、事务等处理时,这种无法抵御变化的紧耦合的设计就不太合适。
-
何时使用:在某些场合,比如要对行为进行"记录、撤销/重做、事务"等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,如何将"行为请求者"与"行为实现者"解耦?将一组行为抽象为对象,可以实现二者之间的松耦合。
-
如何解决:通过调用者调用接受者执行命令,顺序:调用者→命令→接受者。
-
关键代码:定义三个角色:
1️⃣ received 真正的命令执行对象
2️⃣ Command
3️⃣ invoker 使用命令对象的入口 -
应用实例:struts 1 中的 action 核心控制器 ActionServlet 只有一个,相当于 Invoker,而模型层的类会随着不同的应用有不同的模型类,相当于具体的 Command。
-
优点:
1️⃣ 降低了系统耦合度。
2️⃣ 新的命令可以很容易添加到系统中去。 -
缺点:使用命令模式可能会导致某些系统有过多的具体命令类。
-
使用场景:认为是命令的地方都可以使用命令模式,比如:
1️⃣ GUI 中每一个按钮都是一条命令。
2️⃣ 模拟 CMD。 -
注意事项:系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作,也可以考虑使用命令模式,见命令模式的扩展。
-
命令模式结构示意图:
1.2 概述
定义:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行存储、传递、调用、增加与管理。
命令模式将请求封装成对象,从而允许参数化客户端与请求排队或记录请求日志,以及支持可撤销的操作。此外,命令模式的使用场景通常包括需要对操作进行记录、撤销/重做、事务管理等情况。例如,在GUI中的按钮和菜单项,或者在编程中的回调函数等场景下,命令模式都非常适用。
1.3 命令模式的结构
- 命令模式包含以下主要角色:
1️⃣ 抽象命令类(Command)角色: 定义命令的接口,声明执行的方法。
2️⃣ 具体命令(Concrete Command)角色:具体的命令,实现命令接口;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
3️⃣ 实现者/接收者(Receiver)角色: 接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
4️⃣ 调用者/请求者(Invoker)角色: 要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。
1.4 命令模式的优缺点
优点:
- 降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。
- 增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”,对扩展比较灵活。
- 可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
- 方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。
缺点:
- 使用命令模式可能会导致某些系统有过多的具体命令类。
- 系统结构更加复杂。
1.5 命令模式的使用场景
- 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
- 系统需要在不同的时间指定请求、将请求排队和执行请求。
- 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
2 案例一
【案例】点餐
2.1 需求
顾客把订单交给服务员,服务员拿到了订单,放在订单柜台,然后递给资深大厨,资深大厨根据订单准备餐点。
将上面的案例用代码实现,那我们就需要分析命令模式的角色在该案例中由谁来充当:
-
服务员: 就是调用者角色,由她来发起命令。
-
资深大厨: 就是接收者角色,真正命令执行的对象。
-
订单: 命令中包含订单。
2.2 代码实现
/**
* 抽象命令类
*
* @author LiaoYuXing-Ray
**/
public interface Command {
// 只需要定义一个统一的执行方法
void execute();
}
import java.util.Map;
import java.util.Set;
/**
* 具体的命令类
*
* @author LiaoYuXing-Ray
**/
public class OrderCommand implements Command {
// 持有接收者对象
private final SeniorChef receiver;
private final Order order;
public OrderCommand(SeniorChef receiver, Order order) {
this.receiver = receiver;
this.order = order;
}
@Override
public void execute() {
System.out.println(order.getDiningTable() + "桌的订单:");
Map<String, Integer> foodDir = order.getFoodDir();
// 遍历map集合
Set<String> keys = foodDir.keySet();
for (String foodName : keys) {
receiver.makeFood(foodName, foodDir.get(foodName));
}
try {
Thread.sleep(1000);//停顿一下 模拟做饭的过程
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(order.getDiningTable() + "桌的饭准备完毕!!!");
}
}
import java.util.HashMap;
import java.util.Map;
/**
* 订单类
*
* @author LiaoYuXing-Ray
**/
public class Order {
// 餐桌号码
private int diningTable;
// 所下的餐品及份数
private final Map<String,Integer> foodDir = new HashMap<String, Integer>();
public int getDiningTable() {
return diningTable;
}
public void setDiningTable(int diningTable) {
this.diningTable = diningTable;
}
public Map<String, Integer> getFoodDir() {
return foodDir;
}
public void setFood(String name,int num) {
foodDir.put(name,num);
}
}
/**
* 厨师类(接收者角色)
*
* @author LiaoYuXing-Ray
**/
public class SeniorChef {
public void makeFood(String name, int num) {
System.out.println(num + "份" + name);
}
}
import java.util.ArrayList;
import java.util.List;
/**
* 服务员类(属于请求者角色)
*
* @author LiaoYuXing-Ray
**/
public class waitress {
// 持有多个命令对象
private final List<Command> commands = new ArrayList<Command>();
public void setCommand(Command cmd) {
// 将cmd对象存储到list集合中
commands.add(cmd);
}
// 发起命令功能 喊 订单来了
public void orderUp() {
System.out.println("美女服务员:大厨,新订单来了。。。。");
// 遍历list集合
for (Command command : commands) {
if(command != null) {
command.execute();
}
}
}
}
/**
* 测试类
*
* @author LiaoYuXing-Ray
**/
public class Client {
public static void main(String[] args) {
// 创建第一个订单对象
Order order1 = new Order();
order1.setDiningTable(1);
order1.setFood("西红柿鸡蛋面",1);
order1.setFood("小杯可乐",2);
// 创建第二个订单对象
Order order2 = new Order();
order2.setDiningTable(2);
order2.setFood("尖椒肉丝盖饭",1);
order2.setFood("小杯雪碧",1);
// 创建厨师对象
SeniorChef receiver = new SeniorChef();
// 创建命令对象
OrderCommand cmd1 = new OrderCommand(receiver,order1);
OrderCommand cmd2 = new OrderCommand(receiver,order2);
// 创建调用者(服务员对象)
waitress invoke = new waitress();
invoke.setCommand(cmd1);
invoke.setCommand(cmd2);
// 让服务员发起命令
invoke.orderUp();
}
}
代码运行结果如下:
美女服务员:大厨,新订单来了。。。。
1桌的订单:
1份西红柿鸡蛋面
2份小杯可乐
1桌的饭准备完毕!!!
2桌的订单:
1份尖椒肉丝盖饭
1份小杯雪碧
2桌的饭准备完毕!!!
3 案例二
本案例为菜鸟教程中的案例
3.1 需求
我们首先创建作为命令的接口 Order,然后创建作为请求的 Stock 类。实体命令类 BuyStock 和 SellStock,实现了 Order 接口,将执行实际的命令处理。创建作为调用对象的类 Broker,它接受订单并能下订单。
Broker 对象使用命令模式,基于命令的类型确定哪个对象执行哪个命令。CommandPatternDemo 类使用 Broker 类来演示命令模式。
3.2 代码实现
步骤 1
创建一个命令接口。
public interface Order {
void execute();
}
步骤 2
创建一个请求类。
public class Stock {
private String name = "ABC";
private int quantity = 10;
public void buy(){
System.out.println("Stock [ Name: "+name+",
Quantity: " + quantity +" ] bought");
}
public void sell(){
System.out.println("Stock [ Name: "+name+",
Quantity: " + quantity +" ] sold");
}
}
步骤 3
创建实现了 Order 接口的实体类。
public class BuyStock implements Order {
private Stock abcStock;
public BuyStock(Stock abcStock){
this.abcStock = abcStock;
}
public void execute() {
abcStock.buy();
}
}
public class SellStock implements Order {
private Stock abcStock;
public SellStock(Stock abcStock){
this.abcStock = abcStock;
}
public void execute() {
abcStock.sell();
}
}
步骤 4
创建命令调用类。
import java.util.ArrayList;
import java.util.List;
public class Broker {
private List<Order> orderList = new ArrayList<Order>();
public void takeOrder(Order order){
orderList.add(order);
}
public void placeOrders(){
for (Order order : orderList) {
order.execute();
}
orderList.clear();
}
}
步骤 5
使用 Broker 类来接受并执行命令。
public class CommandPatternDemo {
public static void main(String[] args) {
Stock abcStock = new Stock();
BuyStock buyStockOrder = new BuyStock(abcStock);
SellStock sellStockOrder = new SellStock(abcStock);
Broker broker = new Broker();
broker.takeOrder(buyStockOrder);
broker.takeOrder(sellStockOrder);
broker.placeOrders();
}
}
步骤 6
执行程序,输出结果:
Stock [ Name: ABC, Quantity: 10 ] bought
Stock [ Name: ABC, Quantity: 10 ] sold
4 JDK源码解析(Runable)
Runable是一个典型命令模式,Runnable担当命令的角色,Thread充当的是调用者,start方法就是其执行方法
// 命令接口(抽象命令角色)
public interface Runnable {
public abstract void run();
}
// 调用者
public class Thread implements Runnable {
private Runnable target;
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
private native void start0();
}
会调用一个 native 方法 start0() ,调用系统方法,开启一个线程。而接收者是对程序员开放的,可以自己定义接收者。
以下代码为演示Runnable命令模式的接受者使用(程序员自定义)
/**
* 接受者类
*
* @author LiaoYuXing-Ray
**/
public class Receiver {
public void turnOFF() {
System.out.println("程序员自定义接受者执行方法...");
}
}
/**
* jdk Runnable 命令模式
* TurnOffThread : 属于具体命令角色
*/
public class TurnOffThread implements Runnable{
private final Receiver receiver;
public TurnOffThread(Receiver receiver) {
this.receiver = receiver;
}
public void run() {
// 接受者,程序员自定义
receiver.turnOFF();
}
}
/**
* 测试类
*
* @author LiaoYuXing-Ray
**/
public class Demo {
public static void main(String[] args) {
Receiver receiver = new Receiver();
TurnOffThread turnOffThread = new TurnOffThread(receiver);
Thread thread = new Thread(turnOffThread);
thread.start();
}
}
执行程序,输出结果:
程序员自定义接受者执行方法...
如果觉得这篇文章对您有所帮助的话,请动动小手点波关注💗,你的点赞👍收藏⭐️转发🔗评论📝都是对博主最好的支持~