目录
一、基础概念
二、UML类图
三、角色设计
四、案例分析
1、基本实现
2、点餐案例
五、总结
一、基础概念
1、将一个请求封装为一个对象,使您可以用不同的请求对客户进行参数化。
2、对请求排队或记录请求日志,以及支持可撤销的操作。
3、将命令对象与执行命令的对象分离,实现调用者和接收者的解耦。
其中命令对象是关键,它包含了一个接收者和一个执行操作的方法。该命令对象绑定一个接收者对象,并通过调用接收者相应的操作来完成执行请求的功能。
二、UML类图
三、角色设计
角色 | 描述 |
---|---|
抽象命令类 | 定义命令的接口,声明执行的方法 |
具体命令角色 | 具体命令类,实现了命令接口,绑定接收者,并实现执行命令的操作 |
接收者类 | 知道如何实施与执行一个请求相关的操作 |
调用者类 | 接收命令请求并执行命令 |
客户端类 | 创建命令对象并设定它的接收者 |
四、案例分析
1、基本实现
主要包含以下几个部分:
1、Command抽象命令类:定义了命令的公共接口一般是一个execute()方法。
2、ConcreteCommand具体命令角色:实现了Command接口,是具体的命令类,包含对Receiver对象的引用。其execute()方法会调用Receiver的相应方法。
3、Receiver接收者类:命令对象Indirect调用的接收者对象。实现了具体的业务逻辑。
4、Invoker调用者类:请求的调用者。其持有Command对象的引用,并通过command.execute()间接调用Receiver。
5、Client客户端类:创建Command和Receiver对象,并创建Invoker传入Command对象,最后调用Invoker的invoke()方法触发执行。
这个实现让调用者Invoker和接收者Receiver解耦,Invoker只与Command接口发生依赖,不需要知道具体的命令与接收者。
抽象命令类:
public interface Command {
/**
* 执行方法
*/
void execute();
}
接收者类:
public class Receiver {
/**
* 真正执行命令相应的操作
*/
public void action(){
System.out.println("执行操作");
}
}
具体命令角色类:
public class ConcreteCommand implements Command {
//持有相应的接收者对象
private Receiver receiver = null;
/**
* 构造方法
*/
public ConcreteCommand(Receiver receiver){
this.receiver = receiver;
}
@Override
public void execute() {
//通常会转调接收者对象的相应方法,让接收者来真正执行功能
receiver.action();
}
}
调用者类:
public class Invoker {
/**
* 持有命令对象
*/
private Command command = null;
/**
* 构造方法
*/
public Invoker(Command command){
this.command = command;
}
/**
* 行动方法
*/
public void action(){
command.execute();
}
}
客户端:
public class Client {
public static void main(String[] args) {
//创建接收者
Receiver receiver = new Receiver();
//创建命令对象,设定它的接收者
Command command = new ConcreteCommand(receiver);
//创建请求者,把命令对象设置进去
Invoker invoker = new Invoker(command);
//执行方法
invoker.action();
}
}
运行结果如下:
2、点餐案例
这个点餐的命令模式案例,主要演示了几个角色对象之间的关系:
1、Waiter是接收者对象,知道如何执行点餐操作。
2、OrderCommand是命令对象,它封装了一个点餐请求,绑定了接收者Waiter。可以调用execute()执行点餐。
3、Customer是调用者对象,它通过命令对象indirect地执行点餐请求。可以设定并触发命令。
4、Client是客户端,进行对象的创建和使用。
抽象命令类:
public interface Command {
public void execute();
}
服务员(接收者类):
public class Waiter {
public void takeOrder(String food) {
System.out.println("服务员:收到点餐,食物是:" + food);
}
}
点餐命令类(具体的命令类):
public class OrderCommand implements Command {
private Waiter waiter;
private String food;
public OrderCommand(Waiter waiter, String food) {
this.waiter = waiter;
this.food = food;
}
@Override
public void execute() {
waiter.takeOrder(food);
}
}
顾客(调用者类):
public class Customer {
private Command command;
public void setOrder(Command command) {
this.command = command;
}
public void orderUp() {
command.execute();
}
}
客户端类:
public class Client {
public static void main(String[] args) {
Waiter waiter = new Waiter();
Command cmd = new OrderCommand(waiter, "番茄炒蛋");
Customer customer = new Customer();
customer.setOrder(cmd);
customer.orderUp();
}
}
运行结果如下:
整个执行流程是:
1、Client创建Waiter、OrderCommand、Customer对象。
2、Client把OrderCommand命令对象设置给Customer调用者。
3、Client请求Customer调用orderUp方法。
4、Customer的orderUp方法内部将调用OrderCommand的execute。
5、OrderCommand的execute会调用其内部绑定的Waiter的takeOrder方法。
6、这样客户的请求就通过命令对象传达给服务员,进行点餐。
这实现了调用者与接收者的解耦,并且使用命令对象可以方便实现撤销、重做、日志等功能。
五、总结
优点:
1、解耦了命令的发出者和执行者:发出命令的对象只需要知道命令接口,不需要知道具体的命令执行者。
2、可以较容易地设计一个命令队列和宏命令:通过命令队列可以对命令进行排队,而宏命令可以执行一系列的命令。
3、可以较容易实现命令的撤销和重做:通过保存执行过的命令对象,可以方便地实现回退。
缺点:
1、可能产生很多具体的命令类:因为每一个命令都需要一个具体的命令类,所以如果命令种类很多,会导致类的个数增加很多。
2、系统可能要慢一点,因为每次执行命令时,都需要先创建命令对象。
应用场景:
1、需要对操作进行记录、撤销/重做、事务等处理的场景。命令模式可以方便实现这些功能。
2、需要将请求调用者和请求接收者解耦的场景。命令模式可以使两者独立变化。
3、需要把操作封装成对象传递和存储的场景。命令模式可以把操作转换为对象。
4、需要对操作进行排队或记录日志、支持事务等功能的场景。可以用命令队列实现排队,用命令对象记录日志。
5、需要支持向组合系统中添加新命令的场景。命令模式使新增命令比较方便。
6、需要对系统进行状态恢复的场景。可以利用命令对象实现状态恢复。
7、需要实现宏命令、也就是组合命令的场景。命令模式可以方便实现宏命令。
符合的设计原则:
1、开闭原则(Open-Closed Principle)
命令模式中,命令的执行者与发出者是解耦的,发出者只知道命令接口,具体的实现执行者可以新增而不需要修改发出者。这样就满足了开闭原则。
2、单一职责原则(Single Responsibility Principle)
命令模式把请求的发出者和执行者进行了分离,发出者负责发出命令请求,执行者负责具体执行,职责划分明确,都满足单一职责原则。
3、组合复用原则(Composite Reuse Principle)
命令模式可以方便地将多个命令组合成一个组合命令,满足组合复用原则。
4、里氏替换原则(Liskov Substitution Principle)
命令模式中抽象命令类规定了接口,具体命令类都遵循这个接口,满足里氏替换原则。