命令模式是一种行为型模式。请求以命令的形式包裹在对象中,并传递给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。
简单而言,就是调用者->命令->执行者,将调用者和执行者解耦。
介绍
命令模式主要定义了三种角色:
- Receiver:执行命令的对象;
- Command:命令;
- Invoker:命令的发起者;
UML关系图:
解决的问题
在软件系统中,行为请求者与行为实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行记录、撤销或重做、事务等处理时,这种无法抵御变化的紧耦合的设计就不太合适。
使用场合
需要对行为进行"记录、撤销/重做、事务"等处理,这种无法抵御变化的紧耦合是不合适的。在这种情况下,使用命令模式将一组行为/命令抽象为对象,可以实现二者之间的松耦合。典型的如Hystrix断路器。
示例
长官对士兵下达命令,长官喊“向左转!”,士兵向左转,长官喊“向右转!”,士兵就向右转。长官也可以把要下达的命令列在命令清单上,士兵接到清单,按照清单上的命令执行。
普通实现
/**
* @author xujian
* 2020-12-01 14:48
*
* 军官
*
* 军官和士兵紧耦合
**/
public class General {
/**
* 士兵
*/
private Soldier soldier;
public General(Soldier soldier) {
this.soldier = soldier;
}
/**
* 下达命令
* @param order
*/
public void makeOrder(String order) {
if ("turnLeft".equals(order)) {
soldier.turnLeft();
} else if ("turnRight".equals(order)) {
soldier.turnRight();
}
}
public static void main(String[] args) {
General general = new General(new Soldier());
//下达向左转的命令
general.makeOrder("turnLeft");
//下达向右转的命令
general.makeOrder("turnRight");
}
}
该实现的缺点显而易见:军官和士兵紧耦合。如果想要增加一种命令,那么必须要修改General
类,为其加上一种if
分支。
命令模式实现
UML类图:
命令接口:
/**
* @author xujian
* 2020-11-30 17:05
*
* 命令接口
**/
public interface Order {
/**
* 执行方法
*/
void execute();
}
具体的命令:
/**
* @author xujian
* 2020-11-30 17:12
*
* 向左转命令
**/
public class TurnLeftOrder implements Order {
/**
* 士兵
*/
private Soldier soldier;
public TurnLeftOrder(Soldier soldier) {
this.soldier = soldier;
}
/**
* 执行方法
*/
@Override
public void execute() {
soldier.turnLeft();
}
}
/**
* @author xujian
* 2020-11-30 17:08
*
* 向右转命令
**/
public class TurnRightOrder implements Order{
/**
* 士兵
*/
private Soldier soldier;
public TurnRightOrder(Soldier soldier) {
this.soldier = soldier;
}
/**
* 执行方法
*/
@Override
public void execute() {
soldier.turnRight();
}
}
命令执行者-士兵:
/**
* @author xujian
* 2020-11-30 17:09
*
* 士兵
**/
public class Soldier {
public void turnRight() {
System.out.println("🪖士兵向右转!");
}
public void turnLeft() {
System.out.println("🪖士兵向左转!");
}
}
也可以将“士兵”抽象成接口,实现多个不同类型的“士兵”实现。
命令发起者-军官:
/**
* @author xujian
* 2020-11-30 17:18
*
* 军官
**/
public class General {
/**
* 命令清单
*/
private List<Order> orderList = new ArrayList<>();
/**
* 添加命令到命令清单
* @param order
*/
public void addOrder(Order order) {
orderList.add(order);
}
/**
* 下达命令清单上的所有命令
*/
public void issueOrderList() {
for (Order order : orderList) {
issueOrder(order);
}
}
/**
* 下达命令
* @param order
*/
public void issueOrder(Order order) {
order.execute();
}
}
测试:
/**
* @author xujian
* 2020-11-30 17:26
**/
public class TestCommandMode {
public static void main(String[] args) {
General general = new General();
Soldier soldier = new Soldier();
Order orderLeft = new TurnLeftOrder(soldier);
Order orderRight = new TurnRightOrder(soldier);
general.addOrder(orderLeft);
general.addOrder(orderRight);
//执行命令清单上的命令
System.out.println("----执行命令清单上的命令----");
general.issueOrderList();
//执行指定命令
System.out.println("----执行指定命令----");
general.issueOrder(orderLeft);
}
}
执行结果:
----执行命令清单上的命令----
🪖士兵向左转!
🪖士兵向右转!
----执行指定命令----
🪖士兵向左转!
命令模式和策略模式的区别
从上面示例看,命令模式的实现方式取代了普通实现方式的if-else
,之前说的策略模式也是取代了普通实现方式的if-else
,并且他们的类图也很相似。那么他们有什么异同呢?
相同点
- 他们都是行为型模式。
- 他们都有解耦的目的。策略模式重点关注“客户”和“行为”的解耦;命令模式重点关注“请求者”和“实现者”的解耦。
不同点
- 策略模式的目的是封装“算法”,它认为“算法”已经是一个完整的、不可拆分的原子业务,为了让这些算法独立,并且可以相互替换;命令模式的目的是封装命令,使请求者与实现者解耦。
- 策略模式适用于算法要求变换的场景,而命令模式适用于解耦两个有紧耦合关系的对象场合或者多命令多撤销的场景。
总结
通过命令模式,降低了系统的耦合度,可以很容易扩展新的命令。但是和很多设计模式一样,可能会导致系统有过多的具体实现类。