命令模式(5.2):傻乎乎的幸福

最后更新:2019.11

面向对象中的使用关系,通常被称为Client/IServer(C/S) 结构。Client类体中通常会见到这样的代码:

IServer s = (IServer) God.create("server");
s.foo();

然而,框架的设计者有时候发现,Client不知道或不在乎应该依赖谁!Client不知道要依赖的类IServer,更不知道可以调用的方法foo()。例如某将军要求打下一个山头,他可不在乎谁去打;再例如将要设计按钮/MyButton类,点击按钮后,用户可能想打开对话框、保存文件或关闭程序,此时程序员不可能让MyButton依赖Java的终极类Object,那么MyButton依赖谁呢?

1.傻乎乎的幸福

傻人傻福。既然MyButton(命令发出者,调用者/Invoker)傻乎乎地不知道命令接收者/Receiver是谁,更不知道Receiver有什么方法可以调用,于是,程序员会设计一个接口如ICommand,并封装一个一般性的方法doSth()或exe(),而让MyButton不管不顾地依赖ICommand,通常,如例程4-1所示,MyButton和ICommand属于框架/底层。

 

package chap4.commandP.framework;

/**
 * 框架中的类,设计者显然不知道点击按钮后
 *
 * @author yqj2065
 * @version (a version number or a date)
 */
public abstract class MyButton{  
    public void click(Command c){
        c. exe();
    }    
}


package chap4.commandP.framework;
public interface Command{
     public void exe();
}

由应用程序员实现的ICommand的子类型被称为子命令,指定MyButton需要依赖的某个服务类并调用服务类的某个方法。例如,服务类可能是一个对话框MyDialog,或者Java的System类(将关闭程序)等等;而子命令如SaveCommand,将指定MyDialog完成打开对话框并保存文件的任务。命令模式中的服务类在GoF中被称为接收者/Receiver。接收者可以是任意的类型。

package chap4.commandP;
public class MyDialog{
    public void foo(){//可以是任意的函数名
        System.out.println("XDialog:文件另存为...");
    }    
}

package chap4.commandP;
public class SaveCommand implements chap4.commandP.framework.Command{    
    public void exe(){
        new MyDialog().foo();
    }
    
}

现在考虑应用程序Client如何工作。Client首先创建一个MyButton对象,它可能被贴上OK、退出等标签;再创建一个子命令SaveCommand对象,然后将两个对象关联起来。子命令指定命令接收者为MyDialog并调用它的foo()方法。另外一个子命令是退出应用程序的命令exit对象,为lambda表达式,指定命令接收者为System并调用它的exit ()方法。

在Client中通常不需要了解接收者。

package chap4.commandP;
import static yqj2065.util.Print.*;
import chap4.commandP.framework.*;

public class Client{
    public static void main(String[] args) {
        pln();
        Command save = new SaveCommand();
        MyButton btn = new MyButton(){};
        pln(save.getClass().getSimpleName()+" click");
        btn.click(save);
        Command exit = () -> {
                System.exit(0);
            };
        pln(exit.getClass().getSimpleName()+" click");        
        btn.click(exit);        
    }

    public static void test(){
        MyButton btn = new MyButton(){};
        Command eat = new Command(){ //EatCommand
                @Override public void exe()	{
                    new Chowhound().eat();
                }
                class Chowhound{//吃货
                    public void eat(){pln("好吃佬吃东西");}
                }
            };
        btn.click(eat); 
        btn.click(()->{pln("吃自己");}); //没有接受者无所谓
    }

}

命令模式的角色:

  • Command:例子中为ICommand,定义一个通用的接口。
  • Invoker:例子中为MyButton,它只会调用Command的接口。
  • ConcreteCommand:例子中为X1/SaveCommand、XSystem/匿名类,改写Command接口时,调用某个消息接收者的某个方法。
  • Receiver:任何类都可能成为一个接收者。
  • Client:创建各种具体的命令对象,还可能使用Receiver或Invoker。

很多人认为命令模式的优点,是完成消息发出者与执行者(Invoker/ Receiver)之间的解耦,并认为采用命令模式使得软件有更松散的耦合。从结果上看有一点道理,但事实上,使用命令模式不是希望Invoker和Receiver离婚,Invoker只有10岁,他完全就不知道它老婆将会是谁。耦都没有,咋解。

命令模式的意图,形象地说,是单身狗的幸福。通常而言,阅读应用了命令模式的源代码时,阅读者不会将Invoker与某个被隔离的Receiver联系起来。

Command模式 = 单身狗的幸福

 

