适配器模式
概念
适配器的作用:它位于美式插头和欧式插座的中间,它的工作是将欧式插座转换成美式插座,好让美式插头可以插进这个插座得到电力。或者也可以这么认为:适配器改变了插座的接口,以符合美式笔记本电脑的需求。
某些交流电适配器相当简单,它们只是改变插座的形状来匹配你的插头,直接把电流传送过去。但是有些适配器内部则是相当复杂,可能会发改电流符合装置的需求。
OO适配器和真实世界的适配器扮演着同样的角色:将一个接口转换成另一个接口,以符全客户的期望
假设已有一个软件系统,你希望它能和一个新的厂商类库搭配使用,但是这个新厂商所设计出来的接口,不同于旧厂商的接口:
你不想改变现有的代码,解决这个问题(而且你也不能改变厂商的代码)。所以该怎么做?这个嘛,你可以写一个类,将新厂商接口转接成你所期望的接口。
这个适配器工作起来就如同一个中间人,它将客户所发出的请求转换成厂商类能理解的请求。
示例
火鸡转接器
如果它走起路来像只鸭子,叫起来像只鸭子,那么他必定可能是一只鸭子包装了鸭子适配器的火鸡......
鸭子接口
public interface Duck {
void quack();
void fly();
}
绿头鸭子也是鸭
public class MallardDuck implements Duck {
@Override
public void quack() {
System.out.println("Quack");
}
@Override
public void fly() {
System.out.println("I'm flying");
}
}
为您介绍最新的“街头顽禽”
public interface Turkey {
void gobble();
void fly();
}
火鸡的一个具体的实现,就和鸭子一样,只是打印出火鸡的动作说明。
public class WildTurkey implements Turkey {
@Override
public void gobble() {
System.out.println("Gobble gobble");
}
@Override
public void fly() {
System.out.println("I'm flying a short distance");
}
}
**现在,假设你缺鸭子对象,想用一些火鸡对象来冒充。显而易见,因为火鸡的接口不同,所以我们不能公然拿来用。
那么,就写一个适配器吧:**
public class TurkeyAdapter implements Duck {
private Turkey turkey;
public TurkeyAdapter(Turkey turkey) {
this.turkey = turkey;
}
@Override
public void quack() {
turkey.gobble();
}
@Override
public void fly() {
for(int i = 0; i < 5; i++) {
turkey.fly();
}
}
}
首先,你需要实现你想转换成的类型接口,也就是你的客户所期望看到的接口。
接着,需要取得要适配的对象引用,这里我们利用构造器取得这个引用。
实现接口中的所有的方法
测试适配器的例子
public class DuckTestDrive {
public static void main(String[] args) {
MallardDuck duck = new MallardDuck();
WildTurkey turkey = new WildTurkey();
Duck turkeyAdapter = new TurkeyAdapter(turkey);
System.out.println("The Turkey says....");
turkey.gobble();
turkey.fly();
System.out.println("\nThe Duck says...");
testDuck(duck);
System.out.println("\nThe TurkeyAdapter say...");
testDuck(turkeyAdapter);
}
static void testDuck(Duck duck) {
duck.quack();
duck.fly();
}
}
/*output:
The Turkey says....
Gobble gobble
I'm flying a short distance
The Duck says...
Quack
I'm flying
The TurkeyAdapter say...
Gobble gobble
I'm flying a short distance
I'm flying a short distance
I'm flying a short distance
I'm flying a short distance
I'm flying a short distance
*/
类图
真实世界的适配器
让我们看看真实世界中一个简单的适配器(至少比鸭子更实际些)......
旧世界的枚举器
如果你已经使用这Java,可能记得早期的集合(collection)类型(例如:Vector, Stack, Hashtable)都实现了一个名为elements()的方法。该方法会返回一个Enumeration(举)。这个Enumeration接口可以走过此集合内的每个元素,而无需知道它们集合内是如何被管理的。
新世界的迭代器
当Sun推出更新的集合类时,开始使用Iterator(迭代器)接口,这个接品和枚举接口很像,都可以让你遍历此集合类型内的每个元素,但不同的是,迭代器还提供了删除元素的能力。
将枚举适配到迭代器
我们先看看这两个接口,找出它们的方法映射关系。换句话说,我们要找出每个适配器方法在被适配者中的对应方法是什么
设计适配器
这个类应该是这样的:我们需要一个适配器,实现目标接口,而此目标妆口是由被适配者所组合的。hasNext()和next()方法很容易实现,直接把它们从目标对应到被适配者就可以了。但是对于remove()方法,我们又该怎么办?请花一些时间想一想(我们在下一页就会处理)。目前,类图是这样的:
处理remove()方法
好了,我们知道枚举不支持删除,因为枚举是一个“只读”接口。适配器无法实现一个有实际功能的remove()方法,最多只能抛出一个运行地异常。幸运地,迭代器接口的设计者事先料到了这样的需要,所以将remove()方法定义成会抛出UnsupportedOperationException。
在这个例子中,我们看到适配器并不完美;客户必须小心潜在的异常,但只要客户小心,而且适配器的文档能做出说明,这也算是一个合理的解决方案。
编写一个EnumeratorIterator适配器
这是一份简单而有效的代码,适合依然会产生枚举的遗留类。