适配器模式:将一个类的接口,转换成客户期望的另一个接口。适配器让原本接口不兼容的类可以合作无间;
知识点的梳理:
- 当需要使用一个现有的类而其接口并不符合你的需要时,就使用适配器;
- 适配器改变接口以符合客户的期望;
- 实现一个适配器花费的时间取决于目标接口的大小与复杂程度;
- 适配器有两种形式:对象适配器和类适配器;类适配器需要多重继承;
-
适配器将一个对象包装起来以改变其接口;装饰者将一个对象包装起来以增加新的行为和责任;而外观将一群对象"包装"起来以简化其接口;
-
关于适配器
-
我们周围的适配器
-
如果在欧洲国家使用美国制造的笔记本电脑,可能需要使用一个交流电的插头适配器,向下图一样:
- OO适配器和真实的案例一样:将一个接口转换成另一个接口,以符合客户的期望;
-
-
面向对象适配器
-
假设一个软件系统,你希望它能和一个新的厂商类库搭配使用,但是这个新厂商设计出来的接口,不同于久厂商的接口:
-
如果不想改变现有代码来解决这个问题的话,可以自己搞一个类,将新厂商接口转接成你所期望的接口;
-
这个适配器工作起来就如同一个中间人,它将客户所发出的请求转换重厂商类能理解的请求
-
-
如果它走路像鸭子,叫起来像只鸭子,那么它可能是一只包装了鸭子适配器的火鸡!啊哈哈哈哈哈!
- 来看看鸭子接口还有它的子类--绿头鸭
-
//鸭子实现了Duck接口,具备呱呱叫和飞行的能力 public interface Duck { public void quack(); public 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 { public void gobble();//火鸡的叫发是咯咯叫 public 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 { Turkey turkey; //接着,需要取得要适配的对象引用,这里利用构造器取得这个引用 public TurkeyAdapter(Turkey turkey){ this.turkey = turkey; } @Override public void quack() { //现在我们需要实现接口中所有的方法。quack()在类之间的转换很简单,只要调用gobble()就可以了 turkey.gobble(); } @Override public void fly() { for (int i = 0; i < 5; i++) { //固然两个接口都具备了fly()方法,火鸡的飞行距离很短,不像鸭子可以长途飞行。 //要让鸭子的飞行和火鸡的飞行能够对应,必须连续五次调用火鸡的fly()来完成 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); } /** * 取得一只鸭子,并调用它的quack()和fly()方法 */ static void testDuck(Duck duck) { duck.quack(); duck.fly(); } } |
- 效果
|
-
定义适配器
-
各个部件之间的关系
-
客户使用适配器的过程如下:
- 客户通过目标接口调用适配器的方法对适配器发出请求;
- 适配器使用被适配者接口把请求转换成被适配者的一个或多个调用接口;
- 客户接受到调用的结果,但并未察觉这一切是适配器在起作用;
-
一些问题
-
一个适配器需要做多少的适配工作?如果需要实现一个很大的目标接口,似乎有很多的工作要做?
- 实现一个适配器需要进行的工作与目标接口的大小成正比;如果不用适配器,就必须改写客户端的代码来调用这个新的接口,将会花费很多时间;
- 相比之下,提供一个适配器类,将所有的改变封装在一个类中,是比较好的做法;
-
一个适配器只能够封装一个类吗?
- 适配器模式的工作是将一个接口转换成另一个。大多数的适配器模式所采取的例子都是让一个适配器包装一个被适配者。而一个适配器包装多个被适配者涉及另一个模式"外观模式";
-
万一系统中新旧并存,旧的部分期望旧的厂商接口,但我们已经使用了新厂商的接口编写了一部分,这个时候该怎么办?这里使用适配器,那里却使用未包装的接口,这让人感到很混乱。如果固守旧的代码,不管适配器会不会好一些?
- 不需要这样,可以创建一个双向的适配器,支持两边的接口;
- 创建一个双向适配器,必须实现所涉及的两个接口,这样,这个适配器可以当做旧的接口,或者当做新的接口使用;
-
-
适配器类图
-
-
对象和类的适配器
-
"类"适配器需要多重继承才能实现,这在Java中是不可能的。但使用支持多重继承语言的时候,还是可能遇到这样的需求。多重继承的类图如下:
- "类"适配器与"对象"适配器的差别就在于适配器继承了Target和Adaptee。而对象适配器利用组合的方法将请求传送给被适配者;
-
回到"鸭子"示例,将鸭子和火鸡带入到类适配器或对象适配器
-
类适配器
-
对象适配器
-
-
将适配器应用到Java中的类
-
早期集合类型collection,实现了一个名为elements()的方法。该方法返回一个Enumeration(枚举)。这个接口可以逐一走过此集合内的每个元素,而无需知道它们在集合内是如何被管理的;
-
Sun公司在今后的更新中,开始使用Iterator(迭代器)接口,这个接口和枚举接口很像,都可以遍历集合类型内的每个元素。但与枚举接口的不同在于,迭代器还提供了remove()功能
-
现在一些老系统仍旧使用枚举接口。现在我们希望在新代码中使用迭代器。解决这个问题,我们需要构造一个适配器;先来看看这两个接口中,各个方法的对应关系
-
如果添加适配器的话,应该是如下的类图。但问题是,如何实现remove()方法呢?
-
这个例子里面,枚举是一个"只读"接口。适配器无法实现一个有实际功能的remove()方法,最多抛出一个运行时异常,UnsupportedOpeartionException;那么代码可以这样写:
-
-