模式的秘密——适配器模式

模式的秘密—适配器模式

最近比较忙,比较忙,比较忙~,苦逼的博主快要阵亡在毕业设计的前线鸟!一提到论文,吓得我赶紧喝了一口98度的开水刺激一下我那颗快要冰凉的心。完全不知道该写什么有木有!!!

悄悄地,我躲开了对酒当歌的夜,也躲开了热闹繁华的街。在电脑前撸出一篇新的技术博文,以此来调试那颗烦躁的心。稍微扯得有些远了,好了回归正题,今天我要分享的是适配器模式。

生活中适配的例子到处都是,比如说,国内居民用电是220伏,而给手机充电只需要5伏左右,那么将220伏转化为手机的5伏,就需要适配器进行降压处理;某某公司招聘android开发,而作为应聘者需要制作一份android开发相关的简历,这也是一种适配;我们国家的电器使用普通的扁平两项或三项插头,而去外国的话,使用的标准就不一样了,比如德国,使用的是德国标准,是两项圆头的插头。如果去德国旅游,那么我们使用的手机充电器插头无法插到德国的插排中去,那就意味着我们无法给手机充电。怎样解决这个问题呢?只要使用一个电源转化器就行了。如下图所示:


说了这么多,那么什么是适配器模式呢?

适配器模式将一个类的接口适配成用户所期待的。一个适配器通常允许因为接口不兼容而不能一起工作的类能够在一起工作,做法是将类自己的接口包裹在一个已存在的类中。

Adapter 设计模式主要目的组合两个不相干类,常用有两种方法,第一种解决方案是修改各自类的接口。但是如果没有源码,或者不愿意为了一个应用而修改各自的接口,则需要使用 Adapter 适配器,在两种接口之间创建一个混合接口。

图1.适配器模式类图


图1所示是适配器模式的类图。Adapter适配器设计模式中有3个重要角色:被适配者Adaptee,适配器Adapter和目标接口Target。其中两个现存的想要组合到一起的类分别是被适配者Adaptee和目标对象Target角色,按照类图所示,我们需要创建一个适配器Adapter将其组合在一起。

 

            我以充电适配的源码进行说明。

1、客户端使用的接口

/*

 * 三相插座接口

 */

public interfaceThreePlugIf {

     public voidpowerWithThree();

}

2、被适配的对象

/*

 * 插座

 */

public class TwoPlug {

     public void powerWithTwo() {

          System.out.println("使用两相电流供电");

     }

}

3、适配器实现

public class TwoPlugAdapter implements ThreePlugIf {

 

     private TwoPlugplug;

 

     public TwoPlugAdapter(TwoPlugplug) {

          // TODO Auto-generated constructor stub

          this.plug =plug;

     }

 

     @Override

     public void powerWithThree(){

          // TODO Auto-generated method stub

          System.out.println("二相转三相充电");

          plug.powerWithTwo();

     }

 

}

4、客户端代码

public class NoteBook {

     private ThreePlugIfplug;

 

     public NoteBook(ThreePlugIfplug) {

          this.plug =plug;

     }

 

     // 使用插座充电

     public void charge() {

          plug.powerWithThree();

     }

 

     public static void main(String[] args) {

          TwoPlug two = new TwoPlug();

          ThreePlugIf three = new TwoPlugAdapter(two);

          NoteBook nb = new NoteBook(three);

          nb.charge();

     }

}

Adapter模式比较适合以下几种情况:

1、当你想使用一个已经存在的类,而它的接口不符合你的需求;

2、你想创建一个可以复用的类,该类可以与其它不相关的类或不可预见的类协同工作;

3、你想使用一些已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口,对象适配器可以适配它的父亲接口。

 

适配器模式的应用

            JDK1.1之前提供的容器有Arrays,Vector,Stack,Hashtable,Properties,BitSet,其中定义了一种访问群集内各元素的标准fang方式,称为Enumeration(列举器)接口,用法如下:

Vector v = new Vector();

for(Enumerationen =v.elements();en.hasMoreElements();){

          Objecto = en.nextElement();

          processObject(o);

}