-------------------

3.万能适配目标?通用策略?

 

站在X的角度,它有方法doSth(),而消息接收者千奇百怪,有System、有客户自定义的XDialog,我们是否可以将X1视为XDialog的某个(被调用)方法适配器,XSystem视为System.exit(0的适配器呢?

不管消息接收者有什么方法,通通适配成doSth()。

单纯从类图/结构上看(不管意图),MyButton-X-X1-XDialog 与对象适配器的类图完全一样。

 

《设计模式》中,有一句话:“命令模式正是回调机制的一个面向对象的替代品”。这句话很不合适。什么是回调机制(Call back)中说明,更一般地,回调机制可以理解为:在设计框架时使用高阶函数。在面向对象中,回调机制 = 框架中使用多态,也可以说使用策略模式。所以,我们说“策略模式是C语言回调机制的一个面向对象的替代品”也好过《设计模式》的说法。换言之,单纯看X与其子类型的关系,我们可以说这里使用了策略模式

4.Invoker与Client角色

在简单的介绍Command模式的程序中,可能出现Client使用Invoker(如上面例子中的App使用了MyButton),甚至可能直接将调用者与客户类合二为一。

如果底层框架能够调用btn.click(x)从而底层框架能够调用x.doSth(),程序员编写的程序通常就是客户类Client,而Client不依赖Invoker。

为了演示Client不依赖Invoker,我们借助GUI框架,其中java.awt.event.ActionListener就是抽象命令角色

 

package java.awt.event;
import java.util.EventListener;
public interface ActionListener extends EventListener {//EventListener 是一个标记接口
    public void actionPerformed(ActionEvent e);

}
EventListener 是一个标记接口
    public void actionPerformed(ActionEvent e);

}

在GUI中有1个Button,2个MenuItem——"Open..."和"Exit",先设计一个ActionListener的独立子类型

 

 

package cmd;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class Exit implements ActionListener {
    @Override    public void actionPerformed(ActionEvent e) {
      System.exit(0);
    }
  }

而在图形界面中,定义了ActionListener的匿名类和lamdba表达式。

 

 

package cmd;
import java.awt.Button;
import java.awt.Color;
import java.awt.FileDialog;
import java.awt.Frame;
import java.awt.Menu;
import java.awt.MenuBar;
import java.awt.MenuItem;
import java.awt.Panel;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class ActionCommandDemo extends Frame {
    public ActionCommandDemo() {
        super("Command");
        MenuBar mbar = new MenuBar();
        setMenuBar(mbar);
        Menu mnuFile = new Menu("File", true);
        mbar.add(mnuFile);
        MenuItem mnuOpen = new MenuItem("Open...");
        mnuFile.add(mnuOpen);
        MenuItem mnuExit = new MenuItem("Exit");
        mnuFile.add(mnuExit);

        mnuOpen.addActionListener(new ActionListener() {
            @Override public void actionPerformed(ActionEvent e) {
                FileDialog fDlg = new FileDialog(ActionCommandDemo.this, "Open a file", FileDialog.LOAD);
                fDlg.setVisible(true);
            }
        });
        mnuExit.addActionListener(new Exit());

        Button btnRed = new Button("Red");
        Panel p = new Panel();
        add(p);
        p.add(btnRed);

        btnRed.addActionListener( e -> p.setBackground(Color.red));
        setBounds(100, 100, 200, 100);
        setVisible(true);
    }

    static public void main(String argv[]) {
        new ActionCommandDemo();
    }
}

ActionCommandDemo是Client角色,Exit、匿名类和lamdba表达式是ConcreteCommand,Receiver则是System、FileDialog和Panel。Invoker是谁?先不管它。

 



 

2. 命令与执行

先直接给出命令模式的例子吧。

既然有了Command,按照多态也好,难度系数为0的策略模式也罢,tv的open()演变成Command的子类OpenCommand

OpenCommand有私有成员TV tv,而OpenCommand的exe()干什么?显然只需要一条语句tv.open()。代码自己随手写吧。

因为我们拥有依赖注入工具tool.God,(注意:在我的博客的很多的文章中,都使用了该工具,但是类名用过FromPropertyFile、IoC、God,所在包也有所变化,懒得逐一修改相关博文了。代码的意思很清楚,读者自己对应修改一下),因而代码

 

package method.command; 
import tool.God;
public class Controller{
	public static void test()	{
	    Command c1 = (Command)God.create("open");
	    c1.exe();
	}
}

