深入浅出软件设计模式之命令模式

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上,下面附上代码链接

命令模式代码   github.com/Tianyapiao/Command.git

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,265评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,078评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,852评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,408评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,445评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,772评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,921评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,688评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,130评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,467评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,617评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,276评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,882评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,740评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,967评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,315评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,486评论 2 348

推荐阅读更多精彩内容