JDK1.2版本中引入了Iterator接口,新版本的集合对象(HashSet, HashMap,WeakHeahMap,ArrayList,TreeSet,TreeMap,LinkedList)是通过 Iterator 接口访问集合元素的,用法如下:

List list = new ArrayList();

for(Iterator it = list.iterator();it.hasNext();){        

System.out.println(it.next());

}

这样,如果将老版本的程序运行在新的Java编译器上就会出错。因为 List 接口中已经没有 elements(),而只有 iterator() 了。那么如何将老版本的程序运行在新的 Java 编译器上呢? 如果不加修改,是肯定不行的,但是修改要遵循“开-闭”原则。我们可以用 Java 设计模式中的适配器模式解决这个问题。解决办法如下:

import java.util.ArrayList;

import java.util.Enumeration;

import java.util.Iterator;

import java.util.List;

 

public class NewEnumeration implements Enumeration {

 

     Iteratorit;

 

     public NewEnumeration(Iteratorit) {

          this.it =it;

          // TODO Auto-generated constructor stub

     }

 

     public booleanhasMoreElements() {

          // TODO Auto-generated method stub

          returnit.hasNext();

     }

 

     public Object nextElement() {

          // TODO Auto-generated method stub

          returnit.next();

     }

 

     public static void main(String[] args) {

          Listlist = new ArrayList();

          list.add("a");

          list.add("b");

          list.add("C");

     for (Enumeratione =new NewEnumeration(list.iterator());e.hasMoreElements();) {

               System.out.println(e.nextElement());

          }

     }

}

上述源程序的NewEnumeration 是一个适配器类,通过它实现了从 Iterator 接口到 Enumeration 接口的适配,这样我们就可以使用老版本的代码来使用新的集合对象了。

     JavaI/O库大量使用了适配器模式,例如ByteArrayInputStream是一个适配器类,它继承了InputStream的接口,并且封装了一个byte数组。换言之,它将一个byte数组的接口适配成InputStream流处理器的接口。

     Java语言支持四种类型:Java 接口,Java 类,Java 数组,原始类型(即 int,float等)。前三种是引用类型,类和数组的实例是对象,原始类型的值不是对象。Java 语言的数组是像所有的其他对象一样的对象,而不管数组中所存储的元素类型是什么。这样一来的话,ByteArrayInputStream就符合适配器模式的描述,是一个对象形式的适配器类。FileInputStream是一个适配器类。在FileInput -Stream继承了InputStrem 类型,同时持有一个对FileDiscriptor的引用。这是将一个FileDiscriptor对象适配成InputStrem类型的对象形式的适配器模式。

     同样地,在OutputStream类型中,所有的原始流处理器都是适配器类。Byte-ArrayOutputStream 继承了OutputStream类型,同时持有一个对byte数组的引用。它一个byte数组的接口适配成OutputString类型的接口,因此也是一个对象形式的适配器模式的应用。

FileOutputStream继承了OutputStream类型,同时持有一个对File-Discriptor对象的引用。这是一个将FileDiscriptor接口适配成Output-Stream接口形式的对象型适配器模式。

Reader类型的原始流处理器都是适配器模式的应用。StringReader是一个适配器类,StringReader类继承了Reader类型,持有一个对String对象的引用。它将String的接口适配成Reader类型的接口。

 

双向适配器

适配器也可以实现双向的适配,前面所讲的都是把 Adaptee 适配成为 Target,其实也可以把 Target 适配成为 Adaptee。也就是说这个适配器可以同时当作 Target 和 Adaptee 来使用。

/*

 * 双向适配器模式

 */

interface IA {

     public void funA1();

 

     public void funA2();

}

 

class A implements IA {

     @Override

     public void funA1() {

          // TODO Auto-generated method stub

          System.out.println("A.funA1()");

     }

 

     @Override

     public void funA2() {

          // TODO Auto-generated method stub

          System.out.println("A.funA2()");

     }

}

 

interface IB {

     public void funB1();

 

     public void funB2();

}

 

class B implements IB {

     @Override

     public void funB1() {

          // TODO Auto-generated method stub

          System.out.println("B.funB1()");

     }

 

     @Override

     public void funB2() {

          // TODO Auto-generated method stub

          System.out.println("B.funB2()");

     }

}

 

