命令模式在设计模式分组中属于行为模式一组,命令模式的核心在于把模块之间的多种调用进行规范和抽象,通过命称,我们不难想象,所谓命令模式应该类似于上级和下级发布命令的过程,让我们假想一下,比如有一个领导身在国外,他需要象的不同部门发出不同的工作要求(我们称为命令),再假设他没有方便的通讯工具,只能通过信件来传达他的要求,那么他将会把给不同部门的要求写成一封信,这样所有的命令都具有了信件的共同特征,而邮递员并不了解信件的内容是什么,他的任务只是负责传递命令,而每个部门的负责人都知道如何打开这个信件和执行信中所表达的任务要求。我们把这样一个类似问题进行抽象,就成了我们这里所提到的命令模式。
我们看一下命令模式所针对的常见问题,比如说:
一个图书管理系统,他可能需要执行各种不同的动作,在某些发生错误的情况下,你可能需要撤消所做的某些动作,同样也可能需要重新执行某个动作,那么我们如何来使用一个简单的办法来构建一个个动作以及组织他们的调用呢?这种问题就适合使用命令模式来解决问题了。
我们用下图来表示上面描述的情况:
让我们总结一下,到底什么样的问题需求需要命令模式呢?
当需要把客户端代码和如何执行一个动作需求的执行过程分离的时候,问题需求如下:
1、 需要有一种简单的方法能够动态的增加新动作或者修改存在的动作。
2、 保持客户端代码的简洁。
3、 客户端不需要知道处理一个特定需求具体细节。
4、 为执行具体需求定义的接口应该是简单的。
5、 可以方便的撤销当前动作或者重复以前的动作。
6、 可以方便的同步执行、远程调用或者在不同的线程中调用这些动作。
在遇到上面问题的时候,你需要想到的就是命令模式,这里我们来具体描述一下命令模式解决如上问题的方法:
1、 首先我们把所有的命令调用过程都抽象成一个接口叫Command,这个接口中定义一个调用方法叫execute。
2、 把每一个要执行的动作都设计成为一个类,让这个类实现Command的接口,实现接口中定义的名为execute的方法。
3、 当客户端需要执行具体的命令的时候,由客户端创建具体的命令对象(比如说:SpecificCommand),然后把这个对象传递给一个调用者对象(比如说:Invoker)。
4、 对于调用者对象(Invoker)而言,它不需要知道某个具体命令的细节,它的任务只是简单的调用命令对象的execute方法就可以了。
上面的问题描述对于程序开发者来说自然不难理解,但是还是通过图形的方式来表达将会更清晰准确,所以我们把前面提到的步骤使用下面的UML图形来表达这个具体的过程:
通过上面图形的描述,你应该对命令模式解决问题的过程有了比较全面的把握了,现在我们对命令模式在解决问题时候的一些规则作一个描述,让你在解决问题的时候知道有哪些需要注意的地方,可以更加顺利和严谨的设计代码:
1、 SpecificCommand可以包含与Command相关的状态数据
2、 SpecificCommand既可以实现某个具体的动作,也可以用来调用其他对象来完成工作
3、 Command接口在适当的时候可以包含一个undo方法,用于完成撤销命令的动作
4、 对于命令对象的调用可以不用局限,既可以在一个单独的线程中、进程中或者从客户端对服务端进行调用
5、 对于命令的调用可以以排队的方式来调用,以达到同步的目的
这些注意点和经验很值得你在设计命令模式的时候多加注意。
经过这样的一个理论分析之后,我们需要的是实践,前面我提到了图书管理系统,我们举例说明了这个系统会执行不同的动作,那么利用命令模式如何来实现这样的一个图书管理系统的动作命令呢?
假设有一个类叫做LibraryCommandInvoker类负责对命令进行调用,就像我们前面提及的邮递员,他的任务仅仅是负责对命令进行调用,而不管命令的具体实现,客户端的类的名字叫Client,他负责构造各种要执行的命令的对象,让后同时对LibraryCommandInvoker的调用来完成命令对象的执行。不同的命令类都实现同一个抽象的接口叫Command,假设存在三种具体的命令分别叫:CheckOutCommand、CheckInCommand、PutOnHoldCommand,我们再次通过一个UML的设计图形来完整地看一下这个命令模式的工作过程:
通过上面的图形,相信你对命令模式已经有了比较细致的了解了,我们现在可以很轻松的明白,命令模式的核心在于把调用者与被调用者之间传递的各种调用需求设计成为命令。通过抽象的命令接口来统一各种不同命令的特征,通过对LibraryCommandInvoker这种调用者对象的设计来统一命令的调用方法和过程,这样我们就可以简化命令的执行复杂度,在实践中,尤其对于给予网络的远程调用,这种命令模式将会大大简化网络通讯程序的复杂程序。
为了能够更加清晰的描述命令模式,我们以图书管理系统中增加图书的命令执行过程过程为例,通过实际的源代码来说明具体的实现。
这个功能包含如下内容:
Command:所有命令的共同规范,一个接口;
AddBookCommand:具体的命令类,负责完成增加图书的命令过程;
CommandInvoker:一个专门负责调用命令对象的工具类;
Client:客户端的类,负责产生命令对象并进行调用;
Command代码如下:
public interface Command {
public void execute();
}
AddBookCommand的代码如下:
public class AddBookCommand implements Command {
private Book book;
public AddBookCommand(Book book) {
this.book = book;
}
public void execute() {
//连接数据库
Connection con = ...;
String sql = "INSERT INTO books values(?, ?, ?)";
//得到PreparedStatment的对象
pSta = con.prepareStament(sql);
pSta.setString(1, book.getId());
pSta.setString(2, book.getName());
pSta.setDouble(3, book.getPrice());
pSta.executeUpdate();
......
}
}
CommandInvoker的代码如下:
public class CommandInvoker {
public void invok(Command cmd) {
cmd.execute();
}
}
Client的代码如下:
public class Client {
public static void main(String args[]) {
Book book = new Book("ID00231", "Java", 128);
Command cmd = new AddBookCommand(book);
CommandInvoker ci = new CommandInvoker();
ci.invok(cmd);
}
}
现在我们看完了一个简单的例题代码,通过上面几种形式的问题举例,我们来总结一下,命令模式在使用中的优缺点有那些呢?
使用命令模式主要有这样一些优点:
降低了调用逻辑的负责度;
为功能扩展提供了方便;
可以很容易的为各种命令提供undo功能;
也可以把多个命令放到一个队列中进行统一执行(我们前面没有给出具体代码,大家可以考虑这样的要求如何实现?);
可以把命令放到独立的线程中运行,也可以进行远程的调用;
同样也存在一些不利因素:
增加了对象创建、使用和消亡的系统开销;
应用程序本身的结构负责性有所上升;
命令模式的内容,我们讲完了,希望你能把它付诸与实践,提升程序的设计理念。