Java 设计模式14——命令模式

模式定义

命令模式(Command Pattern)是一种数据驱动的设计模式,它属于行为型模式。请求以命令的形式包裹在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。模式的核心思想将一个请求封装成一个对象,从而可以用不同的请求对客户进行参数化

实现

定义三个角色:

  1. RealCommandExecutor, 真正的命令执行对象;
  2. Command,封装了具体命令执行者的命令对象
  3. Invoke,使用命令对象的入口

模式示例

举一个例子,一个控制终端,可以控制5种不同的角色进行攻击,这5种角色就是上面的真正命令执行对象,5中角色分别对应5个Command,调用者可以通过控制终端让执行不同的命令以让不同角色进行攻击。下面看具体代码。

1
2
3
4
5
6
public interface Command {
/**
* 攻击命令
*/
void attack();
}

定义5中不同的具体命令执行对象。

1
2
3
4
5
6
7
8
9
/**
* 术士
*/
public class Wizard {
public void spell() {
System.out.println("wizard spell");
}

}
1
2
3
4
5
6
7
8
/**
* 武士
*/
public class Warrior {
public void attack() {
System.out.println("warrior attack!");
}
}
1
2
3
4
5
6
7
8
/**
* 骑士
*/
public class Knight {
public void rideHorseAndAttack(){
System.out.println("knight ride hourse and attack!");
}
}
1
2
3
4
5
6
7
8
/**
* 哥布林(传说中的类人生物)
*/
public class Goblin {
public void attack() {
System.out.println("goblin attack!");
}
}
1
2
3
4
5
6
7
8
9
10
11
/**
* 棒球运动员
*/
public class BaseballPlayer {
/**
* 挥动球棒
*/
public void swingBat(){
System.out.println("baseball player swing bat!");
}
}

将不同角色的攻击请求封装成Command命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 术士对应的命令
*/
public class WizardCommand implements Command {

private Wizard wizard;

public WizardCommand(Wizard wizard) {
this.wizard = wizard;
}

@Override
public void attack() {
wizard.spell();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 武士对应的命令
*/
public class WarriorCommand implements Command {

private Warrior warrior;

public WarriorCommand(Warrior warrior) {
this.warrior = warrior;
}

@Override
public void attack() {
warrior.attack();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 骑士对应的命令
*/
public class KnightCommand implements Command{

private Knight knight;

public KnightCommand(Knight knight) {
this.knight = knight;
}

@Override
public void attack() {
knight.rideHorseAndAttack();
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 哥布林对应的命令
*/
public class GoblinCommand implements Command {

private Goblin goblin;

public GoblinCommand(Goblin goblin) {
this.goblin = goblin;
}

@Override
public void attack() {
goblin.attack();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 棒球运动员对应的命令
*/
public class BaseballPlayerCommand implements Command{

private BaseballPlayer baseballPlayer;

public BaseballPlayerCommand(BaseballPlayer baseballPlayer){
this.baseballPlayer = baseballPlayer;
}

@Override
public void attack() {
baseballPlayer.swingBat();
}
}

定义一个远程控制终端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* 控制终端
*/
public class SimpleRemoteControl {
private final List<Command> commands;

public SimpleRemoteControl() {
commands = new ArrayList<Command>();
}

public void addCommand(Command command) {
commands.add(command);
}

public void buttonWasPressed(int index) {
commands.get(index).attack();
}

public int getCommandsSize() {
return commands.size();
}

@Override
public String toString() {

StringBuffer stringBuff = new StringBuffer();
stringBuff.append("\n------------Remote Control------------\n");

for (int i = 0; i < commands.size(); i++) {
stringBuff.append("[slot " + i + "] " + commands.get(i).getClass().getName() + "\n");
}
return stringBuff.toString();
}
}

编写客户端运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class FightMain {

public static void main(String[] args) {
SimpleRemoteControl remoteControl = new SimpleRemoteControl();

Knight knight = new Knight();
Wizard wizard = new Wizard();
Warrior warrior = new Warrior();
Goblin goblin = new Goblin();
BaseballPlayer baseballPlayer = new BaseballPlayer();
// SonGoku sonGoku = new SonGoku();

Command knightCommand = new KnightCommand(knight);
Command wizardCommand = new WizardCommand(wizard);
Command warriorCommand = new WarriorCommand(warrior);
Command goblinCommand = new GoblinCommand(goblin);
Command baseballPlayerCommand = new BaseballPlayerCommand(baseballPlayer);
// Command sonGoKuCommand = new SonGokuCommand(sonGoku);

// List<Command> commandList= new ArrayList<Command>();
// commandList.add(sonGoKuCommand);
// commandList.add(baseballPlayerCommand);
// Command mircoCommand = new MircoCommand(commandList);

remoteControl.addCommand(knightCommand);
remoteControl.addCommand(wizardCommand);
remoteControl.addCommand(warriorCommand);
remoteControl.addCommand(goblinCommand);
remoteControl.addCommand(baseballPlayerCommand);
// remoteControl.addCommand(sonGoKuCommand);
// remoteControl.addCommand(mircoCommand);

System.out.println(remoteControl);

for (int i = 0; i < remoteControl.getCommandsSize(); i++) {
System.out.println("-------------"+i+"----------------");
remoteControl.buttonWasPressed(i);
}

}
}

输出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
------------Remote Control------------
[slot 0] com.rainmonth.pattern.behavioral.command.command.KnightCommand
[slot 1] com.rainmonth.pattern.behavioral.command.command.WizardCommand
[slot 2] com.rainmonth.pattern.behavioral.command.command.WarriorCommand
[slot 3] com.rainmonth.pattern.behavioral.command.command.GoblinCommand
[slot 4] com.rainmonth.pattern.behavioral.command.command.BaseballPlayerCommand

-------------0----------------
knight ride hourse and attack!
-------------1----------------
wizard spell
-------------2----------------
warrior attack!
-------------3----------------
goblin attack!
-------------4----------------
baseball player swing bat!

从上例可以看出,我们可以很容易的新增一种角色,然后定义该角色的攻击方式,可以使单次攻击(attack里面调用一个攻击方法),也可以是组合攻击(attack里面调用两个攻击方法),还可以利用组合模式,将一组命令封装成Command对象来执行。当然前提是要有相应的角色,要有相应的命令,也就是说要有相应的类。这就可能造成命令泛滥的情形,需要加以控制。

模式总结

优缺点

  • 优点
    1. 降低了系统耦合度;
    2. 新的命令可以很容易添加到系统中去。
  • 缺点
    1. 使用命令模式可能会导致某些系统有过多的具体命令类;

使用场景

认为是命令的地方都可以使用命令模式,比如: 1、GUI 中每一个按钮都是一条命令。 2、模拟 CMD。

注意事项

系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作,也可以考虑使用命令模式。关于命令模式的扩展,可以参考这篇文章。