Controller仅仅知道Command对象,Controller下的命令为字符串"open",God根据字符串"open"创建method.command.OpenCommand对象。

 

忽略一切细节,Controller仅依赖Command,对照的,Controller1依赖TV,和TV的现有操作/方法名

①命令模式的核心,是封装普适方法exe ()的Command。通过它及其子类,将如图3-3所示的通常的服务请求中的请求发送者和接收者完全解耦,或者说将通常的C/S结构的C与S解耦。

C仅仅依赖于Command。而OpenCommand依赖于Command和S。

所以,我们常常说Command采用了命令模式。或许应该说 以Command同志为核心的命令模式?

②依赖于Command的各种类(不包括其子类),在《设计模式》中称为调用者(Invoker),它们是命令的发出者。借助反射机制或依赖注入模式或依赖注入工具类tool.God,调用者可以发出Command的各种子类封装的命令,而且不需要知道最终调用的是什么方法名、不需要知道最终谁执行。
如果调用者突发奇想地发出(需要)新的命令,可以编写Command的新子类以及执行者。

 

package method.command;
public class EatCommand implements Command{
    @Override public void exe()	{
        new Chowhound().eat();
    }
    private class Chowhound{//吃货
        public void eat(){System.out.println("好吃");}
    }
}

 

在配置文件中添加eat =method.command.EatCommand

则修改Controller的"open",即c1 =(Command)God.create("eat");就ok。

③具体命令类是封装的命令的Command的各种子类,如OpenCommand。在override/改写exe ()时,将命令的执行者与某一操作绑定如tv.open()。虽然简单起见,OpenCommand中通过成员变量如电视/TV设定了执行者,事实上,可以通过依赖注入模式,按照配置文件方便地指定消息接收者的类型例如OpenHandler。

3.吐槽 《设计模式·5.2》

《设计模式》中,给命令模式(Command Pattern)的定义/意图比较繁琐。正如刀可以砍人,你把刀玩出花样来——来个回马刀都可以,刀的基本作用还是砍人。

命令模式(Command Pattern):将一个请求封装为一个对象,从而让我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。命令模式是一种对象行为型模式,其别名为动作(Action)模式或事务(Transaction)模式。

既然命令模式使得C仅仅依赖于Command,它不知道S为何物,也不知道S的接口所以

C下达的一系列命令,你可以组合成一个队列、可以组合成一个批命令;也可以反之,将C下达的一个命令分解成若干具体的命令;

对于命令执行前后的变化加以监控,你可以实现undo或redo;如果命令只是改变一个页面的颜色,你很容易undo/取消操作;如果命令导致手榴弹炸了一个房屋,omg,你undo就很麻烦。

你可以玩出其他花样。比如C下达的一个命令open,对于接收者为TV,就打开电视;如果配置的接收者为一个连长,他就打开/攻占一座城门。

你可以玩出更多的花样……

 

续 

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
4S店客户管理小程序-毕业设计,基于微信小程序+SSM+MySql开发,源码+数据库+论文答辩+毕业论文+视频演示 社会的发展和科学技术的进步,互联网技术越来越受欢迎。手机也逐渐受到广大人民群众的喜爱,也逐渐进入了每个用户的使用。手机具有便利性,速度快,效率高,成本低等优点。 因此,构建符合自己要求的操作系统是非常有意义的。 本文从管理员、用户的功能要求出发,4S店客户管理系统中的功能模块主要是实现管理员服务端;首页、个人中心、用户管理、门店管理、车展管理、汽车品牌管理、新闻头条管理、预约试驾管理、我的收藏管理、系统管理,用户客户端:首页、车展、新闻头条、我的。门店客户端:首页、车展、新闻头条、我的经过认真细致的研究,精心准备和规划,最后测试成功,系统可以正常使用。分析功能调整与4S店客户管理系统实现的实际需求相结合,讨论了微信开发者技术与后台结合java语言和MySQL数据库开发4S店客户管理系统的使用。 关键字:4S店客户管理系统小程序 微信开发者 Java技术 MySQL数据库 软件的功能: 1、开发实现4S店客户管理系统的整个系统程序; 2、管理员服务端;首页、个人中心、用户管理、门店管理、车展管理、汽车品牌管理、新闻头条管理、预约试驾管理、我的收藏管理、系统管理等。 3、用户客户端:首页、车展、新闻头条、我的 4、门店客户端:首页、车展、新闻头条、我的等相应操作; 5、基础数据管理:实现系统基本信息的添加、修改及删除等操作,并且根据需求进行交流信息的查看及回复相应操作。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值