公告
如果您是第一次阅读我的设计模式系列文章,建议先阅读设计模式开篇,希望能得到您宝贵的建议。
前言
随着上文 装饰器模式 中的顾客Alice
购买了机器人回家后,他开始了机器人使用之旅。
正文
机器人
:“你好,Alice!”
Alice
:“你好!你叫什么?”
机器人
:“Alice,我是你忠臣的仆人 Samu。”
Alice
:“Samu,你有什么功能?”
机器人
:“我会唱歌,我也会跳舞。Alice,你只要说 唱歌+歌曲名称
或者跳舞+舞蹈名称
我就会唱歌跳舞。如果你连续的说唱歌+歌曲名称
跳舞+舞蹈名称
,我会一边唱歌一边跳舞。”
Alice
:“唱歌稻香 跳舞肚皮舞”
机器人
:“对这个世界如果你有太多的抱怨”
机器人
:“跌倒了 就不敢继续往前走”
机器人
:“为什么 人要这么的脆弱 堕落”
……(画外音:曼妙的舞姿)……
程序员视角
现在要实现对机器人Samu
说(发出指令) 唱歌+歌曲名称
或者跳舞+舞蹈名称
,机器人便会自动的唱歌或跳舞。
代码实现
定义命令的接口的目的是为了抽象类型,并且将命令实现分离。
public interface ICommand {
void excute();
}
内部命令抽象对象,用于提供命令的上下文。
public abstract class Command implements ICommand {
private String param;
public Command(String param) {
this.param = param;
}
protected String param() {
return param;
}
}
唱歌命令的实现(跳舞命令实现类似)
public class SongCommandImpl extends Command {
public static final String KEY_SONG = "唱歌";
public SongCommandImpl(String param) {
super(param);
}
@Override
public void excute() {
System.out.println("调用指令 " + KEY_SONG + param());
}
}
命令的接口与实现均已准备妥当,接下去是思考如何调用命令。
为了避免客户端与具体的命令对象耦合,所以通常建议搭配适配器模式
将唱歌
、跳舞
这些指令,转化为程序可理解的SongCommandImpl
、DanceCommandImpl
。
// 适配器对象用于适配字符串到命令的执行接口
public class StringCommandAdapter implements ICommand {
private String method;
private String param;
private HashMap<String, Command> map = new HashMap<>();
private Command pickCommand(String method, String param) {
Command command = null;
if (method.startsWith(DanceCommandImpl.KEY_DANCE)) {
command = createCommand(DanceCommandImpl.KEY_DANCE, param, DanceCommandImpl.class);
} else if (method.startsWith(SongCommandImpl.KEY_SONG)) {
command = createCommand(SongCommandImpl.KEY_SONG, param, SongCommandImpl.class);
}
return command;
}
private Command createCommand(String key, String param, Class<?> clazz) {
if (map.containsKey(key)) {
return map.get(key);
}
Command command = null;
try {
Constructor<?> constructor = clazz.getConstructor(String.class);
command = (Command) constructor.newInstance(param);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
if (command != null) {
map.put(key, command);
return command;
} else {
throw new IllegalArgumentException("NO COMMAND CREATED!!!");
}
}
public StringCommandAdapter(String method, String param) {
this.method = method;
this.param = param;
}
@Override
public void excute() {
System.out.println("调用指令:" + toString());
Command command = pickCommand(this.method, this.param);
command.excute();
}
@Override
public String toString() {
return "StringCommandAdapter{" +
"method='" + method + '\'' +
", param='" + param + '\'' +
", map=" + map +
'}';
}
}
命令如何构建已经完毕,接着是命令如何被触发。这里模拟构建机器人接收到命令在触发
// 构建命令管理器,命令的日志跟踪都可以在这里实现。
public class CommandManager {
public void invoke(StringCommandAdapter adapter) {
adapter.excute();
}
}
考虑到通常命令都是通过观察者接收到消息后才触发调用的,所以这里模拟了观察者接收到消息的调用过程。
public class SamuCommandReceiver {
private CommandManager invoke = new CommandManager();
private Machine machine;
public SamuCommandReceiver(Machine machine) {
this.machine = machine;
System.out.printf("机器人%s的接收功能正常开启%n", machine);
}
public void onReceive(String command, String param) {
System.out.printf("机器人%s接收到指令:%s,%s%n", machine, command, param);
invoke.invoke(new StringCommandAdapter(command, param));
}
}
客户端的调用
public static void main(String args[]) {
Machine machine = new Machine("Samu");
SamuCommandReceiver receiver = new SamuCommandReceiver(machine);
receiver.onReceive("唱歌", "稻香");
receiver.onReceive("跳舞", "肚皮舞");
}
运行结果
创建了机器人 Samu
机器人Samu的接收功能正常开启
机器人Samu接收到指令:唱歌,稻香
调用指令:StringCommandAdapter{method='唱歌', param='稻香', map={}}
调用指令 唱歌稻香
机器人Samu接收到指令:跳舞,肚皮舞
调用指令:StringCommandAdapter{method='跳舞', param='肚皮舞', map={}}
调用指令 跳舞肚皮舞
总结
在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计,使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活。
命令模式可以对发送者和接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求。这就是命令模式的模式动机。
命令模式在命令模式中,将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。命令模式是一种对象行为型模式,其别名为动作模式或事务模式。
命令模式包含四个角色:
- 抽象命令类中声明了用于执行请求的execute()等方法,通过这些方法可以调用请求接收者的相关操作;
- 具体命令类是抽象命令类的子类,实现了在抽象命令类中声明的方法,它对应具体的接收者对象,将接收者对象的动作绑定其中;
- 调用者即请求的发送者,又称为请求者,它通过命令对象来执行请求;
- 接收者执行与请求相关的操作,它具体实现对请求的业务处理。
命令模式的本质是对命令进行封装,将发出命令的责任和执行命令的责任分割开。
命令模式使请求本身成为一个对象,这个对象和其他对象一样可以被存储和传递。
命令模式的主要优点在于降低系统的耦合度,增加新的命令很方便,而且可以比较容易地设计一个命令队列和宏命令,并方便地实现对请求的撤销和恢复;
其主要缺点在于可能会导致某些系统有过多的具体命令类。
命令模式适用情况包括:
- 需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互;
- 需要在不同的时间指定请求、将请求排队和执行请求;
- 需要支持命令的撤销操作和恢复操作,需要将一组操作组合在一起,即支持宏命令。
网友评论