1. 命令模式概念
命令模式的定义是将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。
在面向对象编程当中,一个很重要的事情是设计使得代码松耦合。命令模式在生活中的体现就是遥控器和电视,遥控器是命令的触发者会给电视这个命令接受者发送一条换台命令,电视才会去执行这个换台命令,遥控器和电视就是通过命令关联起来,当遥控器坏了,我们不必去直接更换电视这个接受者,我们只需要更换命令的触发者遥控器即可。
命令模式优点:
-
通过引入中间件降低系统的耦合度。
-
扩展性良好,采用命令模式增加、删除命令不会影响其他类,满足开闭原则。
命令模式缺点:
- 可能产生大量具体的命令类,因为每一个具体操作都需要设计一个具体命令类,这会增加系统的复杂性。
- 命令模式的结果其实就是接收方的执行结果,但是为了以命令的形式进行架构、解耦请求与实现,引入了额外类型结构增加了理解上的困难。
命令模式类图如下:
- 抽象命令类(Command):这是表示命令封装的抽象,它定义了命令的执行与取消行为,应该被所有的具体命令类实现。
- 具体命令类(ConcreteCommand):具体的命令实现,它关联一个接收者,最终交由接收者进行处理。
- 命令接收者(Receiver):负责执行命令相关的操作的角色。
- 命令触发者(Invoker):触发命令。
2. 命令模式案例
客户去餐厅点单就餐,会通过服务员来下单,服务员根据菜单给后厨下发做菜的命令,比如下发甜点命令给甜点师,下发面点命令给面点师,具体实现如下:
2.1 抽象命令
新建抽象的命令接口定义命令执行和取消执行的操作,接口名称为Command,内容如下:
public interface Command {
/**
* 执行命令
*/
void excute();
/**
* 撤销命令
*/
void unExcute();
}
2.2 命令接收者
新建命令接收者甜点师类,甜点师负责如何制作甜点,类名称为DessertTeacherReceiver,内容如下:
public class DessertTeacherReceiver {
public void action(){
System.out.println("甜品师:开始制作甜品......");
}
public void cancel(){
System.out.println("甜品师:取消制作甜品......");
}
}
类似的新建命令接收者面点师类,面点师负责如何制作面点,类名称为PastryTeacherRecever,内容如下:
public class PastryTeacherRecever {
public void action() {
System.out.println("面点师:开始制作面条......");
}
public void cancel() {
System.out.println("面点师:取消制作面条......");
}
}
2.3 具体命令类
新建甜点命令类,它聚合了甜点师实现了抽象命令接口,类名称为DessertCommond,内容如下:
public class DessertCommond implements Command {
/**
* 甜品师
*/
private DessertTeacherReceiver receiver;
public DessertCommond(DessertTeacherReceiver receiver){
this.receiver = receiver;
}
@Override
public void excute() {
receiver.action();
}
@Override
public void unExcute() {
receiver.cancel();
}
}
类似的新建面点命令类,它聚合了面点师实现了抽象命令接口,类名称为PastryCommand,内容如下:
public class PastryCommand implements Command {
/**
* 面点师
*/
private PastryTeacherRecever receiver;
public PastryCommand (PastryTeacherRecever receiver){
this.receiver = receiver;
}
@Override
public void excute() {
receiver.action();
}
@Override
public void unExcute() {
receiver.cancel();
}
}
2.4 命令触发者
新建服务员命令触发者类,对客户端提供下单和撤单操作,内部维护一个菜单,类名称为ServiceInvoker,内容如下:
public class ServiceInvoker {
/**
* 定义一个菜单
*/
private static Map<String, Command> menu;
/**
* 初始化菜单
*/
static {
menu = new HashMap<>(2);
menu.put("甜品", new DessertCommond(new DessertTeacherReceiver()));
menu.put("面点", new PastryCommand(new PastryTeacherRecever()));
}
/**
* 下单
*/
public void placeOrder(String foodName){
// 发布命令
menu.get(foodName).excute();
}
/**
* 撤单
*/
public void cancellations(String foodName){
// 发布命令
menu.get(foodName).unExcute();
}
}
2.5 客户端
新建客户端进行点餐,内容如下:
public class Client {
public static void main(String[] args) {
// 创建一个服务员
ServiceInvoker serviceInvoker = new ServiceInvoker();
// 点一个甜品
serviceInvoker.placeOrder("甜品");
// 点一个面点
serviceInvoker.placeOrder("面点");
// 撤销甜品
serviceInvoker.cancellations("甜品");
}
}
3. 命令模式应用
在Spring的JDBC模块中有一个JdbcTemplate类就是用了类似的命令模式,其中JdbcTemplate的query()方法如下:
@Override
@Nullable
public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException {
Assert.notNull(sql, "SQL must not be null");
Assert.notNull(rse, "ResultSetExtractor must not be null");
if (logger.isDebugEnabled()) {
logger.debug("Executing SQL query [" + sql + "]");
}
/**
* Callback to execute the query.
*/
class QueryStatementCallback implements StatementCallback<T>, SqlProvider {
@Override
@Nullable
public T doInStatement(Statement stmt) throws SQLException {
ResultSet rs = null;
try {
rs = stmt.executeQuery(sql);
return rse.extractData(rs);
}
finally {
JdbcUtils.closeResultSet(rs);
}
}
@Override
public String getSql() {
return sql;
}
}
return execute(new QueryStatementCallback());
}
QueryStatementCallback就是一个实现了StatementCallback接口的一个具体命令,这个命令最终会交给Statement对象去具体执行,JdbcTemplate充当的就是命令触发者这样的角色。