外部化对象状态(Externalizing object state)
备忘录(Memento)
使用序列化创建一个可撤销的机制。复杂的交互(Complex interactions)
多重分发(Multiple dispatching)
当处理正在交互的多个类型,一个程序会变得特别凌乱。例如,考虑一个系统解析和执行一个数学表达式。你想要能够数值+数值,数值*数值等等,这里数值是数字家族对象的一个基本类。当时当你说a+b时,你确实不知道a或者b的类型,因此你怎能让他们正确的交互?
答案始于你开始可能没有考虑得某些东西:Java仅仅执行单一分发。这就是说,如果你执行多于一个未知类型的对象的操作,java能够调用动态绑定机制仅仅是那些类型中的一个。这样并没有解决问题,因此你结束手工探测一些类型并且有效的产生你自己的动态绑定行为。
方案被称作多重分发。记住这个机制能够发生仅仅通过成员函数调用,因此,如果你想要两路分发,就必须有两个成员函数调用:第一个确定第一个未知的类型,第二个确定第二哥未知的类型。一般来说,你将设置一个配置,这样一个单一成员函数调用过程多于一个动态成员函数调用,这样就确定过程中多于一个的类型。为了获得这个效果,你需要多于一个多态方法调用:你需要一个调用对每个分派。下面例子中的方法被调用compete( ) 和 eval( ),他们都是同样类型的成员函数。(这个例子中仅仅有两个分派,作为两路分派被参照)。如果你正在处理两个不同类型的交互层面,那么你必须有一个多态方法调用在每个层面上。
下面是个多重分派的例子:
//: multipledispatch:PaperScissorsRock.java
// Demonstration of multiple dispatching.
package multipledispatch;
import java.util.*;
import junit.framework.*;
// An enumeration type:
class Outcome {
private String name;
private Outcome(String name) { this.name = name; }
public final static Outcome
WIN = new Outcome("wins"),
LOSE = new Outcome("loses"),
DRAW = new Outcome("draws");
public String toString() { return name; }
}
interface Item {
Outcome compete(Item it);
Outcome eval(Paper p);
Outcome eval(Scissors s);
Outcome eval(Rock r);
}
class Paper implements Item {
public Outcome compete(Item it) { return it.eval(this); }
public Outcome eval(Paper p) { return Outcome.DRAW; }
public Outcome eval(Scissors s) { return Outcome.WIN; }
public Outcome eval(Rock r) { return Outcome.LOSE; }
public String toString() { return "Paper"; }
}
class Scissors implements Item {
public Outcome compete(Item it) { return it.eval(this); }
public Outcome eval(Paper p) { return Outcome.LOSE; }
public Outcome eval(Scissors s) { return Outcome.DRAW; }
public Outcome eval(Rock r) { return Outcome.WIN; }
public String toString() { return "Scissors"; }
}
class Rock implements Item {
public Outcome compete(Item it) { return it.eval(this); }
public Outcome eval(Paper p) { return Outcome.WIN; }
public Outcome eval(Scissors s) { return Outcome.LOSE; }
public Outcome eval(Rock r) { return Outcome.DRAW; }
public String toString() { return "Rock"; }
}
class ItemGenerator {
private static Random rand = new Random();
public static Item newItem() {
switch(rand.nextInt(3)) {
default:
case 0: return new Scissors();
case 1: return new Paper();
case 2: return new Rock();
}
}
}
class Compete {
public static void match(Item a, Item b) {
System.out.println(
a + " " + a.compete(b) + " vs. " + b);
}
}
public class PaperScissorsRock extends TestCase {
static int SIZE = 20;
public void test() {
for(int i = 0; i < SIZE; i++)
Compete.match(ItemGenerator.newItem(),
ItemGenerator.newItem());
}
public static void main(String args[]) {
junit.textui.TestRunner.run(PaperScissorsRock.class);
}
} ///:~
参观者(Visitor, a type of multiple dispatching)
假定你有一个固定的主要类的层次;也许它来自另外一个供应商并且你不可能修改它的层次结构。然而,你想要添加新的多态方法到哪个层次结构中去,这意味着正常的方式你不得不添加某些东西到基类的接口中去。因此两难的地方在于你需要添加方法到基类中去,但是你不能修改基类,应该怎么做呢?
解决这个问题的设计模式叫做“参观者”(《设计模式》一书中的最后一个),最后一段显示架构了两路分发的结构。
参观者模式允许你扩展主要类型的接口通过创建分离的参观者类型层次来虚拟依赖于主要类型的操作。主要类型的对象简单的“接收”参观者,然后调用观察者动态绑定的成员函数。
//: visitor:BeeAndFlowers.java
// Demonstration of "visitor" pattern.
package visitor;
import java.util.*;
import junit.framework.*;
interface Visitor {
void visit(Gladiolus g);
void visit(Runuculus r);
void visit(Chrysanthemum c);
}
// The Flower hierarchy cannot be changed:
interface Flower {
void accept(Visitor v);
}
class Gladiolus implements Flower {
public void accept(Visitor v) { v.visit(this);}
}
class Runuculus implements Flower {
public void accept(Visitor v) { v.visit(this);}
}
class Chrysanthemum implements Flower {
public void accept(Visitor v) { v.visit(this);}
}
// Add the ability to produce a string:
class StringVal implements Visitor {
String s;
public String toString() { return s; }
public void visit(Gladiolus g) {
s = "Gladiolus";
}
public void visit(Runuculus r) {
s = "Runuculus";
}
public void visit(Chrysanthemum c) {
s = "Chrysanthemum";
}
}
// Add the ability to do "Bee" activities:
class Bee implements Visitor {
public void visit(Gladiolus g) {
System.out.println("Bee and Gladiolus");
}
public void visit(Runuculus r) {
System.out.println("Bee and Runuculus");
}
public void visit(Chrysanthemum c) {
System.out.println("Bee and Chrysanthemum");
}
}
class FlowerGenerator {
private static Random rand = new Random();
public static Flower newFlower() {
switch(rand.nextInt(3)) {
default:
case 0: return new Gladiolus();
case 1: return new Runuculus();
case 2: return new Chrysanthemum();
}
}
}
public class BeeAndFlowers extends TestCase {
List flowers = new ArrayList();
public BeeAndFlowers() {
for(int i = 0; i < 10; i++)
flowers.add(FlowerGenerator.newFlower());
}
public void test() {
// It's almost as if I had a function to
// produce a Flower string representation:
StringVal sval = new StringVal();
Iterator it = flowers.iterator();
while(it.hasNext()) {
((Flower)it.next()).accept(sval);
System.out.println(sval);
}
// Perform "Bee" operation on all Flowers:
Bee bee = new Bee();
it = flowers.iterator();
while(it.hasNext())
((Flower)it.next()).accept(bee);
}
public static void main(String args[]) {
junit.textui.TestRunner.run(BeeAndFlowers.class);
}
} ///:~
练习Exercises
1. 创建一个业务模型环境,使用三种类型Inhabitant: Dwarf (为工程师), Elf (为市场人员)和 Troll (为管理者).创建一个叫 Project 类,它创建不同的Inhabitant并且让他们interact( ) 通过使用多路分发。
2. 修改上面的例子使交互更详细. 每一个 Inhabitant 能够随机的产生一个武器,使用getWeapon( ): Dwarf 使用 Jargon或者 Play, Elf 使用 InventFeature 或者 SellImaginaryProduct, Troll 使用 Edict 和 Schedule. 你不许决定武器输赢在每次交互中(如在PaperScissorsRock.java 中)。 添加一个 battle( ) 成员函数到项目中去,获取两个Inhabitants 并且匹配他们的每一个。创建一个meeting( ) 成员函数到Project 中,它创建Dwarf, Elf 和 Manager 组,并且组内相互战斗知道一组的成员失去了位置。这就是那些“赢家”。
1. 修改PaperScissorsRock.java ,使用一个查询表替换两路分派。最容易的方式是创建一个Maps的MAP, 使用每个Map的键值映射每个对象的类。你能想下面这样查找:((Map)map.get(o1.getClass())).get(o2.getClass())注意他是多么容易的重新配置系统。与硬编码来比什么时候才能恰当的使用这种方式?你能够创建一个系统语义上简化应用动态分派而非使用一个查询表?
4. 修改练习2,使用练习三中描述的查询表技巧。