25-Patterns
第二十五章 设计模式
概念
最初,你可以将模式视为解决特定类问题的一种特别巧妙且有深刻见解的方法。这就像前辈已经从所有角度去解决问题,并提出了最通用,最灵活的解决方案。问题可能是你之前看到并解决过的问题,但你的解决方案可能没有你在模式中体现的那种完整性。
虽然它们被称为“设计模式”,但它们实际上并不与设计领域相关联。模式似乎与传统的分析、设计和实现的思维方式不同。相反,模式在程序中体现了一个完整的思想,因此它有时会出现在分析阶段或高级设计阶段。因为模式在代码中有一个直接的实现,所以你可能不会期望模式在低级设计或实现之前出现
模式的基本概念也可以看作是程序设计的基本概念
将易变的事物与不变的事物分开
另一种方法是,一旦你发现程序的某些部分可能因某种原因而发生变化,你要保持这些变化不会引起整个代码中其他变化。 如果代码更容易理解,那么维护起来会更容易。
通常,开发一个优雅且易维护设计中最困难的部分是发现我称之为变化的载体(也就是最易改变的地方
因此,设计模式的目标是隔离代码中的更改。 如果以这种方式去看,你已经在本书中看到了设计模式。 例如,继承可以被认为是一种设计模式(虽然是由编译器实现的
你还看到了设计模式中出现的另一种模式:迭代器(
即使模式是非常有用的,但有些人断言:
设计模式代表语言的失败。
这是一个非常重要的见解,因为一个模式在
我认为“语言失败”这个观点是有道理的,但是我也认为这个观点过于简单化。如果你试图解决一个特定的问题,而你使用的语言没有直接提供支持你使用的技巧,你可以说这个是语言的失败。但是,你使用特定的技巧的频率的是多少呢?也许平衡是对的:当你使用特定的技巧的时候,你必须付出更多的努力,但是你又没有足够的理由去使得语言支持这个技术。另一方面,没有语言的支持,使用这种技术常常会很混乱,但是在语言支持下,你可能会改变编程方式(例如,
单例模式
也许单例模式是最简单的设计模式,它是一种提供一个且只有一个对象实例的方法。这在
// patterns/SingletonPattern.java
interface Resource {
int getValue();
void setValue(int x);
}
/*
* 由于这不是从Cloneable基类继承而且没有添加可克隆性,
* 因此将其设置为final可防止通过继承添加可克隆性。
* 这也实现了线程安全的延迟初始化:
*/
final class Singleton {
private static final class ResourceImpl implements Resource {
private int i;
private ResourceImpl(int i) {
this.i = i;
}
public synchronized int getValue() {
return i;
}
public synchronized void setValue(int x) {
i = x;
}
}
private static class ResourceHolder {
private static Resource resource = new ResourceImpl(47);
}
public static Resource getResource() {
return ResourceHolder.resource;
}
}
public class SingletonPattern {
public static void main(String[] args) {
Resource r = Singleton.getResource();
System.out.println(r.getValue());
Resource s2 = Singleton.getResource();
s2.setValue(9);
System.out.println(r.getValue());
try {
// 不能这么做,会发生:compile-time error(编译时错误).
// Singleton s3 = (Singleton)s2.clone();
} catch(Exception e) {
throw new RuntimeException(e);
}
}
} /* Output: 47 9 */
创建单例的关键是防止客户端程序员直接创建对象。 在这里,这是通过在
此时,你将决定如何创建对象。在这里,它是按需创建的,在第一次访问的时候创建。 该对象是私有的,只能通过
懒惰地创建对象的原因是它嵌套的私有类
模式分类
“设计模式”一书讨论了
-
创建型:如何创建对象。 这通常涉及隔离对象创建的细节,这样你的代码就不依赖于具体的对象的类型,因此在添加新类型的对象时不会更改。单例模式(Singleton)被归类为创作模式,本章稍后你将看到
Factory Method 的示例。 -
构造型:设计对象以满足特定的项目约束。它们处理对象与其他对象连接的方式,以确保系统中的更改不需要更改这些连接。
-
行为型:处理程序中特定类型的操作的对象。这些封装要执行的过程,例如解释语言、实现请求、遍历序列
( 如在迭代器中) 或实现算法。本章包含观察者和访问者模式的例子。
《设计模式》一书中每个设计模式都有单独的一个章节,每个章节都有一个或者多个例子,通常使用
构建应用程序框架
应用程序框架允许您从一个类或一组类开始,创建一个新的应用程序,重用现有类中的大部分代码,并根据需要覆盖一个或多个方法来定制应用程序。
模板方法模式
应用程序框架中的一个基本概念是模板方法模式,它通常隐藏在底层,通过调用基类中的各种方法来驱动应用程序
模板方法模式的一个重要特性是它是在基类中定义的,并且不能更改。它有时是一个
// patterns/TemplateMethod.java
// Simple demonstration of Template Method
abstract class ApplicationFramework {
ApplicationFramework() {
templateMethod();
}
abstract void customize1();
abstract void customize2();
// "private" means automatically "final":
private void templateMethod() {
IntStream.range(0, 5).forEach(
n -> {
customize1();
customize2();
});
}
}
// Create a new "application":
class MyApp extends ApplicationFramework {
@Override
void customize1() {
System.out.print("Hello ");
}
@Override
void customize2() {
System.out.println("World!");
}
}
public class TemplateMethod {
public static void main(String[] args) {
new MyApp();
}
}
/* Output:
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
*/
基类构造函数负责执行必要的初始化,然后启动运行应用程序的“engine”
面向实现
代理模式和桥接模式都提供了在代码中使用的代理类
在结构上,代理模式和桥接模式的区别很简单
代理模式
如果我们按照上面的关系图实现,它看起来是这样的
// patterns/ProxyDemo.java
// Simple demonstration of the Proxy pattern
interface ProxyBase {
void f();
void g();
void h();
}
class Proxy implements ProxyBase {
private ProxyBase implementation;
Proxy() {
implementation = new Implementation();
}
// Pass method calls to the implementation:
@Override
public void f() { implementation.f(); }
@Override
public void g() { implementation.g(); }
@Override
public void h() { implementation.h(); }
}
class Implementation implements ProxyBase {
public void f() {
System.out.println("Implementation.f()");
}
public void g() {
System.out.println("Implementation.g()");
}
public void h() {
System.out.println("Implementation.h()");
}
}
public class ProxyDemo {
public static void main(String[] args) {
Proxy p = new Proxy();
p.f();
p.g();
p.h();
}
}
/*
Output:
Implementation.f()
Implementation.g()
Implementation.h()
*/
具体实现不需要与代理对象具有相同的接口
状态模式
状态模式向代理对象添加了更多的实现,以及在代理对象的生命周期内从一个实现切换到另一种实现的方法
// patterns/StateDemo.java // Simple demonstration of the State pattern
interface StateBase {
void f();
void g();
void h();
void changeImp(StateBase newImp);
}
class State implements StateBase {
private StateBase implementation;
State(StateBase imp) {
implementation = imp;
}
@Override
public void changeImp(StateBase newImp) {
implementation = newImp;
}
// Pass method calls to the implementation:
@Override
public void f() {
implementation.f();
}
@Override
public void g() {
implementation.g();
}
@Override
public void h() {
implementation.h();
}
}
class Implementation1 implements StateBase {
@Override
public void f() {
System.out.println("Implementation1.f()");
}
@Override
public void g() {
System.out.println("Implementation1.g()");
}
@Override
public void h() {
System.out.println("Implementation1.h()");
}
@Override
public void changeImp(StateBase newImp) {
}
}
class Implementation2 implements StateBase {
@Override
public void f() {
System.out.println("Implementation2.f()");
}
@Override
public void g() {
System.out.println("Implementation2.g()");
}
@Override
public void h() {
System.out.println("Implementation2.h()");
}
@Override
public void changeImp(StateBase newImp) {
}
}
public class StateDemo {
static void test(StateBase b) {
b.f();
b.g();
b.h();
}
public static void main(String[] args) {
StateBase b =
new State(new Implementation1());
test(b);
b.changeImp(new Implementation2());
test(b);
}
}
/* Output:
Implementation1.f()
Implementation1.g()
Implementation1.h()
Implementation2.f()
Implementation2.g()
Implementation2.h()
*/
在
-
远程代理。它在不同的地址空间中代理对象。远程方法调用
(RMI) 编译器rmic 会自动为您创建一个远程代理。 -
虚拟代理。这提供了“懒加载”来根据需要创建“昂贵”的对象。
-
保护代理。当您希望对代理对象有权限访问控制时使用。
-
智能引用。要在被代理的对象被访问时添加其他操作。例如,跟踪特定对象的引用数量,来实现写时复制用法,和防止对象别名。一个更简单的例子是跟踪特定方法的调用数量。您可以将
Java 引用视为一种保护代理,因为它控制在堆上实例对象的访问( 例如,确保不使用空引用) 。
在设计模式中,代理模式和桥接模式并不是相互关联的,因为它们被赋予
状态机
桥接模式允许程序员更改实现,状态机利用一个结构来自动地将实现更改到下一个。当前实现表示系统所处的状态,系统在不同状态下的行为不同
// patterns/state/StateMachineDemo.java
// The StateMachine pattern and Template method
// {java patterns.state.StateMachineDemo}
package patterns.state;
import onjava.Nap;
interface State {
void run();
}
abstract class StateMachine {
protected State currentState;
protected abstract boolean changeState();
// Template method:
protected final void runAll() {
while (changeState()) // Customizable
currentState.run();
}
}
// A different subclass for each state:
class Wash implements State {
@Override
public void run() {
System.out.println("Washing");
new Nap(0.5);
}
}
class Spin implements State {
@Override
public void run() {
System.out.println("Spinning");
new Nap(0.5);
}
}
class Rinse implements State {
@Override
public void run() {
System.out.println("Rinsing");
new Nap(0.5);
}
}
class Washer extends StateMachine {
private int i = 0;
// The state table:
private State[] states = {
new Wash(), new Spin(),
new Rinse(), new Spin(),
};
Washer() {
runAll();
}
@Override
public boolean changeState() {
if (i < states.length) {
// Change the state by setting the
// surrogate reference to a new object:
currentState = states[i++];
return true;
} else
return false;
}
}
public class StateMachineDemo {
public static void main(String[] args) {
new Washer();
}
}
/* Output:
Washing
Spinning
Rinsing
Spinning
*/
在这里,控制状态的类
工厂模式
当你发现必须将新类型添加到系统中时,合理的第一步是使用多态性为这些新类型创建一个通用接口。这会将你系统中的其余代码与要添加的特定类型的信息分开,使得可以在不改变现有代码的情况下添加新类型……或者看起来如此。起初,在这种设计中,似乎你必须更改代码的唯一地方就是你继承新类型的地方,但这并不是完全正确的。 你仍然必须创建新类型的对象,并且在创建时必须指定要使用的确切构造器。因此,如果创建对象的代码分布在整个应用程序中,那么在添加新类型时,你将遇到相同的问题——你仍然必须追查你代码中新类型碍事的所有地方。恰好是类型的创建碍事,而不是类型的使用(通过多态处理
解决方案是强制对象的创建都通过通用工厂进行,而不是允许创建代码在整个系统中传播。 如果你程序中的所有代码都必须执行通过该工厂创建你的一个对象,那么在添加新类时只需要修改工厂即可。
由于每个面向对象的程序都会创建对象,并且很可能会通过添加新类型来扩展程序,因此工厂是最通用的设计模式之一。
举例来说,让我们重新看一下
// patterns/shapes/BadShapeCreation.java package patterns.shapes;
public class BadShapeCreation extends RuntimeException {
public BadShapeCreation(String msg) {
super(msg);
}
}
接下来,是一个
// patterns/shapes/Shape.java
package patterns.shapes;
public class Shape {
private static int counter = 0;
private int id = counter++;
@Override
public String toString(){
return getClass().getSimpleName() + "[" + id + "]";
}
public void draw() {
System.out.println(this + " draw");
}
public void erase() {
System.out.println(this + " erase");
}
}
该类自动为每一个id
。
toString()
使用运行期信息来发现特定的
现在我们能很快创建一些
// patterns/shapes/Circle.java
package patterns.shapes;
public class Circle extends Shape {}
// patterns/shapes/Square.java
package patterns.shapes;
public class Square extends Shape {}
// patterns/shapes/Triangle.java
package patterns.shapes;
public class Triangle extends Shape {}
工厂是具有能够创建对象的方法的类。 我们有几个示例版本,因此我们将定义一个接口:
// patterns/shapes/FactoryMethod.java
package patterns.shapes;
public interface FactoryMethod {
Shape create(String type);
}
create()
接收一个参数,这个参数使其决定要创建哪一种String
,但是它其实可以是任何数据集合。对象的初始化数据(这里是字符串)可能来自系统外部。 这个例子将测试工厂:
// patterns/shapes/FactoryTest.java
package patterns.shapes;
import java.util.stream.*;
public class FactoryTest {
public static void test(FactoryMethod factory) {
Stream.of("Circle", "Square", "Triangle",
"Square", "Circle", "Circle", "Triangle")
.map(factory::create)
.peek(Shape::draw)
.peek(Shape::erase)
.count(); // Terminal operation
}
}
在主函数main()
里,要记住除非你在最后使用了一个终结操作,否则count()
的值被丢弃了。
创建工厂的一种方法是显式创建每种类型:
// patterns/ShapeFactory1.java
// A simple static factory method
import java.util.*;
import java.util.stream.*;
import patterns.shapes.*;
public class ShapeFactory1 implements FactoryMethod {
public Shape create(String type) {
switch(type) {
case "Circle": return new Circle();
case "Square": return new Square();
case "Triangle": return new Triangle();
default: throw new BadShapeCreation(type);
}
}
public static void main(String[] args) {
FactoryTest.test(new ShapeFactory1());
}
}
输出结果:
Circle[0] draw
Circle[0] erase
Square[1] draw
Square[1] erase
Triangle[2] draw
Triangle[2] erase
Square[3] draw
Square[3] erase
Circle[4] draw
Circle[4] erase
Circle[5] draw
Circle[5] erase
Triangle[6] draw
Triangle[6] erase
create()
现在是添加新类型的
动态工厂
前面例子中的静态create()
方法强制所有创建操作都集中在一个位置,因此这是添加新类型的
// patterns/ShapeFactory2.java
import java.util.*;
import java.lang.reflect.*;
import java.util.stream.*;
import patterns.shapes.*;
public class ShapeFactory2 implements FactoryMethod {
Map<String, Constructor> factories = new HashMap<>();
static Constructor load(String id) {
System.out.println("loading " + id);
try {
return Class.forName("patterns.shapes." + id)
.getConstructor();
} catch(ClassNotFoundException |
NoSuchMethodException e) {
throw new BadShapeCreation(id);
}
}
public Shape create(String id) {
try {
return (Shape)factories
.computeIfAbsent(id, ShapeFactory2::load)
.newInstance();
} catch(InstantiationException |
IllegalAccessException |
InvocationTargetException e) {
throw new BadShapeCreation(id);
}
}
public static void main(String[] args) {
FactoryTest.test(new ShapeFactory2());
}
}
输出结果:
loading Circle
Circle[0] draw
Circle[0] erase
loading Square
Square[1] draw
Square[1] erase
loading Triangle
Triangle[2] draw
Triangle[2] erase
Square[3] draw
Square[3] erase
Circle[4] draw
Circle[4] erase
Circle[5] draw
Circle[5] erase
Triangle[6] draw
Triangle[6] erase
和之前一样,create()
方法基于你传递给它的newInstance()
创建新的
然而,当你开始运行程序时,工厂的map
为空。create()
使用map
的computeIfAbsent()
方法来查找构造器(如果该构造器已存在于map
中load()
计算出该构造器,并将其插入到map
中。 从输出中可以看到,每种特定类型的map
中检索它。
多态工厂
《设计模式》这本书强调指出,采用“工厂方法”模式的原因是可以从基本工厂中继承出不同类型的工厂。 再次修改示例,使工厂方法位于单独的类中:
// patterns/ShapeFactory3.java
// Polymorphic factory methods
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
import patterns.shapes.*;
interface PolymorphicFactory {
Shape create();
}
class RandomShapes implements Supplier<Shape> {
private final PolymorphicFactory[] factories;
private Random rand = new Random(42);
RandomShapes(PolymorphicFactory... factories){
this.factories = factories;
}
public Shape get() {
return factories[ rand.nextInt(factories.length)].create();
}
}
public class ShapeFactory3 {
public static void main(String[] args) {
RandomShapes rs = new RandomShapes(
Circle::new,
Square::new,
Triangle::new);
Stream.generate(rs)
.limit(6)
.peek(Shape::draw)
.peek(Shape::erase)
.count();
}
}
输出结果:
Triangle[0] draw
Triangle[0] erase
Circle[1] draw
Circle[1] erase
Circle[2] draw
Circle[2] erase
Triangle[3] draw
Triangle[3] erase
Circle[4] draw
Circle[4] erase
Square[5] draw
Square[5] erase
Stream.generate()
创建get()
方法随机获取此数组中一个对象的索引,并在结果上调用create()
以产生新的
鉴于
抽象工厂
抽象工厂模式看起来像我们之前所见的工厂对象,但拥有不是一个工厂方法而是几个工厂方法, 每个工厂方法都会创建不同种类的对象。 这个想法是在创建工厂对象时,你决定如何使用该工厂创建的所有对象。 《设计模式》中提供的示例实现了跨各种图形用户界面(GUI)的可移植性:你创建一个适合你正在使用的
// patterns/abstractfactory/GameEnvironment.java
// An example of the Abstract Factory pattern
// {java patterns.abstractfactory.GameEnvironment}
package patterns.abstractfactory;
import java.util.function.*;
interface Obstacle {
void action();
}
interface Player {
void interactWith(Obstacle o);
}
class Kitty implements Player {
@Override
public void interactWith(Obstacle ob) {
System.out.print("Kitty has encountered a ");
ob.action();
}
}
class KungFuGuy implements Player {
@Override
public void interactWith(Obstacle ob) {
System.out.print("KungFuGuy now battles a ");
ob.action();
}
}
class Puzzle implements Obstacle {
@Override
public void action() {
System.out.println("Puzzle");
}
}
class NastyWeapon implements Obstacle {
@Override
public void action() {
System.out.println("NastyWeapon");
}
}
// The Abstract Factory:
class GameElementFactory {
Supplier<Player> player;
Supplier<Obstacle> obstacle;
}
// Concrete factories:
class KittiesAndPuzzles extends GameElementFactory {
KittiesAndPuzzles() {
player = Kitty::new;
obstacle = Puzzle::new;
}
}
class KillAndDismember extends GameElementFactory {
KillAndDismember() {
player = KungFuGuy::new;
obstacle = NastyWeapon::new;
}
}
public class GameEnvironment {
private Player p;
private Obstacle ob;
public GameEnvironment(GameElementFactory factory) {
p = factory.player.get();
ob = factory.obstacle.get();
}
public void play() {
p.interactWith(ob);
}
public static void main(String[] args) {
GameElementFactory kp = new KittiesAndPuzzles(), kd = new KillAndDismember();
GameEnvironment g1 = new GameEnvironment(kp), g2 = new GameEnvironment(kd);
g1.play();
g2.play();
}
}
输出结果:
Kitty has encountered a Puzzle
KungFuGuy now battles a NastyWeapon
在这种环境中,
函数对象
一个 函数对象 封装了一个函数。其特点就是将被调用函数的选择与那个函数被调用的位置进行解耦。
《设计模式》 中也提到了这个术语,但是没有使用。然而,函数对象 的话题却在那本书的很多模式中被反复论及。
命令模式
从最直观的角度来看,命令模式 就是一个函数对象:一个作为对象的函数。我们可以将 函数对象 作为参数传递给其他方法或者对象,来执行特定的操作。
在
// patterns/CommandPattern.java
import java.util.*;
public class CommandPattern {
public static void main(String[] args) {
List<Runnable> macro = Arrays.asList(
() -> System.out.print("Hello "),
() -> System.out.print("World! "),
() -> System.out.print("I'm the command pattern!")
);
macro.forEach(Runnable::run);
}
}
/* Output:
Hello World! I'm the command pattern!
*/
命令模式 的主要特点是允许向一个方法或者对象传递一个想要的动作。在上面的例子中,这个对象就是
《设计模式》 认为“命令模式是回调的面向对象的替代品”。尽管如此,我认为
策略模式
策略模式 看起来像是从同一个基类继承而来的一系列 命令 类。但是仔细查看 命令模式,你就会发现它也具有同样的结构:一系列分层次的 函数对象。不同之处在于,这些函数对象的用法和策略模式不同。就像前面的 io/DirList.java
那个例子,使用 命令 是为了解决特定问题 – 从一个列表中选择文件
另外,策略模式 还可以添加一个“上下文(context)”,这个上下文(context)可以是一个代理类(surrogate class
// patterns/strategy/StrategyPattern.java
// {java patterns.strategy.StrategyPattern}
package patterns.strategy;
import java.util.function.*;
import java.util.*;
// The common strategy base type:
class FindMinima {
Function<List<Double>, List<Double>> algorithm;
}
// The various strategies:
class LeastSquares extends FindMinima {
LeastSquares() {
// Line is a sequence of points (Dummy data):
algorithm = (line) -> Arrays.asList(1.1, 2.2);
}
}
class Perturbation extends FindMinima {
Perturbation() {
algorithm = (line) -> Arrays.asList(3.3, 4.4);
}
}
class Bisection extends FindMinima {
Bisection() {
algorithm = (line) -> Arrays.asList(5.5, 6.6);
}
}
// The "Context" controls the strategy:
class MinimaSolver {
private FindMinima strategy;
MinimaSolver(FindMinima strat) {
strategy = strat;
}
List<Double> minima(List<Double> line) {
return strategy.algorithm.apply(line);
}
void changeAlgorithm(FindMinima newAlgorithm) {
strategy = newAlgorithm;
}
}
public class StrategyPattern {
public static void main(String[] args) {
MinimaSolver solver =
new MinimaSolver(new LeastSquares());
List<Double> line = Arrays.asList(
1.0, 2.0, 1.0, 2.0, -1.0,
3.0, 4.0, 5.0, 4.0 );
System.out.println(solver.minima(line));
solver.changeAlgorithm(new Bisection());
System.out.println(solver.minima(line));
}
}
/* Output:
[1.1, 2.2]
[5.5, 6.6]
*/
MinimaSolver
中的 changeAlgorithm()
方法将一个不同的策略插入到了 私有
域 strategy
中,这使得在调用 minima()
方法时,可以使用新的策略。
我们可以通过将上下文注入到 FindMinima
中来简化我们的解决方法。
// patterns/strategy/StrategyPattern2.java // {java patterns.strategy.StrategyPattern2}
package patterns.strategy;
import java.util.function.*;
import java.util.*;
// "Context" is now incorporated:
class FindMinima2 {
Function<List<Double>, List<Double>> algorithm;
FindMinima2() { leastSquares(); } // default
// The various strategies:
void leastSquares() {
algorithm = (line) -> Arrays.asList(1.1, 2.2);
}
void perturbation() {
algorithm = (line) -> Arrays.asList(3.3, 4.4);
}
void bisection() {
algorithm = (line) -> Arrays.asList(5.5, 6.6);
}
List<Double> minima(List<Double> line) {
return algorithm.apply(line);
}
}
public class StrategyPattern2 {
public static void main(String[] args) {
FindMinima2 solver = new FindMinima2();
List<Double> line = Arrays.asList(
1.0, 2.0, 1.0, 2.0, -1.0,
3.0, 4.0, 5.0, 4.0 );
System.out.println(solver.minima(line));
solver.bisection();
System.out.println(solver.minima(line));
}
}
/* Output:
[1.1, 2.2]
[5.5, 6.6]
*/
FindMinima2
封装了不同的算法,也包含了“上下文”(Context
责任链模式
责任链模式 也许可以被看作一个使用了 策略 对象的“递归的动态一般化”。此时我们进行一次调用,在一个链序列中的每个策略都试图满足这个调用。这个过程直到有一个策略成功满足该调用或者到达链序列的末尾才结束。在递归方法中,一个方法将反复调用它自身直至达到某个终止条件;使用责任链,一个方法会调用相同的基类方法(拥有不同的实现
除了调用某个方法来满足某个请求以外,链中的多个方法都有机会满足这个请求,因此它有点专家系统的意味。由于责任链实际上就是一个链表,它能够动态创建,因此它可以看作是一个更一般的动态构建的 switch
语句。
在上面的 StrategyPattern.java
例子中,我们可能想自动发现一个解决方法。而 责任链 就可以达到这个目的:
// patterns/chain/ChainOfResponsibility.java
// Using the Functional interface
// {java patterns.chain.ChainOfResponsibility}
package patterns.chain;
import java.util.*;
import java.util.function.*;
class Result {
boolean success;
List<Double> line;
Result(List<Double> data) {
success = true;
line = data;
}
Result() {
success = false;
line = Collections.<Double>emptyList();
}
}
class Fail extends Result {}
interface Algorithm {
Result algorithm(List<Double> line);
}
class FindMinima {
public static Result leastSquares(List<Double> line) {
System.out.println("LeastSquares.algorithm");
boolean weSucceed = false;
if(weSucceed) // Actual test/calculation here
return new Result(Arrays.asList(1.1, 2.2));
else // Try the next one in the chain:
return new Fail();
}
public static Result perturbation(List<Double> line) {
System.out.println("Perturbation.algorithm");
boolean weSucceed = false;
if(weSucceed) // Actual test/calculation here
return new Result(Arrays.asList(3.3, 4.4));
else
return new Fail();
}
public static Result bisection(List<Double> line) {
System.out.println("Bisection.algorithm");
boolean weSucceed = true;
if(weSucceed) // Actual test/calculation here
return new Result(Arrays.asList(5.5, 6.6));
else
return new Fail();
}
static List<Function<List<Double>, Result>>
algorithms = Arrays.asList(
FindMinima::leastSquares,
FindMinima::perturbation,
FindMinima::bisection
);
public static Result minima(List<Double> line) {
for(Function<List<Double>, Result> alg :
algorithms) {
Result result = alg.apply(line);
if(result.success)
return result;
}
return new Fail();
}
}
public class ChainOfResponsibility {
public static void main(String[] args) {
FindMinima solver = new FindMinima();
List<Double> line = Arrays.asList(
1.0, 2.0, 1.0, 2.0, -1.0,
3.0, 4.0, 5.0, 4.0);
Result result = solver.minima(line);
if(result.success)
System.out.println(result.line);
else
System.out.println("No algorithm found");
}
}
/* Output:
LeastSquares.algorithm
Perturbation.algorithm
Bisection.algorithm
[5.5, 6.6]
*/
我们从定义一个 Result
类开始,这个类包含一个 success
标志,因此接收者就可以知道算法是否成功执行,而 line
变量保存了真实的数据。当算法执行失败时, Fail
类可以作为返回值。要注意的是,当算法执行失败时,返回一个 Result
对象要比抛出一个异常更加合适,因为我们有时可能并不打算解决这个问题,而是希望程序继续执行下去。
每一个 Algorithm
接口的实现,都实现了不同的 algorithm()
方法。在 FindMinama
中,将会创建一个算法的列表(这就是所谓的“链”minima()
方法只是遍历这个列表,然后找到能够成功执行的算法而已。
改变接口
有时候我们需要解决的问题很简单,仅仅是“我没有需要的接口”而已。有两种设计模式用来解决这个问题:适配器模式 接受一种类型并且提供一个对其他类型的接口。外观模式 为一组类创建了一个接口,这样做只是为了提供一种更方便的方法来处理库或资源。
适配器模式(Adapter)
当我们手头有某个类,而我们需要的却是另外一个类,我们就可以通过 适配器模式 来解决问题。唯一需要做的就是产生出我们需要的那个类,有许多种方法可以完成这种适配。
// patterns/adapt/Adapter.java
// Variations on the Adapter pattern
// {java patterns.adapt.Adapter}
package patterns.adapt;
class WhatIHave {
public void g() {}
public void h() {}
}
interface WhatIWant {
void f();
}
class ProxyAdapter implements WhatIWant {
WhatIHave whatIHave;
ProxyAdapter(WhatIHave wih) {
whatIHave = wih;
}
@Override
public void f() {
// Implement behavior using
// methods in WhatIHave:
whatIHave.g();
whatIHave.h();
}
}
class WhatIUse {
public void op(WhatIWant wiw) {
wiw.f();
}
}
// Approach 2: build adapter use into op():
class WhatIUse2 extends WhatIUse {
public void op(WhatIHave wih) {
new ProxyAdapter(wih).f();
}
}
// Approach 3: build adapter into WhatIHave:
class WhatIHave2 extends WhatIHave implements WhatIWant {
@Override
public void f() {
g();
h();
}
}
// Approach 4: use an inner class:
class WhatIHave3 extends WhatIHave {
private class InnerAdapter implements WhatIWant {
@Override
public void f() {
g();
h();
}
}
public WhatIWant whatIWant() {
return new InnerAdapter();
}
}
public class Adapter {
public static void main(String[] args) {
WhatIUse whatIUse = new WhatIUse();
WhatIHave whatIHave = new WhatIHave();
WhatIWant adapt= new ProxyAdapter(whatIHave);
whatIUse.op(adapt);
// Approach 2:
WhatIUse2 whatIUse2 = new WhatIUse2();
whatIUse2.op(whatIHave);
// Approach 3:
WhatIHave2 whatIHave2 = new WhatIHave2();
whatIUse.op(whatIHave2);
// Approach 4:
WhatIHave3 whatIHave3 = new WhatIHave3();
whatIUse.op(whatIHave3.whatIWant());
}
}
我想冒昧的借用一下术语“proxy”(代理
外观模式(Façade)
当我想方设法试图将需求初步(first-cut)转化成对象的时候,通常我使用的原则是:
“把所有丑陋的东西都隐藏到对象里去”。
基本上说,外观模式 干的就是这个事情。如果我们有一堆让人头晕的类以及交互(Interactions
外观模式经常被实现为一个符合单例模式(Singleton)的抽象工厂(abstract factory
// patterns/Facade.java
class A { A(int x) {} }
class B { B(long x) {} }
class C { C(double x) {} }
// Other classes that aren't exposed by the
// facade go here ...
public class Facade {
static A makeA(int x) { return new A(x); }
static B makeB(long x) { return new B(x); }
static C makeC(double x) { return new C(x); }
public static void main(String[] args) {
// The client programmer gets the objects
// by calling the static methods:
A a = Facade.makeA(1);
B b = Facade.makeB(1);
C c = Facade.makeC(1.0);
}
}
《设计模式》给出的例子并不是真正的 外观模式 ,而仅仅是一个类使用了其他的类而已。
包(Package)作为外观模式的变体
我感觉,外观模式 更倾向于“过程式的(procedural)”,也就是非面向对象的(non-object-oriented
其实,这也正是
公平起见
解释器:运行时的弹性
如果程序的用户需要更好的运行时弹性,例如创建脚本来增加需要的系统功能,你就能使用解释器设计模式。这个模式下,你可以创建一个语言解释器并将它嵌入你的程序内。
在开发程序的过程中,设计自己的语言并为它构建一个解释器是一件让人分心且耗时的事。最好的解决方案就是复用代码:使用一个已经构建好并被调试过的解释器。