算法分解(Algorithmic partitioning)
命令模式(Command):运行时刻选择操作
在《Advanced C++: Programming Styles And Idioms》 (Addison-Wesley, 1992) 一书中,Jim Copline借用了functor这个术语,用以指代那些只为封装一个函数而构造的对象(因为“functor”在数学上有专门的含义,所以在本书中我会使用更明确的术语:函数对象function object)。关于这个模式重要的一点是,将被调用函数的选择和被调用函数的调用分离开来。
《设计模式》只是提到函数对象这个术语而没有使用它。但是,关于函数对象的话题在那本书的模式章节里倒是重复了好几次。
从本质上说,Command就是一个函数对象:一个被封装成对象的方法。通过把方法封装到一个对象,你可以把它当作参数传给其它方法或者对象,让它们在实现你的某个请求(request)的时候完成一些特殊的操作。你可能会说Command其实就是一个把数据成员换成行为的messenger(因为它的目的和使用方法都很直接)。
//: 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模式最重要的一点就是,你可以通过它把想要完成的动作(action)交给一个方法或者对象。上面的例子提供了一种方法把一系列的动作排队后集中处理。这种情况下,你可以动态的创建新的行为,而通常你只能靠编写新的代码才能做到这一点;上例中,你可以通过解释脚本来完成(如果你需要(动态改变)的行为非常复杂,那么请参看Interpreter模式)。
另外一个关于Command模式的例子是refactor:DirList.java[????]。DirFilter类是一个command对象,它的动作包含在accept()方法里,而这些动作又需要传递给list()方法。List()方法通过调用accept()来决定结果中需要包含哪些东西。
《设计模式》一书写道“Commands其实就是回调函数(callbacks)的面向对象替代品(replacement)。”但是,我认为,“回(back)”对于回调(callback)的概念来说是非常关键的。也就是说,回调函数实际上会回溯到它的创建者。而另一方面,通常你只是创建Command对象然后把它传递给某个方法或对象,随后创建者和Command对象之间不再有什么联系。总之,这是我个人的一点看法。本书后面的章节,我会把一组相关的设计模式放到一起,标题是“回调”。
Strategy模式看起来像是从同一个基类继承而来的一系列Command类。但是仔细看看Command模式,你就会发现它也具有同样的结构:一系列分层次的函数对象。不同之处在于,这些函数对象的用法和Strategy模式不同。就像前面Refactor: DirList.Java那个例子,使用Command是为了解决特定问题 ——从一个列表选择文件。“不变的部分”是被调用的那个方法,变化的部分被分离出来放到函数对象里。我想冒昧的下个结论:Command模式在编码阶段提供灵活性,而Strategy模式的灵活性在运行时体现出来。尽管如此,这种区别却是非常模糊的。
练习:
4.用Command模式重做第三章的练习1。
职责链(Chain of responsibility)
职责链模式可以被想象成递归(recursion)的动态泛化(generalization),这种泛化通过使用Strategy对象来完成。被调用的时候,链表里的每个Strategy对象都试图去满足这次调用。当某个策略(strategy)调用成功或者整个strategy链到达末尾的时候,这个过程结束。递归的时候,某个方法不断的调用它自己直到满足某个结束条件;对于职责链,某个方法调用它自己,进而(通过遍历strategy链表)调用它自己的不同实现,知道某个满足某个结束条件。所谓结束条件,要么是到达链表的末尾,要么是某一个strategy调用成功。对于第一种情况,得返回一个默认对象;如果不能提供一个默认结果,那就必须以某种方式告知调用者链表访问成功与否。
由于strategy链表中可能会有多于一个的方法满足要求,所以职责链似乎有点专家系统的味道。由于这一系列Strategy实际上是一个链表,它可以被动态创建,所以你也可以把职责链想象成更一般化的,动态构建的switch语句。
在GoF的书中,有很多关于如何把职责链创建成链表的讨论。但是,仔细看看这个模式你就会发现如何管理链表实际上并不重要;那只是一个实现上的细节。因为GoF那本书是在标准模板库(STL)被大多数C++编译器支持之前写的,他们之所以要讨论链表的原因是(1)没有现成的链表类,所以他们得自己写一个(2)学术界常常将数据结构作为一项基本的技能来讲授,而且GoF那时候可能还没意识到数据结构应该成为编程语言的标准工具。我的主张是,把职责链作为链表来实现对于问题的解决没有任何裨益,它可以简单的通过使用标准的java List来实现,下面的例子会说明这一点。更进一步,你会看到我还费了些劲把链表管理的部分从不同Strategy的实现里分离出来了,从而使这部分代码更易于重用。
前面章节StategyPattern.java那个例子里,很可能你需要的是自动找到一个解决问题的方案。职责链通过另外一种方法达到这种效果,它把一系列Strategy对象放到链表里,并提供一种机制让它自动遍历链表的每一个节点。
//: 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源代码的工具,它通过尝试不同的方法来分行。注意,普通代码和注释可能需要区别对待,这可能需要实现一个职责树Tree of Responsibility. 还需注意这种方法和Composite模式之间的相似性;或许这种技术更一般的描述应该叫做:组合策略(Composite of Strategies)。
目录
本文地址:http://com.8s8s.com/it/it28028.htm