一、概述
一般问题:在软件系统中,行为请求者与行为实现者通常是一种紧耦合的关系,但某些场合,比如需要对行为进行记录、撤销或重做、事务等处理时,这种无法抵御变化的紧耦合的设计就不太合适。
核心方案:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。
设计意图:一个对象调用另一个对象方法,或者换一种说法,一个对象向另一个对象发起一个请求,这在程序中是再正常不过的现象。然而设计模式的脾气向来都是“一言不合就拆,拆,拆”,这次拆不是因为请求内容多变,而是其执行时机多变。换句话说,我知道我要发起一个请求,但是什么时候执行、由谁来执行、是否会反悔都是不确定的。所以,干脆把请求对象化,想仍给谁就仍给谁,想什么时候扔就什么时候扔,不想要了就丢掉。这就是命令模式的初衷:把行为请求者与行为执行者解耦。
命令模式类图如下:
- Invoker是请求者,负责调用命令对象发起执行
- Receiver是接收者,负责请求具体内容的执行
我们以生活中点菜为例,说明命令模式的实际运用:
看上图,如果不使用命令模式,而是客户直接到厨房要求厨师做自己喜欢的宫保鸡丁,可以吗?当然可以,而且最有效率。但同时也损失了可能的服务升级,比如:
- 客户不能预约下单
- 客户必须知道哪个师傅会做宫保鸡丁
- 客户下单后无法撤回
如果采用命令模式,以上问题可以轻松解决。客户和厨师解耦了,而且可以对菜单有更灵活的控制。
二、应用实战
在Android中我们几乎每天都在使用命令模式,典型的就是Runnable:一个Runnable就是一个Command;Thread扮演Invoder的角色,负责发起执行Runnable;Receiver可以是系统中的任何类,通常也可以作为Runnable的成员变量或者以参数形式传入Runnable。Runnable类图如下:
以锁屏图案解锁中Runnable的用法为例:输入错误图案后,我们希望延迟几秒后清空错误图案,以便用户可以重新输入。
定义具体Runnable :
/** * Useful for clearing out the wrong pattern after a delay */ private Runnable mCancelPatternRunnable = new Runnable() { @Override public void run() { mLockPatternView.clearPattern(); } };
匹配错误后:
private void onPatternChecked(int userId, boolean matched, int timeoutMs, boolean isValidPattern) { ... //图案匹配失败 mSecurityMessageDisplay.setMessage(R.string.kg_wrong_pattern); //延迟执行Runnable mLockPatternView.postDelayed(mCancelPatternRunnable, PATTERN_CLEAR_TIMEOUT_MS); } } }
如果延迟时间未到,用户再次输入,则立刻清空错误图案,并且撤回已在延时的Runnable:
@Override public void onPatternStart() { //撤回已经在延时的Runnable mLockPatternView.removeCallbacks(mCancelPatternRunnable); mSecurityMessageDisplay.setMessage(""); }
三、总结
总结:命令模式是一种行为型设计模式,它针对程序中最常见的请求与执行,将请求的发起者与执行者解耦,同时给予命令更多的可控性。
优点:
- 降低系统耦合
- 新命令易于扩展
缺点:可能造成过多命令类