Command Pattern Written by Tianyapiao
1.定义
将一个请求封装为一个对象(即我们创建的Command对象),从而使你可用不同的请求对客户进行参数化; 对请求排队或记录请求日志,以及支持可撤销的操作。命令模式是一种对象行为型模式,其别名为动作(Action)模式或事物(Transaction)模式。
相信大家看完命令模式定义的时候是一脸懵逼的,其实我也和你们一样。但是我们如果能联系下生活或者我们所学过的知识,那么理解起来就应该很容易了。下面就跟着我来探讨一下命令模式吧。
学过Java的童鞋们应该都配过jdk环境变量(jdk的环境配置在这里我们不做讨论),配置成功后,在cmd命令提示符中输入java -version会弹出jdk的具体版本,输入javac则会弹出javac相关的命令操作,具体是什么我们来看图:
在这里我们可以将javac命令理解成一个请求的发送者,用户通过它来发送一个“查看jdk环境是否配置成功的命令”,而jdk是请求的最终接收者和处理者,javac和java version "1.8.0_131"之间并不存在直接耦合的关系,它们通过JAVA_HOME,CLASSPATH,Path这三个环境变量的配置连接在一起,使得cmd最终输出了java version "1.8.0_131",如果我们没配置,或者配置不成功,那么它们两个完全没有任何关系,cmd也会返回“javac不是内部或外部命令”。
说了这么多 ,我们来看看命令模式需要解决哪些问题呢?请看下文:
2. 解决的问题
在软件系统中,行为请求者与行为实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行记录、撤销或重做、事务等处理时,这种无法抵御变化的紧耦合的设计就不太合适。
3.命令模式的类图
4.根据上面的类图我们很容易得到以下代码
//1.抽象命令类,用来声明执行操作的接口
public interface Command{
void execute();
}
//2.具体命令类,实现具体命令。
public class ConcereteCommand implements Command{
//具体命令类包含有一个接收者,将这个接收者对象绑定于一个动作
private Receiver receiver;
public ConcereteCommand(Receiver receiver){
this.receiver =receiver;
}
//说这个实现是“虚”的,因为它是通过调用接收者相应的操作来实现execute方法
public void execute(){
receiver.action();
}
}
//3.请求接收者类,知道如何实施与执行一个请求相关的操作,任何类都可能作为一个接收者。
public class Receiver{
//真正的命令实现
public void action(){
System.out.println("Execute request!");
}
}
///请求调用者类,要求该命令执行这个请求
public class Invoker{
private Command command;
// 设置命令 public void SetCommand(Command command){
this.command =command;
} //执行命令 public void executeCommand(){
command.execute();
}
}
//客户端代码
public class Client{
public static void main(string[] args)
{
Receiver receiver=new Receiver();
Command command=new ConcereteCommand(receiver);
Invoker invoker=new Invoker();
invoker.SetCommand(command);
invoker.executeCommand();
}
}
在IntelliJ IDEA 2017.1 x64软件中的测试结果如下:
5.应用举例
Sunny软件公司开发人员使用命令模式来设计“自定义功能键模块”,其核心结构如下图:
在图中,FBSettingWindow是“功能键设置界面类”,FunctionButton充当请求调用者,Command充当抽象命令类,MinimizeCommand和HelpCommand充当具体命令类,WindowHandler和HelpHandler充当请求接收者。完整代码如下:
//1.抽象命令类
public abstract classCommand {
public abstract voidexecute();
}
//2.具体命令类---帮助命令类
public class HelpCommand extends Command{
private HelpHandler hhObj;//维持对请求接收者的引用
public HelpCommand() {
hhObj=newHelpHandler();
}
//命令执行方法,将调用请求接收者的业务方法
@Override
public void execute() {
hhObj.display();
}
}
//3.具体命令类--最小化窗口命令类
public class MinimizeCommand extends Command{
private WindowHandler whObj;//维持对请求接收者的引用
public MinimizeCommand() {
whObj=newWindowHandler();
}
//执行命令方法,将调用请求接收者的业务方法
@Override
public void execute() {
whObj.minimize();
}
}
//4.窗口处理类:请求接收者
public class WindowHandler {
public voidminimize(){
System.out.println("将窗口最小化至托盘!");
}
}
//5.帮助文档处理类:请求接收者
public class HelpHandler {
public void display(){
System.out.println("显示帮助文档!");
}
}
//6..功能键设置窗口类
import java.util.ArrayList;
//功能键设置窗口类
public class FBSettingWindow{
private String title;
//定义一个ArrayList集合来存储所有的功能键
private ArrayList<FunctionButton> fbs=newArrayList<>();
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title= title;
}
public ArrayList<FunctionButton> getFb() {
returnfbs;
}
public void setFb(ArrayList<FunctionButton> fbs) {
this.fbs= fbs;
}
public FBSettingWindow(String title) {
this.title= title;
}
public void addFunctionButton(FunctionButton fb){
fbs.add(fb);
}
public void removeFunctionButton(FunctionButton fb){
fbs.remove(fb);
}
//7.显示窗口及功能键
public void display(){
System.out.println("显示窗口:"+title);
System.out.println("显示功能键:");
for(Object obj:fbs){
System.out.println(((FunctionButton) obj).getName());
}
System.out.println("----------------------------");
}
}
//8.功能键类:请求 的发送者
public class FunctionButton {
private String name;
private Command command;
public FunctionButton(String name) {
this.name= name;
}
public String getName() {
return name;
}
//为功能键注入命令
public void setCommand(Command command) {
this.command= command;
}
//发送请求的方法
public void onClick(){
System.out.print("点击功能键:");
command.execute();
}
}
//9.配置文件config.xml
<?xml version="1.0" encoding="UTF-8"?>
<config>
<type>HelpCommand</type>
<type>MinimizeCommand</type>
</config>
//10.xml配置文件解析类,此处我使用的是dom解析
注:使用下列代码需要导入dom4j-1.6.1.jar
下载地址 链接:http://pan.baidu.com/s/1geC6xMF 密码:e8mf
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.File;
import java.util.List;
public class XMLUtil{
public static Object getBean(inti){
try{
//创建saxReader对象
SAXReader reader=new SAXReader();
//通过read方法读取一个文件 转换成Document对象
Document doc=reader.read(newFile("src/config.xml"));
//获取根节点元素对象
Element rootNode=doc.getRootElement();//得到了config
String type =null;
//获取根元素节点下 所有元素的子节点
List<Element> elements = rootNode.elements();
if(0==i){
type= elements.get(0).getText();
}else{
type= elements.get(1).getText();
}
Class c=Class.forName(type);
Object obj=c.newInstance();
return obj;
}catch(Exception e) {
e.printStackTrace();
return null;
}
}
}
//客户端
public class Client {
public static void main(String args[]){
FBSettingWindow fbsw=new FBSettingWindow("功能键设置");
FunctionButton fb1,fb2;
fb1=new FunctionButton("功能键1");
fb2=newFunctionButton("功能键2");
Command cmd1,cmd2;
//通过读取配置文件和反射生成具体命令对象
cmd1=(Command)XMLUtil.getBean(0);
cmd2=(Command)XMLUtil.getBean(1);
//将命令注入到功能键
fb1.setCommand(cmd1);
fb2.setCommand(cmd2);
//Ctrl+D 复制一行代码(只适用于idea)
fbsw.addFunctionButton(fb1);
fbsw.addFunctionButton(fb2);
fbsw.display();
//调用功能键的业务方法
fb1.onClick();
fb2.onClick();
}
}
//idea中的运行结果和项目结构图如下:
6.适用场景
1. 命令的发送者和命令执行者有不同的生命周期。命令发送了并不是立即执行。
2. 命令需要进行各种管理逻辑。
3. 需要支持撤消\重做操作(这种状况的代码大家可以上网搜索下,有很多,这里不进行详细解读)。
7.结论:
通过对上面的分析我们可以知道如下几点:
1. 命令模式是通过命令发送者和命令执行者的解耦来完成对命令的具体控制的。
2. 命令模式是对功能方法的抽象,并不是对对象的抽象。
3. 命令模式是将功能提升到对象来操作,以便对多个功能进行一系列的处理以及封装。
好了,我对命令模式的理解到这里就结束了,如果大家发现有什么错误的地方,希望能不吝指正。如果有疑问的地方也可以提出,共同进步。
如果觉得文章对你有帮助,请点个赞喽,嘿嘿!
附:为了帮助大家更好的理解代码,我已经把代码上传到github上,下面附上代码链接