命令
命令模式是一种将发送者和接收者解耦的对象行为模式。它也可以被认为是回调方法的面向对象等价物。回调:它是在用户操作后注册在稍后时间调用的函数。
范围
对象
目的
行为
意图
将服务请求封装成一个对象。
适用性
- 用要执行的操作参数化对象
- 在不同时间指定、排队和执行请求
- 用于请求历史
- 用于多级撤销/重做
结构
后果
- + 抽象服务执行者
- + 支持任意级别的撤销/重做
- + 组合产生宏命令
- - 可能会导致大量琐碎的命令子类
例子
此模式的最佳示例是图形用户界面。在大多数多数据界面上,您都有“新建”菜单和“新建”按钮,带有类似 Libre Office Writer 中的图标。这两个控件都连接到一个命令对象,因此操作由相同的代码执行。
成本
此模式处理整个程序的架构。它可能会对项目产生重大影响。
创建
如果代码已经存在,此模式非常昂贵。
维护
此模式维护起来非常昂贵。
移除
此模式移除起来也非常昂贵。
建议
- 使用命令术语来向其他开发人员表明使用该模式。
实现
考虑一个“简单”开关。在这个例子中,我们用两个命令配置开关:打开灯和关闭灯。此命令模式实现的一个好处是,开关可以用于任何设备,而不仅仅是灯——以下示例中的开关打开和关闭灯,但开关的构造函数能够接受其两个参数的任何 Command 子类。例如,您可以配置开关来启动发动机。
/* The Command interface */
public interface Command {
void execute();
}
import java.util.List;
import java.util.ArrayList;
/* The Invoker class */
public class Switch {
private List<Command> history = new ArrayList<Command>();
public Switch() {
}
public void storeAndExecute(Command cmd) {
this.history.add(cmd); // optional
cmd.execute();
}
}
/* The Receiver class */
public class Light {
public Light() {
}
public void turnOn() {
System.out.println("The light is on");
}
public void turnOff() {
System.out.println("The light is off");
}
}
/* The Command for turning on the light - ConcreteCommand #1 */
public class FlipUpCommand implements Command {
private Light theLight;
public FlipUpCommand(Light light) {
this.theLight = light;
}
public void execute(){
theLight.turnOn();
}
}
/* The Command for turning off the light - ConcreteCommand #2 */
public class FlipDownCommand implements Command {
private Light theLight;
public FlipDownCommand(Light light) {
this.theLight = light;
}
public void execute() {
theLight.turnOff();
}
}
/* The test class or client */
public class PressSwitch {
public static void main(String[] args){
// Check number of arguments
if (args.length != 1) {
System.err.println("Argument \"ON\" or \"OFF\" is required.");
System.exit(-1);
}
Light lamp = new Light();
Command switchUp = new FlipUpCommand(lamp);
Command switchDown = new FlipDownCommand(lamp);
// See criticism of this model above:
// The switch itself should not be aware of lamp details (switchUp, switchDown)
// either directly or indirectly
Switch mySwitch = new Switch();
switch (args[0]) {
case "ON":
mySwitch.storeAndExecute(switchUp);
break;
case "OFF":
mySwitch.storeAndExecute(switchDown);
break;
default:
System.err.println("Argument \"ON\" or \"OFF\" is required.");
System.exit(-1);
}
}
}
操作
要做的实现是
- 在将命令放入历史列表之前复制命令
- 处理滞后
- 支持事务
public interface Command {
public int execute(int a, int b);
}
public class AddCommand implements Command {
public int execute(int a, int b) {
return a + b;
}
}
public class MultCommand implements Command {
public int execute(int a, int b) {
return a * b;
}
}
public class TestCommand {
public static void main(String a[]) {
Command add = new AddCommand();
add.execute(1, 2); // returns 3
Command multiply = new MultCommand();
multiply.execute(2, 3); // returns 6
}
}
在上面的例子中,可以注意到命令模式将调用操作的对象与具有执行操作知识的对象解耦。
Java 中的命令
菜单和按钮提供了对命令模式需求的经典示例。当您向应用程序添加菜单时,您必须用描述用户可以选择的操作的单词来配置菜单,例如保存和打开。按钮也是如此。您还必须配置菜单或按钮,以便它能够采取行动,在响应用户点击时调用一个方法。但是,JMenuItem 或 JButton 类没有办法知道在选择项目或按钮时要执行什么操作。在下面的示例中,我们对菜单项和按钮使用相同的 Action,这使我们不必编写两次相同的代码。
Action actionOpen = new AbstractAction("Open...", iconOpen) {
public void actionPerformed(ActionEvent e) {
... // open a file
}
}
JMenu mFile = new JMenu("File");
JMenuItem item = mFile.add(actionOpen); // use the same action for both a menu item ...
JToolBar m_toolBar = new JToolBar();
JButton bOpen = new JButton(actionOpen); // ... and a button
m_toolBar.add(bOpen);
Java Swing 应用程序通常应用中介者模式,注册一个接收所有 GUI 事件的单个对象。此对象调解组件的交互,并将用户输入转换为业务领域对象的命令。
Java 中的撤销
Swing 提供了撤销/重做功能。
import javax.swing.undo.*;
UndoManager undoManager = new UndoManager();
Action undoAction, redoAction;
undoAction = new AbstractAction("Undo",
new ImageIcon("edit_undo.gif")) {
public void actionPerformed(ActionEvent e) {
try {
undoManager.undo(); // undoManager.redo();
}
catch (CannotUndoException ex) {
System.err.println("Unable to undo: " + ex);
}
updateUndo();
}
};
// same for Redo
protected void updateUndo() {
if(undo.canUndo()) {
undoAction.setEnabled(true);
undoAction.putValue(Action.NAME, undo.getUndoPresentationName());
} else {
undoAction.setEnabled(false);
undoAction.putValue(Action.NAME, "Undo");
}
if(undo.canRedo()) {
redoAction.setEnabled(true);
redoAction.putValue(Action.NAME, undo.getRedoPresentationName());
} else {
redoAction.setEnabled(false);
redoAction.putValue(Action.NAME, "Redo");
}
}
考虑一个“简单”开关。在这个例子中,我们用两个命令配置开关:打开灯和关闭灯。
此命令模式实现的一个好处是,开关可以用于任何设备,而不仅仅是灯。以下 C# 实现中的开关打开和关闭灯,但开关的构造函数能够接受其两个参数的任何 Command 子类。例如,您可以配置开关来启动发动机。
using System;
namespace CommandPattern;
public interface ICommand
{
void Execute();
}
/* The Invoker class */
public class Switch
{
ICommand _closedCommand;
ICommand _openedCommand;
public Switch(ICommand closedCommand, ICommand openedCommand)
{
_closedCommand = closedCommand;
_openedCommand = openedCommand;
}
// Close the circuit / power on
public void Close()
{
_closedCommand.Execute();
}
// Open the circuit / power off
public void Open()
{
_openedCommand.Execute();
}
}
/* An interface that defines actions that the receiver can perform */
public interface ISwitchable
{
void PowerOn();
void PowerOff();
}
/* The Receiver class */
public class Light : ISwitchable
{
public void PowerOn()
{
Console.WriteLine("The light is on");
}
public void PowerOff()
{
Console.WriteLine("The light is off");
}
}
/* The Command for turning off the device - ConcreteCommand #1 */
public class CloseSwitchCommand : ICommand
{
private ISwitchable _switchable;
public CloseSwitchCommand(ISwitchable switchable)
{
_switchable = switchable;
}
public void Execute()
{
_switchable.PowerOff();
}
}
/* The Command for turning on the device - ConcreteCommand #2 */
public class OpenSwitchCommand : ICommand
{
private ISwitchable _switchable;
public OpenSwitchCommand(ISwitchable switchable)
{
_switchable = switchable;
}
public void Execute()
{
_switchable.PowerOn();
}
}
/* The test class or client */
internal class Program
{
public static void Main(string[] arguments)
{
string argument = arguments.Length > 0 ? arguments[0].ToUpper() : null;
ISwitchable lamp = new Light();
// Pass reference to the lamp instance to each command
ICommand switchClose = new CloseSwitchCommand(lamp);
ICommand switchOpen = new OpenSwitchCommand(lamp);
// Pass reference to instances of the Command objects to the switch
Switch @switch = new Switch(switchClose, switchOpen);
if (argument == "ON")
{
// Switch (the Invoker) will invoke Execute() on the command object.
@switch.Open();
}
else if (argument == "OFF")
{
// Switch (the Invoker) will invoke the Execute() on the command object.
@switch.Close();
}
else
{
Console.WriteLine("Argument \"ON\" or \"OFF\" is required.");
}
}
}
以下代码是 C# 中命令模式的另一个实现。
using System;
using System.Collections.Generic;
namespace CommandPattern
{
public interface ICommand
{
void Execute();
}
/* The Invoker class */
public class Switch
{
private List<ICommand> _commands = new List<ICommand>();
public void StoreAndExecute(ICommand command)
{
_commands.Add(command);
command.Execute();
}
}
/* The Receiver class */
public class Light
{
public void TurnOn()
{
Console.WriteLine("The light is on");
}
public void TurnOff()
{
Console.WriteLine("The light is off");
}
}
/* The Command for turning on the light - ConcreteCommand #1 */
public class FlipUpCommand : ICommand
{
private Light _light;
public FlipUpCommand(Light light)
{
_light = light;
}
public void Execute()
{
_light.TurnOn();
}
}
/* The Command for turning off the light - ConcreteCommand #2 */
public class FlipDownCommand : ICommand
{
private Light _light;
public FlipDownCommand(Light light)
{
_light = light;
}
public void Execute()
{
_light.TurnOff();
}
}
/* The test class or client */
internal class Program
{
public static void Main(string[] args)
{
Light lamp = new Light();
ICommand switchUp = new FlipUpCommand(lamp);
ICommand switchDown = new FlipDownCommand(lamp);
Switch s = new Switch();
string arg = args.Length > 0 ? args[0].ToUpper() : null;
if (arg == "ON")
{
s.StoreAndExecute(switchUp);
}
else if (arg == "OFF")
{
s.StoreAndExecute(switchDown);
}
else
{
Console.WriteLine("Argument \"ON\" or \"OFF\" is required.");
}
}
}
}
以下代码是 Python 中命令模式的实现。
class Switch(object):
"""The INVOKER class"""
def __init__(self, flip_up_cmd, flip_down_cmd):
self.flip_up = flip_up_cmd
self.flip_down = flip_down_cmd
class Light(object):
"""The RECEIVER class"""
def turn_on(self):
print "The light is on"
def turn_off(self):
print "The light is off"
class LightSwitch(object):
"""The CLIENT class"""
def __init__(self):
lamp = Light()
self._switch = Switch(lamp.turn_on, lamp.turn_off)
def switch(self, cmd):
cmd = cmd.strip().upper()
if cmd == "ON":
self._switch.flip_up()
elif cmd == "OFF":
self._switch.flip_down()
else:
print 'Argument "ON" or "OFF" is required.'
# Execute if this file is run as a script and not imported as a module
if __name__ == "__main__":
light_switch = LightSwitch()
print "Switch ON test."
light_switch.switch("ON")
print "Switch OFF test."
light_switch.switch("OFF")
print "Invalid Command test."
light_switch.switch("****")
/* The Command interface */
trait Command {
def execute()
}
/* The Invoker class */
class Switch {
private var history: List[Command] = Nil
def storeAndExecute(cmd: Command) {
cmd.execute()
this.history :+= cmd
}
}
/* The Receiver class */
class Light {
def turnOn() = println("The light is on")
def turnOff() = println("The light is off")
}
/* The Command for turning on the light - ConcreteCommand #1 */
class FlipUpCommand(theLight: Light) extends Command {
def execute() = theLight.turnOn()
}
/* The Command for turning off the light - ConcreteCommand #2 */
class FlipDownCommand(theLight: Light) extends Command {
def execute() = theLight.turnOff()
}
/* The test class or client */
object PressSwitch {
def main(args: Array[String]) {
val lamp = new Light()
val switchUp = new FlipUpCommand(lamp)
val switchDown = new FlipDownCommand(lamp)
val s = new Switch()
try {
args(0).toUpperCase match {
case "ON" => s.storeAndExecute(switchUp)
case "OFF" => s.storeAndExecute(switchDown)
case _ => println("Argument \"ON\" or \"OFF\" is required.")
}
} catch {
case e: Exception => println("Arguments required.")
}
}
}
以下代码是 Javascript 中命令模式的实现。
/* The Invoker function */
var Switch = function(){
this.storeAndExecute = function(command){
command.execute();
}
}
/* The Receiver function */
var Light = function(){
this.turnOn = function(){ console.log ('turn on')};
this.turnOff = function(){ console.log ('turn off') };
}
/* The Command for turning on the light - ConcreteCommand #1 */
var FlipUpCommand = function(light){
this.execute = light.turnOn;
}
/* The Command for turning off the light - ConcreteCommand #2 */
var FlipDownCommand = function(light){
this.execute = light.turnOff;
}
var light = new Light();
var switchUp = new FlipUpCommand(light);
var switchDown = new FlipDownCommand(light);
var s = new Switch();
s.storeAndExecute(switchUp);
s.storeAndExecute(switchDown);
Object subclass: #Switch
instanceVariableNames:
' flipUpCommand flipDownCommand '
classVariableNames: ''
poolDictionaries: ''
Object subclass: #Light
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
Object subclass: #PressSwitch
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
!Switch class methods !
upMessage: flipUpMessage downMessage: flipDownMessage
^self new upMessage: flipUpMessage downMessage: flipDownMessage; yourself.! !
!Switch methods !
upMessage: flipUpMessage downMessage: flipDownMessage
flipUpCommand := flipUpMessage.
flipDownCommand := flipDownMessage.!
flipDown
flipDownCommand perform.!
flipUp
flipUpCommand perform.! !
!Light methods !
turnOff
Transcript show: 'The light is off'; cr.!
turnOn
Transcript show: 'The light is on'; cr.! !
!PressSwitch class methods !
switch: state
" This is the test method "
| lamp switchUp switchDown switch |
lamp := Light new.
switchUp := Message receiver: lamp selector: #turnOn.
switchDown := Message receiver: lamp selector: #turnOff.
switch := Switch upMessage: switchUp downMessage: switchDown.
state = #on ifTrue: [ ^switch flipUp ].
state = #off ifTrue: [ ^switch flipDown ].
Transcript show: 'Argument #on or #off is required.'.
! !
以下代码是 PHP 中命令模式的实现。
<?php
interface Command
{
public function execute();
}
/** The Invoker class */
class Switcher
{
private $history = array();
public function storeAndExecute(Command $cmd)
{
$this->history[] = $cmd;
$cmd->execute();
}
}
/** The Receiver class */
class Light
{
public function turnOn()
{
echo("The light is on");
}
public function turnOff()
{
echo("The light is off");
}
}
/** The Command for turning on the light - ConcreteCommand #1 */
class FlipUpCommand implements Command {
private $theLight;
public function __construct(Light $light) {
$this->theLight = $light;
}
public function execute() {
$this->theLight->turnOn();
}
}
/** The Command for turning off the light - ConcreteCommand #2 */
class FlipDownCommand implements Command {
private $theLight;
public function __construct(Light $light) {
$this->theLight = $light;
}
public function execute() {
$this->theLight->turnOff();
}
}
/* The test class or client */
class PressSwitch {
public static function main(string $args){
$lamp = new Light();
$switchUp = new FlipUpCommand($lamp);
$switchDown = new FlipDownCommand($lamp);
$mySwitch = new Switcher();
switch($args) {
case 'ON':
$mySwitch->storeAndExecute($switchUp);
break;
case 'OFF':
$mySwitch->storeAndExecute($switchDown);
break;
default:
echo("Argument \"ON\" or \"OFF\" is required.");
break;
}
}
}
/*INPUT*/
PressSwitch::main('ON'). PHP_EOL;
PressSwitch::main('OFF'). PHP_EOL;
/*OUTPUT*/
//The light is on
//The light is off