请求发送者与接收者解耦–命令模式
装修新房的最后几道工序之一是安装插座和开关,通过开关可以控制一些电器的打开和关 闭,例如电灯或者排气扇。在购买开关时,我们并不知道它将来到底用于控制什么电器,也 就是说,开关与电灯、排气扇并无直接关系,一个开关在安装之后可能用来控制电灯,也可 能用来控制排气扇或者其他电器设备。开关与电器之间通过电线建立连接,如果开关打开, 则电线通电,电器工作;反之,开关关闭,电线断电,电器停止工作。相同的开关可以通过 不同的电线来控制不同的电器,如图1所示:
在图1中,我们可以将开关理解成一个请求的发送者,用户通过它来发送一个“开灯”请求,而 电灯是“开灯”请求的最终接收者和处理者,在图中,开关和电灯之间并不存在直接耦合关系, 它们通过电线连接在一起,使用不同的电线可以连接不同的请求接收者,只需更换一根电 线,相同的发送者(开关)即可对应不同的接收者(电器)。
在软件开发中也存在很多与开关和电器类似的请求发送者和接收者对象,例如一个按钮,它 可能是一个“关闭窗口”请求的发送者,而按钮点击事件处理类则是该请求的接收者。为了降低 系统的耦合度,将请求的发送者和接收者解耦,我们可以使用一种被称之为命令模式的设计 模式来设计系统,在命令模式中,发送者与接收者之间引入了新的命令对象(类似图1中的电线),将发送者的请求封装在命令对象中,再通过命令对象来调用接收者的方法。本章我们 将学习用于将请求发送者和接收者解耦的命令模式。
模式中角色
1 抽象命令(Command):定义命令的接口,声明执行的方法。
2 具体命令(ConcreteCommand):具体命令,实现要执行的方法,它通常是“虚”的实现;通常会有接收者,并调用接收者的功能来完成命令要执行的操作。
3 接收者(Receiver):真正执行命令的对象。任何类都可能成为一个接收者,只要能实现命令要求实现的相应功能。
4 调用者(Invoker):要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。
5 客户端(Client):命令由客户端来创建,并设置命令的接收者。
关键代码:
public interface Command { public void execute(); } public class ConcreteCommand implements Command { private Receiver receiver = null; private String state; public ConcreteCommand(Receiver receiver){ this.receiver = receiver; } public void execute() { receiver.action(); } } public class Receiver { public void action(){ //真正执行命令操作的功能代码 } } public class Invoker { private Command command = null; public void setCommand(Command command) { this.command = command; } public void runCommand() { command.execute(); } } public class Client { public void assemble(){ //创建接收者 Receiver receiver = new Receiver(); //创建命令对象,设定它的接收者 Command command = new ConcreteCommand(receiver); //创建Invoker,把命令对象设置进去 Invoker invoker = new Invoker(); invoker.setCommand(command); } } |
下面给个例子,是模拟对电视机的操作有开机、关机、换台命令。代码如下
//命令接收者Receiver public class Tv { public int currentChannel = 0; public void turnOn() { System.out.println("The televisino is on."); } public void turnOff() { System.out.println("The television is off."); } public void changeChannel(int channel) { this.currentChannel = channel; System.out.println("Now TV channel is " + channel); } } //执行命令的接口 Command public interface Command { void execute(); } //开机命令 realCommand public class CommandOn implements Command { private Tv myTv; public CommandOn(Tv tv) { myTv = tv; } public void execute() { myTv.turnOn(); } } //关机命令 realCommand public class CommandOff implements Command { private Tv myTv; public CommandOff(Tv tv) { myTv = tv; } public void execute() { myTv.turnOff(); } } //频道切换命令 realCommand public class CommandChange implements Command { private Tv myTv; private int channel; public CommandChange(Tv tv, int channel) { myTv = tv; this.channel = channel; } public void execute() { myTv.changeChannel(channel); } } //可以看作是遥控器吧 Invoker public class Control { private Command onCommand, offCommand, changeChannel; public Control(Command on, Command off, Command channel) { onCommand = on; offCommand = off; changeChannel = channel; } public void turnOn() { onCommand.execute(); } public void turnOff() { offCommand.execute(); } public void changeChannel() { changeChannel.execute(); } } //测试类 public class Client { public static void main(String[] args) { // 命令接收者 Tv myTv = new Tv(); // 开机命令 CommandOn on = new CommandOn(myTv); // 关机命令 CommandOff off = new CommandOff(myTv); // 频道切换命令 CommandChange channel = new CommandChange(myTv, 2); // 命令控制对象 Control control = new Control(on, off, channel); // 开机 control.turnOn(); // 切换频道 control.changeChannel(); // 关机 control.turnOff(); } } |
执行结果为: The televisino is on. Now TV channel is 2 The television is off.
命令模式总结
命令模式是一种使用频率非常高的设计模式,它可以将请求发送者与接收者解耦,请求发送 者通过命令对象来间接引用请求接收者,使得系统具有更好的灵活性和可扩展性。在基于GUI 的软件开发,无论是在电脑桌面应用还是在移动应用中,命令模式都得到了广泛的应用。
1. 主要优点
命令模式的主要优点如下:
(1) 降低系统的耦合度。由于请求者与接收者之间不存在直接引用,因此请求者与接收者之间 实现完全解耦,相同的请求者可以对应不同的接收者,同样,相同的接收者也可以供不同的 请求者使用,两者之间具有良好的独立性。
(2) 新的命令可以很容易地加入到系统中。由于增加新的具体命令类不会影响到其他类,因此增加新的具体命令类很容易,无须修改原有系统源代码,甚至客户类代码,满足“开闭原则”的 要求。
(3) 可以比较容易地设计一个命令队列或宏命令(组合命令)。
(4) 为请求的撤销(Undo)和恢复(Redo)操作提供了一种设计和实现方案。
1. 主要缺点
命令模式的主要缺点如下:
使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个对请求接收者的调 用操作都需要设计一个具体命令类,因此在某些系统中可能需要提供大量的具体命令类,这 将影响命令模式的使用。
1. 适用场景
在以下情况下可以考虑使用命令模式:
(1) 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。请求调用者 无须知道接收者的存在,也无须知道接收者是谁,接收者也无须关心何时被调用。
(2) 系统需要在不同的时间指定请求、将请求排队和执行请求。一个命令对象和请求的初始调 用者可以有不同的生命期,换言之,最初的请求发出者可能已经不在了,而命令对象本身仍 然是活动的,可以通过该命令对象去调用请求接收者,而无须关心请求调用者的存在性,可 以通过请求日志文件等机制来具体实现。
(3) 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
(4) 系统需要将一组操作组合在一起形成宏命令。