设计模式笔记07-适配器模式与外观模式
1 引言
在本章,我们将要进行一项任务,其不可能的程度,简直就像是将一个方块放进一个圆洞中。听起来不可能?有了设计模式,就有可能。还记得装饰者模式吗?我们将对象包装起来,赋予他们新的职责。而现在则是以不同目的,包装某些对象:让他们的接口看起来不像自己而像是别的东西。为何要这样做?因为这样就可以在设计中,将类的接口转换成想要的接口,以便实现不同的接口。不仅如此,我们还要探讨另一个模式,将对象包装起来以简化其接口。
2 正文
2.1 我们周围的适配器
比如某些交流电适配器,改变插座的形状来匹配你的插头,直接把电流传过去。 那么面向对象的适配器又是什么?其实,OO适配器和真实世界的适配器扮演着同样的角色:将一个接口转换成另一个接口,以符合客户的期望。
2.2 火鸡转换器
让我们来看看使用中的适配器。还记得第一章的鸭子吧?让我们看看鸭子接口和类的一个稍微简化的版本:
public interface Duck {
public void quack();
public void fly();
}
绿头鸭是鸭子的子类
public class MallardDuck implements Duck {
//很简单的实现:只是打印出鸭子在干什么
public void quack() {
System.out.println("Quack");
}
public void fly() {
System.out.println("I'm flying");
}
}
为您介绍最新的“街头玩禽“:火鸡
public interface Turkey {
//火鸡不会呱呱叫,只会咕咕(gobble)叫
public void gobble();
//火鸡会飞,虽然飞不远
public void fly();
}
下面是火鸡的一个具体实现
public class WildTurkey implements Turkey {
//简单打印火鸡的动作说明
public void gobble() {
System.out.println("Gobble gobble");
}
public void fly() {
System.out.println("I'm flying a short distance");
}
}
public class TurkeyAdapter implements Duck {
//首先,你需要实现转成的类型接口,也就是你的客户所期望看到的接口
//接着,需要取得要适配的对象引用,这里我们利用构造器取得这个引用
Turkey turkey;
public TurkeyAdapter(Turkey turkey) {
this.turkey = turkey;
}
//现在我们需要实现接口中的方法。
//quack()在类之间转换很简单,只要调用gobble()就可以了
public void quack() {
turkey.gobble();
}
/*
* 固然两个接口都具备了fly()方法,火鸡的飞行距离很短,不像鸭子可以长途飞翔。
* 要让鸭子的飞行和火鸡的飞行能够对应,必须连续五次调用火鸡的fly()方法来完成。
* @see headfirst.adapter.ducks.Duck#fly()
*/
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 says...");
testDuck(turkeyAdapter);
}
static void testDuck(Duck duck) {
duck.quack();
duck.fly();
}
}
适配器模式解析: 1、客户通过目标接口调用适配器方法对适配器发出请求; 2、适配器使用被适配者接口把请求转换成被适配者的一个或者多个调用接口; 3、客户接口到调用的结果,但并未察觉这一切是适配器在起转换作用。
问:一个适配器需要做多少”适配“的工作?如果我需要实现很大的目标接口,似乎有很多工作要做。 答:的确是如此。实现一个适配器所需要进行的工作的确和目标接口的大小成正比。如果不用适配器,你就必须改写客户端的代码来调用这个新的接口,将会花许多力气来做大量的调查工作和代码改写工作。相比之下,提供一个适配器类,将所有的改变封装在一个类中,是比较好的做法。
2.3 定义适配器模式
适配器模式:将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间。 这个适配器模式充满着良好的OO设计原则:使用对象组合,以修改的接口包装被适配者。这种做法还有额外的优点,那就是,被适配者的任何子类,都可以搭配着适配器使用。 也请留意,这个模式是如何把客户和接口绑定起来,而不是和实现绑定起来的。
2.4 对象和类的适配器
现在,尽管已经定义适配器模式,但其实我们还没有告诉你有关的一切。实际上有两种适配器:”对象“适配器和”类“适配器。本章涵盖了对象适配器和类适配器。
究竟什么是”类“适配器?为什么我们还没告诉你这种适配器?因为你需要多重继承才能够实现它,这在 Java中是不可能的。但是当你在使用多重继承语言的时候,还是可能遇到这样的需求。 对象适配器和类适配器使用两种不同的适配方法,分别是组合(实现接口)与继承。
2.5 真实世界的适配器
旧世界的枚举器:如果你已经使用过Java,可能记得早起的集合(collection)类型,例如Vector、Stack、HashTable,都实现了一个名为elements()的方法,该方法会返回一个Enumeration。这个Enumeration接口可以逐一走过此集合内的每个元素,而无需知道它们在集合内是如何被管理的。
新世界的迭代器:当Sun推出更新后的集合类时,开始使用了Iterator(迭代器)接口,这个接口和枚举接口很像,都可以让你遍历此集合类型内的每个元素,但不同的是,迭代器还提供