💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
#### **参考文章** [《Android源码设计模式解析与实战》读书笔记(二十)](http://blog.csdn.net/qq_17766199/article/details/50514877) [【设计模式】适配器模式](http://www.cnblogs.com/chenpi/p/5188384.html) [Java之美[从菜鸟到高手演变]之设计模式二](http://blog.csdn.net/zhangerqing/article/details/8239539) [ Android开发设计模式之——适配者模式 ](http://blog.csdn.net/beyond0525/article/details/22814129) [《JAVA与模式》之适配器模式](http://www.cnblogs.com/java-my-life/archive/2012/04/13/2442795.html#3870969) ### **适配器模式** 适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。 说到底,适配器是将两个不兼容的类融合在一起,它有点像粘合剂,将不同的东西通过一种转换使得它们能够协作起来,例如:经常碰到要在两个没有关系的类型之间进行交互,第一个解决方案就是修改各自类的接口,但是如果没有源代码或者我们不愿意为了一个应用而修改各自的接口,此时怎么办?这种情况我们往往会使用一个Adapter,在这两种接口之间创建一个“混血儿”接口,这个Adapter会将这两个接口进行兼容,在不修改原有代码的情况下满足需求。 #### **适配器模式的用途** 用电器做例子,笔记本电脑的插头一般都是三相的,即除了阳极、阴极外,还有一个地极。而有些地方的电源插座却只有两极,没有地极。电源插座与笔记本电脑的电源插头不匹配使得笔记本电脑无法使用。这时候一个三相到两相的转换器(适配器)就能解决此问题,而这正像是本模式所做的事情。 又或者如果你在欧洲某一个国家使用美国制造的笔记本电脑,你可能需要一个交流电适配器 ![](https://box.kancloud.cn/2c8430dcb30d14367bcf20da98befe30_786x557.jpg) #### **面向对象适配器** 假设有一个软件系统,希望它能和一个新的厂商类库搭配使用,但是这个新厂商所设计出来的接口,不同于旧厂商的接口 如下图所示: ![](https://box.kancloud.cn/72ab671dc9c68e5354fdded7cce6a051_509x266.jpg) 这个适配器就像是一个中间人,它将客户所发出的请求转换成厂商类能理解的请求 ![](https://box.kancloud.cn/f54c6b73db9022e0f1f751b7d54f3320_448x223.jpg) #### **适配器模式(对象适配器)解析** ![](https://box.kancloud.cn/a18df007c92aa3efca0bf5ed2420660e_929x510.jpg) **客户使用适配器的过程如下**: 1. 客户通过目标借口调用适配器的方法对适配器发出请求 2. 适配器使用被适配者接口把请求转换成被设配者的一个或多个调用接口 3. 客户接收到调用的结果,但并未察觉这一切是适配器在起转换作用 >[info] **注意**: >1. 客户和被适配者是解耦的,一个不知道另一个。 >2. 如果系统中新旧并存,旧的部分希望旧的厂商接口,但是我们已经使用新厂商的接口编写了这一部分,这时候,我们可以创建一个双向的适配器,支持两边的接口。想创建一个双向的接口,就必须实现所涉及到的两个接口,这样,这个适配器可以当作旧的接口,或者当做新的接口使用。 >3. 适配器模式将一个类的接口,转换成客户期望的另一个接口,适配器让原来接口不兼容的类可以合作无间 >4. 适配器模式充满良好的OO设计原则:使用对象组合,以修改的接口包装被适配者。被适配者的任何子类,都可以搭配着适配器使用。 #### **使用场景** 1. 系统需要使用现有的类,但此类的接口不符合系统的需要,即接口不兼容。 2. 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。 3. 需要一个统一的输出接口,而输入端的类型不可预知。 #### **适配器模式的结构** 适配器模式有**类的适配器模式**和**对象的适配器模式**两种不同的形式。 ![](http://img.my.csdn.net/uploads/201211/29/1354192484_5322.PNG) **对象的适配器**如下图所示: ![](https://box.kancloud.cn/0a01bb3544d0657dc99d2f0cb4a613fb_845x419.png) **类的适配器模式**如下图所示: ![](https://box.kancloud.cn/89701509db71837d74b8785e0c28d6d4_1081x358.png) #### **类适配器模式**: 类的适配器模式把适配的类的API转换成为目标类的API。 ![](https://box.kancloud.cn/7bd2fa9959d73b806552933fdb6b65d8_793x419.png) 如上图所示:类适配器是**通过实现Target 接口以及继承Adaptee 类来实现接口转换**,例如,目标接口需要的是operation2 ,但是Adaptee 对象只有一个operation3 ,因此就出现了不兼容的情况。此时**通过Adapter 实现一个operation2 函数将Adaptee 的operation3 转换为Target 需要的operation2,以此实现兼容。** **角色介绍**: * Target:目标角色,也就是所期待得到的接口。注意: 由于这里讨论的是类适配器模式,因此目标不可以是类。 * Adaptee:现在需要适配的接口。 * Adapter:适配器角色,也是本模式的核心。适配器把源接口转换成目标接口。显然,这一角色不可以是接口,而必须是具体类。 #### **对象适配器模式**: 与类的适配器模式一样,对象的适配器模式把被适配的类的API转换成为目标类的API,与类的适配器模式不同的是,对象的适配器模式不是使用继承关系连接到Adaptee ,而是使用代理关系连接到Adaptee类, UML类图如图所示。 ![](https://box.kancloud.cn/acb3c2c70d8d65036cb65b0fe6dd761b_793x419.png) **示例**: 以笔记本电源适配器为例,电源适配器将220V的电压转换到5V。那么5V电压就是Target接口,220V电压就是Adaptee类,转换就是Adapter。 * **类适配器模式**: **Target角色** ~~~ public interface FiveVolt { public int getVolt5(); } ~~~ **Adaptee角色,需要被转换的对象** ~~~ public class Volt220 { public int getVolt220(){ return 220; } } ~~~ **Adapter角色,将220V的电压转换成5V的电压** ~~~ public class VoltAdapter extends Volt220 implements FiveVolt{ @Override public int getVolt5() { return 5; } } ~~~ Target角色给出了需要的目标接口,而Adaptee类则是需要被转换的对象。Adapter则是将Volt220转换成Target的接口。对应的Target的目标是要获取5V的输出电压,而Adaptee正常输出电压是220V,此时就需要电源适配器类将220V 的电压转换为5V 电压,解决接口不兼容的问题。 ~~~ public class Test { public static void main(String[] args) { VoltAdapter adapter = new VoltAdapter(); System.out.println("输出电压:" + adapter.getVolt5()); } } ~~~ * **对象适配器模式** 对象适配器模式与类适配器模式不同的是,对象适配器模式不是使用继承关系连接到Adapter类,而是**使用代理关系连接到Adapter类。** 所以FiveVolt 、Volt220 不变,VoltAdapter 修改如下: ~~~ public class VoltAdapter1 implements FiveVolt{ Volt220 mVolt220; public VoltAdapter1(Volt220 adaptee) { this.mVolt220 = adaptee; } public int getVolt220(){ return mVolt220.getVolt220(); } @Override public int getVolt5() { return 5; } } ~~~ 注意,这里为了节省代码,我们并没有遵循一些面向对象的基本原则。使用示例如下: ~~~ public class Test { public static void main(String[] args) { VoltAdapter adapter = new VoltAdapter(new Volt220()); System.out.println("输出电压:" + adapter.getVolt5()); } } ~~~ 这种实现方式直接将要适配的对象传递到Adapter中,使用组合的形式实现接口兼容的效果。这比类适配器方式更为灵活,同时被适配对象的方法不会暴露出来。而类适配器由于继承了被适配对象,因此,被适配对象类的函数在Adapter 类中也都含有,这使得Adapter 类出现一些奇怪的接口,用户使用成本较高。因此对象适配器模式更加灵活、实用。 在**实际开发中Adapter 通常应用于进行不兼容的类型转换的场景,还有一种就是输入有无数种情况,但是输出类型是统一的,我们可以通过Adapter 返回一个统一的输出,而具体的输入留给用户处理,内部只需知道输出的是符合要求的类型即可**。例如ListView 的Adapter,用户的Item View各式各样,但最终都是属于View 类型, ListView 只需要知道getView 返回的是一个View 即可,具体是什么View 类型并不需要ListView 关心。而**在使用Adapter 模式的过程中建议尽量使用对象适配器的实现方式,多用合成或者聚合,少用继承。当然, 具体问题具体分析,根据需要来选用实现方式,最适合的才是最好的**。 #### **接口适配器模式** 除了前面2种适配器模式还有**接口适配器模式**。也叫做[**缺省适配模式**](http://baike.sogou.com/v71483729.htm?fromTitle=缺省适配器模式) 接口的适配器是这样的:有时我们写的一个接口中有多个抽象方法,当我们写该接口的实现类时,必须实现该接口的所有方法,这明显有时比较浪费,**因为并不是所有的方法都是我们需要的,有时只需要某一些**,此处为了解决这个问题,我们引入了接口的适配器模式,**借助于一个抽象类,该抽象类实现了该接口,实现了所有的方法,而我们不和原始的接口打交道,只和该抽象类取得联系**,所以我们**写一个类,继承该抽象类,重写我们需要的方法就行**。 ![](https://box.kancloud.cn/57e3ce1326fb73919b40e151c1a92c72_428x404.PNG) 有时可以这样理解:适配新的业务需求的时候借助抽象实现类(AbsPhone实现Usb接口),也就说,抽象实现类把Usb接口中的行为都实现了,新的适配只需要跟抽象类对话就行,因为抽象实现类就能满足了所有适配的需求,并且做到了只适配业务本身的行为,接口中不需要的行为我根本不需要关注。这就是抽象实现类的作用。类图关系如下: ![](http://img.blog.csdn.net/20140402183340531?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvQmV5b25kMDUyNQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) **示例如下**: **抽象类AbsPhone实现** ~~~ /** * 接口的适配器模式 * 1.借助于一个抽象类,该抽象类实现了该接口,实现了所有的方法 * 2.继承类可以选择性的实现接口中的方法 * * @author xuzhaohu * */ public abstract class AbsPhone implements Usb { public void store() { System.out.println("AbsPhone implements usb's store methond"); } public void takeAlong() { System.out.println("AbsPhone implements usb's takeAlong methond"); } } ~~~ 适配类只跟AbsPhone打交道,根本不需要关心接口的行为,只显示自己所要关注的。 如Phone1适配只需要store()行为 ~~~ public class Phone1 extends AbsPhone { public void call() { System.out.println("Phone1 call"); } public void sms() { System.out.println("Phone1 sms"); } public void store() { System.out.println("Phone1 need usb's store methond"); } } ~~~ Phone2适配只需要takeAlong()行为 ~~~ public class Phone2 extends AbsPhone { public void call() { System.out.println("Phone2 call"); } public void sms() { System.out.println("Phone2 sms"); } public void takeAlong() { System.out.println("Phone2 need usb's takeAlong methond"); } } ~~~ 实例化调用 ~~~ Phone1 p1 = new Phone1(); Phone2 p2 = new Phone2(); p1.store(); p1.takeAlong(); p2.takeAlong(); p2.store(); ~~~ 输出结果: ~~~ Phone1 need usb's store methond AbsPhone implements usb's takeAlong methond Phone2 need usb's takeAlong methond AbsPhone implements usb's store methond ~~~ 这样很清晰的知道适配的什么方法了。 #### **缺省适配模式** 缺省适配(Default Adapter)模式为一个接口提供缺省实现,这样子类型可以从这个缺省实现进行扩展,而不必从原有接口进行扩展。作为适配器模式的一个特例,缺省是适配模式在JAVA语言中有着特殊的应用。 **示例**: **鲁智深的故事** 和尚要做什么呢?吃斋、念经、打坐、撞钟、习武等。如果设计一个和尚接口,给出所有的和尚都需要实现的方法,那么这个接口应当如下: **接口:和尚** ~~~ public interface 和尚 { public void 吃斋(); public void 念经(); public void 打坐(); public void 撞钟(); public void 习武(); public String getName(); } ~~~ 显然,所有的和尚类都应当实现接口所定义的全部方法,不然就根本通不过JAVA语言编辑器。像下面的鲁智深类就不行。 **类:鲁智深** ~~~ public class 鲁智深 implements 和尚{ public void 习武(){ 拳打镇关西; 大闹五台山; 大闹桃花村; 火烧瓦官寺; 倒拔垂杨柳; } public String getName(){ return "鲁智深"; } } ~~~ 由于鲁智深只实现了getName()和习武()方法,而没有实现任何其他的方法。因此,它根本就通不过Java语言编译器。鲁智深类只有实现和尚接口的所有的方法才可以通过Java语言编译器,但是这样一来鲁智深就不再是鲁智深了。以史为鉴,可以知天下。研究一下几百年前鲁智深是怎么剃度成和尚的,会对Java编程有很大的启发。不错,当初鲁达剃度,众僧说:“此人形容丑恶、相貌凶顽,不可剃度他",但是长老却说:”此人上应天星、心地刚直。虽然时下凶顽,命中驳杂,久后却得清净。证果非凡,汝等皆不及他。” 原来如此!看来只要这里也应上一个天星的话,问题就解决了!使用面向对象的语言来说,“应”者,实现也;“天星”者,抽象类也。 **缺省适配类:天星** ~~~ public abstract class 天星 implements 和尚 { public void 吃斋(){} public void 念经(){} public void 打坐(){} public void 撞钟(){} public void 习武(){} public String getName(){ return null; } } ~~~ 鲁智深类继承抽象类“天星” ~~~ public class 鲁智深 extends 天星{ public void 习武(){ 拳打镇关西; 大闹五台山; 大闹桃花村; 火烧瓦官寺; 倒拔垂杨柳; } public String getName(){ return "鲁智深"; } } ~~~ 这个抽象的天星类便是一个适配器类,鲁智深实际上借助于适配器模式达到了剃度的目的。此适配器类实现了和尚接口所要求的所有方法。但是与通常的适配器模式不同的是,此适配器类给出的所有的方法的实现都是“平庸”的。这种“平庸化”的适配器模式称作缺省适配模式。 在很多情况下,必须让一个具体类实现某一个接口,但是这个类又用不到接口所规定的所有的方法。通常的处理方法是,这个具体类要实现所有的方法,那些有用的方法要有实现,那些没有用的方法也要有空的、平庸的实现。 >[warning] **注意**:这些空的方法是一种浪费,有时也是一种混乱。除非看过这些空方法的代码,程序员可能会以为这些方法不是空的。即便他知道其中有一些方法是空的,也不一定知道哪些方法是空的,哪些方法不是空的,除非看过这些方法的源代码或是文档。 缺省适配模式可以很好的处理这一情况。可以设计一个抽象的适配器类实现接口,此抽象类要给接口所要求的每一种方法都提供一个空的方法。就像帮助了鲁智深的“上应天星”一样,此抽象类可以使它的具体子类免于被迫实现空的方法。 #### **缺省适配模式的结构** 缺省适配模式是一种“平庸”化的适配器模式。 ![](https://box.kancloud.cn/04f402c5f6c74d475e445f336b0c096d_357x354.png) ~~~ public interface AbstractService { public void serviceOperation1(); public int serviceOperation2(); public String serviceOperation3(); } ~~~ ~~~ public class ServiceAdapter implements AbstractService{ @Override public void serviceOperation1() { } @Override public int serviceOperation2() { return 0; } @Override public String serviceOperation3() { return null; } } ~~~ 可以看到,接口AbstractService要求定义三个方法,分别是serviceOperation1()、serviceOperation2()、serviceOperation3();而抽象适配器类ServiceAdapter则为这三种方法都提供了平庸的实现。因此,任何继承自抽象类ServiceAdapter的具体类都可以选择它所需要的方法实现,而不必理会其他的不需要的方法。 **适配器模式的用意**是要改变源的接口,以便于目标接口相容。**缺省适配的用意稍有不同**,它是为了方便建立一个不平庸的适配器类而提供的一种平庸实现。 在任何时候,如果不准备实现一个接口的所有方法时,就可以使用“缺省适配模式”制造一个抽象类,给出所有方法的平庸的具体实现。这样,从这个抽象类再继承下去的子类就不必实现所有的方法了。 #### **适配器模式的优点** * **更好的复用性** 系统需要使用现有的类,而此类的接口不符合系统的需要。那么通过适配器模式就可以让这些功能得到更好的复用。 * **更好的扩展性** 在实现适配器功能的时候,可以调用自己开发的功能,从而自然地扩展系统的功能。 #### **适配器模式的缺点** 过多的使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是A接口,其实内部被适配成了B接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。 #### **面向接口编程之适配器模式** 前言: 使用一个现成的类,但是它的接口不完全符合你的需求,我只想要它其中的一个方法,不想覆写其他的方法。 比如,窗体有变大,变小,关闭的行为,但是我现在只需要关闭行为; 代码如下 ~~~ package reviewDemo; //适配器模式:只想用其中的某一个方法,用适配器作为中间的过渡 interface Windows{ void max(); void min(); void close(); } //适配器模式,实现接口所有的方法,但是不写方法体! class AdapterWindows implements Windows{ @Override public void max() { } @Override public void min() { } @Override public void close() { } } class MyWindows extends AdapterWindows{ //覆写父类的方法 public void close(){ System.out.println("这个实现的是关闭功能!"); } } public class Demo17 { public static void main(String[] args) { new MyWindows().close(); } } ~~~