命令模式(Command Pattern)是一种行为设计模式,它将一个请求封装为一个对象,从而允许使用不同的请求、队列或者日志来参数化对象,并支持可撤销的操作。命令模式的核心思想是将命令的发起者和执行者解耦,从而使得命令的发起者不必关心命令是如何被执行的。
命令模式的关键组成部分:
解释:
execute()
方法。Command
接口,并在execute()
中调用接收者的某个动作。execute()
方法来执行请求。我们可以通过一个简单的遥控器来说明命令模式的实际应用。假设我们有一个遥控器,它可以控制不同的设备(如灯、电视、音响)。每个设备有开关功能,使用命令模式设计后,遥控器只需要发出命令,而不必关心设备是如何实现这些功能的。
public interface Command {
// 执行命令
void execute();
// 撤销命令
void undo();
}
用上面的例子设计一个开灯关灯的执行逻辑
public class LightReceiver {
public void on()
{
System.out.println("Light is on");
}
public void off()
{
System.out.println("Light is off");
}
}
实现Command
接口,并在execute()
中调用接收者的某个动作
public class LightOnCommand implements Command{
private LightReceiver lightReceiver;
public LightOnCommand(LightReceiver lightReceiver) {
this.lightReceiver = lightReceiver;
}
@Override
public void execute() {
lightReceiver.on();
}
@Override
public void undo() {
lightReceiver.off();
}
}
public class LightOffCommand implements Command{
private LightReceiver lightReceiver;
public LightOffCommand(LightReceiver lightReceiver) {
this.lightReceiver = lightReceiver;
}
public void execute() {
lightReceiver.off();
}
public void undo() {
lightReceiver.on();
}
}
/**
* 没有任何命令,即空执行: 用于初始化每个按钮, 当调用空命令时,对象什么都不做
* 其实,这样是一种设计模式, 可以省掉对空判断
* @author Administrator
*
*/
public class NoCommand implements Command {
@Override
public void execute() {
// TODO Auto-generated method stub
}
@Override
public void undo() {
// TODO Auto-generated method stub
}
}
public class RemoteController {
Command[] onCommands;
Command[] offCommands;
Command undoCommand;
public RemoteController()
{
onCommands = new Command[5];
offCommands = new Command[5];
for (int i = 0; i < 5; i++)
{
onCommands[i] = new NoCommand();
offCommands[i] = new NoCommand();
}
}
public void setCommand(int no, Command onCommand, Command offCommand)
{
onCommands[no] = onCommand;
offCommands[no] = offCommand;
}
public void onButtonWasPushed(int no)
{
onCommands[no].execute();
undoCommand = onCommands[no];
}
public void offButtonWasPushed(int no)
{
offCommands[no].execute();
undoCommand = offCommands[no];
}
public void undoButtonWasPushed()
{
undoCommand.undo();
}
}
public class Client {
public static void main(String[] args)
{
//创建电灯的对象(接受者)
LightReceiver lightReceiver = new LightReceiver();
//创建电灯相关的开关命令
LightOnCommand lightOnCommand = new LightOnCommand(lightReceiver);
LightOffCommand lightOffCommand = new LightOffCommand(lightReceiver);
//需要一个遥控器
RemoteController remoteController = new RemoteController();
//给我们的遥控器设置命令, 比如 no = 0 是电灯的开和关的操作
remoteController.setCommand(0, lightOnCommand, lightOffCommand);
System.out.println("--------按下灯的开按钮-----------");
remoteController.onButtonWasPushed(0);
System.out.println("--------按下灯的关按钮-----------");
remoteController.offButtonWasPushed(0);
System.out.println("--------按下撤销按钮-----------");
remoteController.undoButtonWasPushed();
}
}
输出结果
--------按下灯的开按钮-----------
Light is on
--------按下灯的关按钮-----------
Light is off
--------按下撤销按钮-----------
Light is on
Spring 框架中的 JdbcTemplate
是一个用于简化 JDBC 操作的工具类,它使用了命令模式来封装和管理数据库的操作。在 Spring 的设计中,JdbcTemplate
通过回调函数(或命令对象)来执行复杂的数据库操作,使得开发者可以将数据库连接、SQL 语句的执行以及资源的关闭等操作封装到具体的命令中。
下面我们从命令模式的角度,深入分析 JdbcTemplate
的源码
在 JdbcTemplate
中,常见的数据库操作,比如查询、插入、更新等,都通过回调函数的形式传递给 JdbcTemplate
,而具体的执行逻辑则由 JdbcTemplate
来处理。
JdbcTemplate
PreparedStatementCallback
、CallableStatementCallback
、RowCallbackHandler
等回调接口Connection
、PreparedStatement
、ResultSet
,这些是执行数据库操作的类JdbcTemplate
通过这些回调接口,将数据库操作的细节交由具体的回调函数去执行,而自己则负责管理数据库连接、事务和资源的关闭
我们来看 JdbcTemplate
源码中是如何通过命令模式来实现数据库操作的。
代码示例:
以 JdbcTemplate
的 execute
方法为例:
public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action) throws DataAccessException {
Assert.notNull(psc, "PreparedStatementCreator must not be null");
Assert.notNull(action, "Callback object must not be null");
Connection con = null;
PreparedStatement ps = null;
try {
// 获取数据库连接
con = DataSourceUtils.getConnection(getDataSource());
// 创建 PreparedStatement
ps = psc.createPreparedStatement(con);
// 执行回调函数,执行具体的 SQL 操作
return action.doInPreparedStatement(ps);
}
catch (SQLException ex) {
throw getExceptionTranslator().translate("PreparedStatementCallback", getSql(psc), ex);
}
finally {
closeStatement(ps);
DataSourceUtils.releaseConnection(con, getDataSource());
}
}
在这段代码中,命令模式的几个关键点体现如下:
PreparedStatementCallback
是一个回调接口,定义了 doInPreparedStatement(PreparedStatement ps)
方法。该方法就是命令接口的 execute
方法,负责具体的数据库操作。@FunctionalInterface
public interface PreparedStatementCallback<T> {
T doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException;
}
具体命令:PreparedStatementCallback
的实现类或匿名内部类就是具体的命令对象,它将实际的 SQL 操作封装在 doInPreparedStatement
方法中。
jdbcTemplate.execute(connection -> connection.prepareStatement("INSERT INTO user (name) VALUES (?)"),
preparedStatement -> {
preparedStatement.setString(1, "John");
return preparedStatement.executeUpdate();
});
接收者:PreparedStatement
和 Connection
是数据库操作的接收者,它们执行具体的数据库操作(例如,SQL 的执行)。
调用者:JdbcTemplate
是调用者,它负责管理数据库连接,调用命令对象(回调函数)并执行具体操作
execute
方法JdbcTemplate
的 execute
方法支持多种回调接口,例如:
每种操作都通过回调的方式封装在命令中,然后由 JdbcTemplate
来统一管理连接和执行。
query
方法JdbcTemplate
中的 query
方法用于查询数据库,并将结果集映射为对象。这也是命令模式的体现,开发者只需要提供一个 RowMapper(命令对象),负责将结果集转换为目标对象,其余的数据库操作细节由 JdbcTemplate
处理。
public List query(String sql, RowMapper rowMapper) throws DataAccessException {
return query(sql, new RowMapperResultSetExtractor(rowMapper));
}
在 query
方法中,RowMapper
就是命令模式中的命令对象,ResultSet
是接收者,而 JdbcTemplate
是调用者,负责管理 SQL 的执行和结果集的处理。
在 JdbcTemplate
中,命令模式通过回调机制得到了很好的应用。命令模式的核心思想是解耦,将请求的发送者与执行者解耦,使得 JdbcTemplate
作为调用者不关心具体的 SQL 执行逻辑,而只负责连接管理、资源管理和事务处理。
命令模式通过将请求封装为对象,使得命令发起者与执行者解耦,具有以下优点:
然而,命令模式也有一定的缺点:
命令模式适用于需要对请求进行排队、撤销或者记录操作的场景,也是实现日志、事务等功能的有效设计模式。在实际开发中,我们可以根据需求灵活地应用命令模式,尤其是在复杂的业务逻辑和行为可变的系统中。