// 组合AB,将TwoWayAdapter当成AB来使用

// TwoWayAdapter类同时实现了A接口和B接口

public class TwoWayAdapter implements IA, IB {

 

     private IAia;

     private IBib;

 

     public void setIa(IA ia) {

          this.ia =ia;

     }

 

     public void setIb(IB ib) {

          this.ib =ib;

     }

 

     // A的对象调用了AfunA1()方法后,同时调用BfunB2()方法

     @Override

     public void funA1() {

          // TODO Auto-generated method stub

          ia.funA1();

          ib.funB1();

     }

 

     @Override

     public void funA2() {

          // TODO Auto-generated method stub

          ia.funA2();

     }

 

     @Override

     public void funB1() {

          // TODO Auto-generated method stub

          ib.funB1();

     }

 

     @Override

     public void funB2() {

          // TODO Auto-generated method stub

          ib.funB2();

          ia.funA2();

     }

}

双向适配器同时实现了Target和Adaptee的接口,使得双向适配器可以在 Target或Adaptee被使用的地方使用,以提供对所有客户的透明性。尤其在两个不同的客户需要用不同的地方查看同一个对象时,适合使用双向适配器。

 

对象适配器和类适配器

     在标准的适配器模式里面,根据适配器的实现方式,把适配器分为对象适配器和类适配器。

对象适配器

     依赖于对象的组合,都是采用对象组合的方式,也就是对象适配器实现的方式。

类适配器

     采用多重继承对一个接口与另一个接口进行匹配。由于Java不支持多重继承,所以到目前为止还没有涉及。但可以通过让适配器去实现Target接口的方式来实现。

 

类适配器和对象适配器的选择

1、从实现角度:类适配器使用对象继承的方式,属于静态的定义方式。对象适配器使用对象组合的方式,属于动态组合的方式;

2、从工作模式角度:类适配器直接继承了Adaptee,使得适配器不能和Adapter的子类一起工作。对象适配器允许一个Adapter和多个Adaptee,包括Adaptee和它所有的子类一起工作;

3、从定义角度:类适配器可以重定义Adaptee的部分行为,相当于子类覆盖父类的部分实现方法。对象适配器要重定义Adaptee很困难;

4、从开发角度:类适配器仅仅引入了一个对象,并不需要额外的引用来间接得到Adaptee。对象适配器需要额外的引用来间接得到Adaptee。

总的来说,建议使用对象适配器的方式。

 

适配器模式使用注意事项

1、充当适配器角色的类就是:实现已有接口的抽象类;

2、为什么要用抽象类?此类是不要被实例化的。而只充当适配器的角色,也就为其子类提供了一个共同的接口,但其子类又可以将精力只集中在其感兴趣的地方。

3、适配器模式中被适配的接口Adaptee和适配成为的接口Target是没有关联的,Adaptee和Target中的方法既可以是相同的,也可以是不同的。

4、适配器在适配的时候,可以适配多个Apaptee,也就是说实现某个新的Target 的功能的时候,需要调用多个模块的功能,适配多个模块的功能才能满足新接口的要求。

5、适配器有一个潜在的问题,就是被适配的对象不再兼容Adaptee的接口,因为适配器只是实现了Target的接口。这导致并不是所有Adaptee对象可以被使用的地方都能是使用适配器,双向适配器解决了这个问题。

 

优点

适配器模式也是一种包装模式,它与装饰模式同样具有包装的功能,此外,对象适配器模式还具有委托的意思。总的来说,适配器模式属于补偿模式,专用来在系统后期扩展、修改时使用。

 

缺点

过多的使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。

 

适配器模式应用场景

在软件开发中,也就是系统的数据和行为都正确,但接口不相符时,我们应该考虑用适配器,目的是使控制范围之外的一个原有对象与某个接口匹配。适配器模式主要应用于希望复用一些现存的类,但是接口又与复用环境要求不一致的情况。比如在需要对早期代码复用一些功能等应用上很有实际价值。适用场景大致包含三类:

1、已经存在的类的接口不符合我们的需求;

2、创建一个可以复用的类,使得该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作;

3、在不对每一个都进行子类化以匹配它们的接口的情况下,使用一些已经存在的子类。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值