连接不同类型(Connecting different types)
适配器(Adapter)
适配器(Adaper)接受一种类型,并为其它类型产生一个接口。当你手头有某个类,而你需要的却是另外一个类(When you’ve got this, and you need that),你可以通过Adapter来解决问题。唯一需要做的就是产生出你需要的那个类(that),有许多种方法可以完成这种配接。
//: adapter:SimpleAdapter.java
// "Object Adapter" from GoF diagram
package adapter;
import junit.framework.*;
class Target {
public void request() {}
}
class Adaptee {
public void specificRequest() {
System.out.println("Adaptee: SpecificRequest");
}
}
class Adapter extends Target {
private Adaptee adaptee;
public Adapter(Adaptee a) {
adaptee = a;
}
public void request() {
adaptee.specificRequest();
}
}
public class SimpleAdapter extends TestCase {
Adaptee a = new Adaptee();
Target t = new Adapter(a);
public void test() {
t.request();
}
public static void main(String args[]) {
junit.textui.TestRunner.run(SimpleAdapter.class);
}
} ///:~
//: adapter:AdapterVariations.java
// Variations on the Adapter pattern.
package adapter;
import junit.framework.*;
class WhatIHave {
public void g() {}
public void h() {}
}
interface WhatIWant {
void f();
}
class SurrogateAdapter implements WhatIWant {
WhatIHave whatIHave;
public SurrogateAdapter(WhatIHave wih) {
whatIHave = wih;
}
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 SurrogateAdapter(wih).f();
}
}
// Approach 3: build adapter into WhatIHave:
class WhatIHave2 extends WhatIHave
implements WhatIWant {
public void f() {
g();
h();
}
}
// Approach 4: use an inner class:
class WhatIHave3 extends WhatIHave {
private class InnerAdapter implements WhatIWant{
public void f() {
g();
h();
}
}
public WhatIWant whatIWant() {
return new InnerAdapter();
}
}
public class AdapterVariations extends TestCase {
WhatIUse whatIUse = new WhatIUse();
WhatIHave whatIHave = new WhatIHave();
WhatIWant adapt= new SurrogateAdapter(whatIHave);
WhatIUse2 whatIUse2 = new WhatIUse2();
WhatIHave2 whatIHave2 = new WhatIHave2();
WhatIHave3 whatIHave3 = new WhatIHave3();
public void test() {
whatIUse.op(adapt);
// Approach 2:
whatIUse2.op(whatIHave);
// Approach 3:
whatIUse.op(whatIHave2);
// Approach 4:
whatIUse.op(whatIHave3.whatIWant());
}
public static void main(String args[]) {
junit.textui.TestRunner.run(AdapterVariations.class);
}
} ///:~
我想冒昧的借用一下术语“proxy”,因为在《设计模式》里,他们坚持认为一个代理(proxy)必须拥有和它所代理的对象一模一样的接口。但是,如果把这两个词一起使用,叫做“代理适配器(proxy adapter)”,似乎更合理一些。
桥接(Bridge)
研究Bridge模式的过程中, 我发现它几乎是GoF描述的最差的一个模式。我开始下这个结论是在我读到Alan Shalloway’s的《Design Patterns Explained》的Bridge模式这一章的时候――他指出GoF关于Bridge模式的描述使他觉得一头雾水。
在一次会议上,我和两位与会者讨论过这个问题,这两位都写过关于设计模式的文章并且做过关于设计模式的演讲(包括Bridge模式)。这两次讨论使我对于Bridge模式的结构产生了完全不同的看法。
带着这些疑惑,我重新钻研GoF的著作,并且意识到上面那两位与会者的观点都与GoF书中不符。我还发现书中对于Bridge模式的描述实在是糟糕,描述Bridge模式通用结构的那张图根本不顶用,倒是描述Bridge模式实际例子的那个图还说的过去。你只要多看看那张图就明白Bridge模式到底是怎么一回事了。
审视Bridge模式的时候你会发现,它的一个重要作用就是经常可以作为辅助你书写代码的一种既定结构。我们可能会根据特定的情况在编译时或运行时选择使用不同的对象,Bridge模式的目的就是为了结构化你的代码,从而使得你可以很容易的添加新类型的前端(front-end)对象(这些前端对象是通过调用新类型的后端(back-end)对象的功能实现的)。这么以来,就可以对前端(front-end)和后端(back-end)互不干扰的进行修改。
前端(front-end)类之间可以拥有完全不同的接口,而且通常就是这样的。它们的共同之处是可以通过使用任意数量的后端(back-end)对象来实现自身的某些功能。后端对象的接口通常也不一样。后端对象之间唯一必须相同的一点是它们都得实现某种类似的功能--例如,一组采用不同方法实现的图形库,或者一系列不同的数据存储解决方案。
Bridge模式实际上就是一个组织代码的工具,它使得你可以添加任意数量的新的前端服务,而这些前端服务又可以通过把这些操作委托给任意数量的后端对象来实现。
通过使用Bridge模式,你可以避免(类)组合所带来的类的数目的爆炸省增长。但是别忘了Bridge模式所处理的一系列变化通常都是发生在编码阶段:当为了实现某个功能而必须处理越来越多的选项(options)时,Bridge模式可以使你的代码保持条例性。
下面这个例子的目的就是为了说明Bridge模式的结构(它实现了上面那张图):
//: bridge:BridgeStructure.java
// A demonstration of the structure and operation
// of the Bridge Pattern.
package bridge;
import junit.framework.*;
class Abstraction {
private Implementation implementation;
public Abstraction(Implementation imp) {
implementation = imp;
}
// Abstraction used by the various front-end
// objects in order to implement their
// different interfaces.
public void service1() {
// Implement this feature using some
// combination of back-end implementation:
implementation.facility1();
implementation.facility2();
}
public void service2() {
// Implement this feature using some other
// combination of back-end implementation:
implementation.facility2();
implementation.facility3();
}
public void service3() {
// Implement this feature using some other
// combination of back-end implementation:
implementation.facility1();
implementation.facility2();
implementation.facility4();
}
// For use by subclasses:
protected Implementation getImplementation() {
return implementation;
}
}
class ClientService1 extends Abstraction {
public ClientService1(Implementation imp) { super(imp); }
public void serviceA() {
service1();
service2();
}
public void serviceB() {
service3();
}
}
class ClientService2 extends Abstraction {
public ClientService2(Implementation imp) { super(imp); }
public void serviceC() {
service2();
service3();
}
public void serviceD() {
service1();
service3();
}
public void serviceE() {
getImplementation().facility3();
}
}
interface Implementation {
// The common implementation provided by the
// back-end objects, each in their own way.
void facility1();
void facility2();
void facility3();
void facility4();
}
class Library1 {
public void method1() {
System.out.println("Library1.method1()");
}
public void method2() {
System.out.println("Library1.method2()");
}
}
class Library2 {
public void operation1() {
System.out.println("Library2.operation1()");
}
public void operation2() {
System.out.println("Library2.operation2()");
}
public void operation3() {
System.out.println("Library2.operation3()");
}
}
class Implementation1 implements Implementation {
// Each facility delegates to a different library
// in order to fulfill the obligations.
private Library1 delegate = new Library1();
public void facility1() {
System.out.println("Implementation1.facility1");
delegate.method1();
}
public void facility2() {
System.out.println("Implementation1.facility2");
delegate.method2();
}
public void facility3() {
System.out.println("Implementation1.facility3");
delegate.method2();
delegate.method1();
}
public void facility4() {
System.out.println("Implementation1.facility4");
delegate.method1();
}
}
class Implementation2 implements Implementation {
private Library2 delegate = new Library2();
public void facility1() {
System.out.println("Implementation2.facility1");
delegate.operation1();
}
public void facility2() {
System.out.println("Implementation2.facility2");
delegate.operation2();
}
public void facility3() {
System.out.println("Implementation2.facility3");
delegate.operation3();
}
public void facility4() {
System.out.println("Implementation2.facility4");
delegate.operation1();
}
}
public class BridgeStructure extends TestCase {
public void test1() {
// Here, the implementation is determined by
// the client at creation time:
ClientService1 cs1 =
new ClientService1(new Implementation1());
cs1.serviceA();
cs1.serviceB();
}
public void test2() {
ClientService1 cs1 =
new ClientService1(new Implementation2());
cs1.serviceA();
cs1.serviceB();
}
public void test3() {
ClientService2 cs2 =
new ClientService2(new Implementation1());
cs2.serviceC();
cs2.serviceD();
cs2.serviceE();
}
public void test4() {
ClientService2 cs2 =
new ClientService2(new Implementation2());
cs2.serviceC();
cs2.serviceD();
cs2.serviceE();
}
public static void main(String[] args) {
junit.textui.TestRunner.run(BridgeStructure.class);
}
} ///:~
前端基类(front-end base class)根据后端基类(back-end base class)所(声明)的方法,提供了用以实现前端派生类(front-end derived class)所需的操作. 这样以来,任何后端派生类都可以被用来实现前端类所需要的操作。 注意到桥接(bridging)是分步完成的,每一步都提供了一个抽象层。上面的例子里,Implementation被定义为一个接口是为了强调所有实际功能都是通过后端派生类来实现的,而后端基类不参与实现。
后端派生类通过委托给(delegating)别的对象(本例中,是Library1 和 Library2)来实现基类所定义的操作, 这些接受委托的对象通常拥有完全不同的接口,但是又提供几乎相同的功能(这也是Bridge模式的一个重要目的)。一言以蔽之,每一个后端类都可以看作是某一个库(library)或者工具的适配器(adapter),这些库或者工具用不同的方法实现预期的功能。
练习
1. 修改BridgeStructure.java,写一个factroy用来(动态)选择(后端对象)的实现。
2. 修改BridgeStructure.java,针对前端对象,使用委托(delegation)而不是继承。这么做有什么好处和坏处?
3. 写一个Bridge模式的例子,要求用到关联数组(associative array)。要能够通过传入键值对象(key object)取出元素。在构造函数里从一个数组读入并初始化一系列的“键-值”对(key-value pairs)。读数据的时候,用数组(array)作为后端实现,但是写入key-value pair的时候,后端实现要切换到map.
4. 用bridge模式,结合java.util.collections的ArrayList写一个栈(stack)和队列(queue)。写好之后,再写一个双端队列(double-ended queue)。再加入一个LinkedList作为后端实现。这写步骤做下来,你就会明白如何使用Bridge模式在影响最小的情况下往你的代码里添加前端和后端对象。
5. 用bridge模式写一个类,连接不同种类的记帐(bookkeeping)程序(连同它们的接口和数据格式)和不同的银行(这些银行提供不同类型的服务和接口)。