设计模式 | 命令模式(Command)
定义:
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
结构:(书中图,侵删)
一个抽象命令类
若干具体命令类
若干具体执行命令的接受者类
一个传话类(Invoker)
并没有客户类,都是直接通过传话类进行请求的
实例:
说到命令就想到了皇上,然后想到通常客户都不止一个,但皇上只有一个,但不重要,重点不在这。
皇上下命令,通常也是直接对身边的太监总管说,他不会亲自去找到某个人去下达命令,况且他可能还真不认识底下的那些个太监宫女。
so...这里用命令模式也再合适不过。
我做了一些改动,先贴上代码再细说。
宫女类(接受者):
package designpattern.command; /* * 宫女 */ public class Maid { String name; public Maid(String name) { this.name = name; } public void clean() { System.out.println(this.name + ":打扫卫生"); } }
太监类(接受者):
package designpattern.command; /* * 太监 */ public class Eunuch { String name; public Eunuch(String name) { this.name = name; } public void carrySedanChair() { System.out.println(this.name + ":抬轿子"); } }
抽象命令接口:
package designpattern.command; public interface Command { public void execute(); }
打扫命令类(具体命令类):
package designpattern.command; public class CleanCommand implements Command { private Maid maid; public CleanCommand(Maid maid) { this.maid = maid; } @Override public void execute() { maid.clean(); } }
抬轿子命令类(具体命令类):
package designpattern.command; public class CarrySedanChairCommand implements Command { private Eunuch eunuch; public CarrySedanChairCommand(Eunuch eunuch) { this.eunuch = eunuch; } @Override public void execute() { eunuch.carrySedanChair(); } }
太监总管类(Invoker):
package designpattern.command; import java.util.ArrayList; import java.util.List; public class ManagerEunuch { private List<Command> commands = new ArrayList<Command>(); public void setCommand(Command command) { this.commands.add(command); } public void notifyIt() { for (Command command : commands) { command.execute(); } } }
客户端:
package designpattern.command; public class Client { public static void main(String[] args) { Maid xiaocui = new Maid("小翠"); Maid xiaohua = new Maid("小花"); Eunuch xiaozhuozi = new Eunuch("小卓子"); Eunuch xiaoguizi = new Eunuch("小贵子"); Command cleanCommand1 = new CleanCommand(xiaocui); Command cleanCommand2 = new CleanCommand(xiaohua); Command carrySedanChairCommand1 = new CarrySedanChairCommand(xiaozhuozi); Command carrySedanChairCommand2 = new CarrySedanChairCommand(xiaoguizi); ManagerEunuch ligongong = new ManagerEunuch(); ligongong.setCommand(cleanCommand1); ligongong.setCommand(cleanCommand2); ligongong.setCommand(carrySedanChairCommand1); ligongong.setCommand(carrySedanChairCommand2); ligongong.notifyIt(); } }
结果输出:
小翠:打扫卫生
小花:打扫卫生
小卓子:抬轿子
小贵子:抬轿子
我原本想着命令的执行者(Receiver)应该有很多种,比如我的例子中,有宫女和太监。但是我的实际感受是,这更合适对应单一的执行者。
虽然我实现了多个执行者,但其实执行者和具体的命令间是深度耦合的,比如打扫的命令(CleanCommand)拥有一个宫女(执行者)的实例。
如果太监也要打扫就要新建另外一个拥有太监实例的打扫命令类,虽然你可以抽象出一个执行者父类,让他们都持有这个父类的实例,但你不能调用father.clean(),除非你在父类中定义这个方法,但每当子类增加方法,父类都要相应增加,这显然不是一个好的设计。我暂时并没有想到一个好的方式去解决这个问题,所以私以为,可能更适合用于单一类型的执行者。
emmm,我刚刚又想了想,可能是我这个例子中执行者的分类有问题,可能通常每个执行者类的工作范围相差比较大,像我举的例子中,宫女和太监可能有50%的工作是重合的,如果我们以具体的工作类型来分类,比如御膳房的,洗衣房的,这样分,就不会有问题了。算是走进了思维的误区吧。引以为戒。
总结:
我觉得书中写的几点挺好的,这边引用过来吧。
第一,它能较容易地设计一个命令队列;
第二,在需要的情况下,可以较容易地将命令记入日志;
第三,允许接收请求的一方决定是否要否决请求
第四,可以容易地实现对请求的撤销和重做;
第五,由于 加进新的具体命令类不影响其他类,因此增加新的具体命令类很容易。
最关键的优点就是命令模式把请求一个操作的对象与知道怎么执行一个操作的对象分割开。