命令模式(Command: choosing the operation at run-time)
在《高级C++:程序风格和习惯》(Addison-Wesley, 1992)中,Jim Coplien 定义了术语“算符”,它是一个对象,它的唯一功能是封装一个函数(因为“算符”在数学中是有意义的,在这本书中我将用一个更直接的术语“函数对象”)。关键在于去解耦函数的选择从函数被调用的地方。
这个术语被提及但是没有在《设计模式》中使用。然而,函数对象的主题在那本书的很多模式中被提及。
纯粹的说,一个命令是一个函数对象:一个方法是一个对象。通过在一个对象中封包一个方法,你能够传递它到另外的方法或对象中,作为一个参数,告诉他去执行特定的操作满足你的请求。你可以说,一个command是一个携带行为的信使(因为它的意图和使用是非常直接的),而非携带数据。
//: command:CommandPattern.java
package command;
import java.util.*;
import junit.framework.*;
interface Command {
void execute();
}
class Hello implements Command {
public void execute() {
System.out.print("Hello ");
}
}
class World implements Command {
public void execute() {
System.out.print("World! ");
}
}
class IAm implements Command {
public void execute() {
System.out.print("I'm the command pattern!");
}
}
// An object that holds commands:
class Macro {
private List commands = new ArrayList();
public void add(Command c) { commands.add(c); }
public void run() {
Iterator it = commands.iterator();
while(it.hasNext())
((Command)it.next()).execute();
}
}
public class CommandPattern extends TestCase {
Macro macro = new Macro();
public void test() {
macro.add(new Hello());
macro.add(new World());
macro.add(new IAm());
macro.run();
}
public static void main(String args[]) {
junit.textui.TestRunner.run(CommandPattern.class);
}
} ///:~
Command主要的目的允许你调控一个预期的动作到一个方法或者对象中。上面的例子,提供了一个一种方式排列要被一起执行的一套动作。在这个例子中,它允许你动态的创建新的行为,你所需要做的正常事情仅仅是写新的代码,但是上面的例子能够做的是写解释性的脚本(参见Interpreter pattern,如果你需要做得更复杂一些的话)
另一个命令模式的例子是重构:DirList.java。DirFilter类是一个命令对象,它包含了它的动作在accept( )方法中,然后被传递到list( )方法中。list( )方法通过调用accept( )方法决定什么包含在结果中。
《设计模式》说:“对于回调来说命令模式是面向对象的替换。”然而,我认为“返回”这个词是一个回调概念中本质的部分。就是说,我认为一个回调确实返回到回调的创建者。另一方面,使用一个命令对象你典型的创建它和控制它到另外的方法或者对象,并非随时关联的对命令对象本身。无论如何,这就是我的看法。本书后面,我整合乐一族设计模式在“回掉”的题头下面。
Strategy看起来是命令类的成员,所有的类从基类继承。但是如果你关注Command模式,你将看到他们又同样的结构:函数对象的一个层次。他们的不同在于层次的使用。正如refactor:DirList.java中看到的,你使用命令解决一个特定的问题—在那个例子中,从列表中选择文件。“保持同样的事”是被调用方法的主体,变化的部分被函数对象分离。我冒昧地说命令模式提供了灵活性当你编写程序的时候,而策略模式提供了灵活在运行时。此外,他们的区别很隐晦。
练习Exercises
4. 第三章练习1使用Command模式。
职责链(Chain of responsibility)
例子:翻译服务 (local->global->Babelfish).
职责链也许可以被看作一个使用策略对象递归的动态泛化。你进行一个调用,并且在相连结的每一个序列中的策略尝试满足这个调用。过程结束当一个策略完成或走完整个链。在递归中,一个方法一遍又一遍的调用自身直到满足终止条件;伴随着职责链,一个方法调用自身,它(通过在链上的策略移动)调用一个不同方法的实现,知道满足终止条件。终止条件或者是遍历完所有的链上的策略(案例中一个默认的对象返回;你也许不会提供一个默认的结果,因此你必须决定链的成功或失败)或者是一个策略是成功的。
取代单一方法满足一个请求,链中的多方法有一个机会满足请求,因此它有专家系统地意味。因此这个链是一个有效的列表,他能够被动态的创建,因此你也能够把它看作为一个更一般的动态的构建的switch语句。
GoF中,有相当多的讨论关于怎样创建职责链作为一个链表。然而,当你看这个设计模式的时候,是真的不用关心这个链怎么保持;那是一个实现细节。因为GoF是在STL之前写的,它被组合进大多数的C++编译器中,理由更可能是没有列表并且这样他们不得不创建一个列表和数据结构,是经常在学院派当做一种基本技能来教授。数据结构的想法应当是程序语言使用的标准工具,也许并未被GoF的作者使用。我坚持职责链的实现作为一个链(特别的,链表)什么也不添加到方案中,并且仅仅容易的被实现通过应用标准的java List,入下面所示。此外,你将看到我尝试分离链-管理部分从各种的策略实线中,因此代码容易被重用。
在上面StrategyPattern.java中,也许你想要找到一个自动解决方案。职责链提供了一种方式来解决这个问题通过链化策略对象到一起,并且提供一种机制来自动重用每一个链中的对象。
//: chainofresponsibility:FindMinima.java
package chainofresponsibility;
import com.bruceeckel.util.*; // Arrays2.toString()
import junit.framework.*;
// Carries the result data and
// whether the strategy was successful:
class LineData {
public double[] data;
public LineData(double[] data) { this.data = data; }
private boolean succeeded;
public boolean isSuccessful() { return succeeded; }
public void setSuccessful(boolean b) { succeeded = b; }
}
interface Strategy {
LineData strategy(LineData m);
}
class LeastSquares implements Strategy {
public LineData strategy(LineData m) {
System.out.println("Trying LeastSquares algorithm");
LineData ld = (LineData)m;
// [ Actual test/calculation here ]
LineData r = new LineData(
new double[] { 1.1, 2.2 }); // Dummy data
r.setSuccessful(false);
return r;
}
}
class NewtonsMethod implements Strategy {
public LineData strategy(LineData m) {
System.out.println("Trying NewtonsMethod algorithm");
LineData ld = (LineData)m;
// [ Actual test/calculation here ]
LineData r = new LineData(
new double[] { 3.3, 4.4 }); // Dummy data
r.setSuccessful(false);
return r;
}
}
class Bisection implements Strategy {
public LineData strategy(LineData m) {
System.out.println("Trying Bisection algorithm");
LineData ld = (LineData)m;
// [ Actual test/calculation here ]
LineData r = new LineData(
new double[] { 5.5, 6.6 }); // Dummy data
r.setSuccessful(true);
return r;
}
}
class ConjugateGradient implements Strategy {
public LineData strategy(LineData m) {
System.out.println(
"Trying ConjugateGradient algorithm");
LineData ld = (LineData)m;
// [ Actual test/calculation here ]
LineData r = new LineData(
new double[] { 5.5, 6.6 }); // Dummy data
r.setSuccessful(true);
return r;
}
}
class MinimaFinder {
private static Strategy[] solutions = {
new LeastSquares(),
new NewtonsMethod(),
new Bisection(),
new ConjugateGradient(),
};
public static LineData solve(LineData line) {
LineData r = line;
for(int i = 0; i < solutions.length; i++) {
r = solutions[i].strategy(r);
if(r.isSuccessful())
return r;
}
throw new RuntimeException("unsolved: " + line);
}
}
public class FindMinima extends TestCase {
LineData line = new LineData(new double[]{
1.0, 2.0, 1.0, 2.0, -1.0, 3.0, 4.0, 5.0, 4.0
});
public void test() {
System.out.println(Arrays2.toString(
((LineData)MinimaFinder.solve(line)).data));
}
public static void main(String args[]) {
junit.textui.TestRunner.run(FindMinima.class);
}
} ///:~
1. 实现职责链创建一个“专家系统”,通过尝试一个一个的方案是否匹配,成功地解决问题。你应当能够动态的添加方案到这个专家系统中。方案的测试应当是一个字符串匹配,当一个方案适合的时候,专家系统应当能够返回合适的ProblemSolver对象,配合其他的模式。
2. 实现职责链创建一个语言翻译器,开始先搜索本地特定的翻译系统(特定在于你的问题域),然后是一个更全局一般化的系统,最终返回BabelFish 如果它不能翻译任何东西。注意职责链中每个连接能够部分的翻译。
3. 实现职责链创建一个一个工具帮助重新格式化 JAVA 源代码通过尝试多个方式打破链。注意正常的代码和注释可能需要不同的对待,尝试实现一个职责树。注意与 Composite 模式的相似性;也许更一般的描述应该是 Composite of Strategies。来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/25966/viewspace-53327/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/25966/viewspace-53327/