分解共同性(Factoring Commonality)
应用“一次且只能有一次” 原则产生最基本的模式,将变化的那部分代码放到方法里。
这可以用两种方法来表达:
策略(Strategy)模式:运行时刻选择算法(choosing the algorithm at run-time)
另外,Strategy模式还可以添加一个“上下文(context)”,这个context可以是一个代理类(surrogate class),用来控制对某个特定strategy对象的选择和使用。
//: strategy:StrategyPattern.java
package strategy;
import com.bruceeckel.util.*; // Arrays2.toString()
import junit.framework.*;
// The strategy interface:
interface FindMinima {
// Line is a sequence of points:
double[] algorithm(double[] line);
}
// The various strategies:
class LeastSquares implements FindMinima {
public double[] algorithm(double[] line) {
return new double[] { 1.1, 2.2 }; // Dummy
}
}
class NewtonsMethod implements FindMinima {
public double[] algorithm(double[] line) {
return new double[] { 3.3, 4.4 }; // Dummy
}
}
class Bisection implements FindMinima {
public double[] algorithm(double[] line) {
return new double[] { 5.5, 6.6 }; // Dummy
}
}
class ConjugateGradient implements FindMinima {
public double[] algorithm(double[] line) {
return new double[] { 3.3, 4.4 }; // Dummy
}
}
// The "Context" controls the strategy:
class MinimaSolver {
private FindMinima strategy;
public MinimaSolver(FindMinima strat) {
strategy = strat;
}
double[] minima(double[] line) {
return strategy.algorithm(line);
}
void changeAlgorithm(FindMinima newAlgorithm) {
strategy = newAlgorithm;
}
}
public class StrategyPattern extends TestCase {
MinimaSolver solver =
new MinimaSolver(new LeastSquares());
double[] line = {
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(solver.minima(line)));
solver.changeAlgorithm(new Bisection());
System.out.println(
Arrays2.toString(solver.minima(line)));
}
public static void main(String args[]) {
junit.textui.TestRunner.run(StrategyPattern.class);
}
} ///:~
请注意模板方法(template method)模式和strategy模式的相似性-template method最显著的特征是它有多个方法需要调用, 它是分段的实现某个功能。但是,这并不是说strategy对象就不能有多个方法调用;考虑Shalloway给出的的订单处理系统的例子,每一个strategy包含不同的国别信息。(此处的Shalloway指《Design Patterns Explained》的作者Alan Shalloway)
JDK里Strategy模式的例子:comparator objects.
Policy模式:泛化的strategy模式
尽管GoF说Policy模式只是Strategy模式的一个别名,但是他们给出的Strategy模式的例子隐含的假定了strategy对象只使用一个方法――也就是说假定你已经把可能变化的算法拆成了一段单独的代码。
其他人(指 Shalloway, Design Patterns Explained, and Alexandrescu, Advanced C++ Design )用Policy模式来表示一个对象有多个方法的情况,对于不同的类,这些方法可能相互独立变化。相对于(Strategy模式) 只能有一个方法而言,Policy模式具有更大的灵活性。
例如,当某个产品需要被运送到不同国家的时候,与该产品相关的海运政策(shipping policy)可以被用来说明一些与运输有关的问题。这些问题可能包括运输方式,如何计算邮资或者运费,客户需求以及费用,特殊处理的费用等等。所有这些东西可能会互不相干的变化,而且很重要的一点是在运输过程中你可能会在不同地点(points)需要上述信息。(这段翻的不好,有些外贸专业词汇不懂 )
通常情况下,把Strategy模式和Policy模式区别开来是很有好处的,用Strategy模式处理一个方法(变化)的情况,而用Policy模式处理多个方法。
模板方法(Template method)
应用程序框架使你可以从一个或者一系列类继承下来,进而创建一个新的应用程序,你可以重用既有类的大多数代码并且按照你自己的需要重载其中的某些方法,从而实现应用程序的定制。Template Method是应用程序框架的一个基本概念,它通常隐藏在(框架)背后,通过调用基类的一组方法(有些方法你可能已经重载过了)来驱动应用程序。
比如,当创建一个applet的时候你就已经在使用应用程序框架了:你从Japplet继承下来,然后重载init()方法。Applet机制(实际上就是template method)完成剩下的工作,比如屏幕显示,处理事件循环,调整大小等等。
Template Method的一个重要特征是:它是在基类里定义的,而且不能够被(派生类)更改。有时候它是私有方法(private method),但实际上它经常被声明为final。它通过调用其它的基类方法(重载过的)来工作,但它经常是作为初始化过程的一部分被调用的,这样就没必要让客户端程序员能够直接调用它了。
//: templatemethod:TemplateMethod.java
// Simple demonstration of Template Method.
package templatemethod;
import junit.framework.*;
abstract class ApplicationFramework {
public ApplicationFramework() {
templateMethod(); // Dangerous!
}
abstract void customize1();
abstract void customize2();
final void templateMethod() {
for(int i = 0; i < 5; i++) {
customize1();
customize2();
}
}
}
// Create a new "application":
class MyApp extends ApplicationFramework {
void customize1() {
System.out.print("Hello ");
}
void customize2() {
System.out.println("World!");
}
}
public class TemplateMethod extends TestCase {
MyApp app = new MyApp();
public void test() {
// The MyApp constructor does all the work.
// This just makes sure it will complete
// without throwing an exception.
}
public static void main(String args[]) {
junit.textui.TestRunner.run(TemplateMethod.class);
}
} ///:~
基类的构造函数负责完成必要的初始化和启动应用程序“引擎”(template method)(在一个图形用户界面(GUI)程序里,这个“引擎”通常是“主事件循环(main event loop)”)。客户端程序员只需简单的提供customize1( ) 和 customize2( )方法的定义,整个程序就可以跑起来了。
练习
1. 写一个框架,从命令行读入一系列文件名。打开除最后一个文件的所有文件,用于读入;打开最后一个文件用于写入。框架用待定的策略(policy)处理输入文件,并将输出结果写入到最后一个文件。继承这个框架,定制两个不同的应用程序。
a. 把每个(输入)文件的文本转换为大写字母。
b. 用第一个文件给出的单词搜索(输入)文件。