设计模式解密(8)- 适配器模式
1、简介
定义:适配器模式把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作。
主要解决:主要解决在软件系统中,常常要将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足的。
实现方式:
1、类的适配器模式(采用继承实现)
2、对象适配器(采用对象组合方式实现)
注意事项:适配器不是在详细设计时添加的,而是解决正在服役的项目的问题。
英文:Adapter
类型:结构型模式
2、类图及组成
(引)类适配器类图:
(引)对象适配器类图:
模式中的角色:
目标接口(Target):客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口
需要适配的类(Adaptee):需要适配的类或适配者类
适配器(Adapter):通过包装一个需要适配的对象,把原接口转换成目标接口
PS:
类适配器使用继承关系复用适配源(Adaptee),因此目标(Target)不能是类,只能是接口(java单继承)。
对象适配器使用委派关系复用适配源(Adaptee),因此目标(Target)可能是类或接口,可以将多个适配源适配到一个目标接口。
3、问题引入
大家都知道,不同国家电源的插头存在差异,不同类型的插头,插座是不能随便使用的;
下面那俄罗斯的插头(双脚圆头)、中国的插头(双脚扁头)来举例说一下适配器
如图: 中间的就是适配插头
4、类适配器
结构说明:
//目标接口,或称为标准接口 public interface Target { //普通功能 public void request(); } //已存在的、具有特殊功能、但不符合我们既有的标准接口的类 public class Adaptee { public void specificRequest() { } } //适配器类,继承了被适配类,同时实现标准接口 public class Adapter extends Adaptee implements Target { @Override public void request() { this.specificRequest(); } }
4-1、实例引入
package com.designpattern.Adapter.ClassAdapter; /** * 俄罗斯插座 * PS: 已存在的、具有特殊功能、但不符合我们既有的标准接口的类 * @author Json */ public class RussiaSocket { //双脚圆形充电 public void specificCharge(){ System.out.println("充电中..."); } }
package com.designpattern.Adapter.ClassAdapter; /** * 自带的充电插头 - 双脚扁头 * PS: 目标接口,或称为标准接口 * @author Json */ public interface MyCharger { public void charge(); }
package com.designpattern.Adapter.ClassAdapter; /** * 电源适配器 * PS: 适配器类,继承了被适配类,同时实现标准接口 * @author Json */ public class PowerAdapter extends RussiaSocket implements MyCharger{ @Override public void charge() { // 这里是使用委托的方式完成特殊功能 System.out.println("我是适配类:双脚扁头充电->可以在->双脚圆形插孔充电."); this.specificCharge(); } }
测试:
package com.designpattern.Adapter.ClassAdapter; /** * 测试 * @author Json */ public class Test { public static void main(String[] args) { MyCharger charger = new PowerAdapter(); //通过适配器调用特殊功能 charger.charge(); } }
结果:
我是适配类:双脚扁头充电->可以在->双脚圆形插孔充电. 充电中...
4-2、类适配器的优缺点
优点:
由于适配器类是适配者类的子类,因此可以再适配器类中置换一些适配者的方法,使得适配器的灵活性更强。
缺点:
对于Java、C#等不支持多重继承的语言,一次最多只能适配一个适配者类,而且目标类只能为接口,不能为类,其使用有一定的局限性,不能将一个适配者类和他的子类同时适配到目标接口。
5、对象适配器
结构说明:
//目标接口,或称为标准接口 public interface Target { //普通功能 public void request(); } //已存在的、具有特殊功能、但不符合我们既有的标准接口的类 public class Adaptee { public void specificRequest() { } } //适配器类,直接关联被适配类,同时实现标准接口 class Adapter implements Target { // 直接关联被适配类 private Adaptee adaptee; // 可以通过构造函数传入具体需要适配的被适配类对象 public Adapter(Adaptee adaptee) { this.adaptee = adaptee; } public void request() { // 这里是使用委托的方式完成特殊功能 this.adaptee.specificRequest(); } }
5-1、实例引入
package com.designpattern.Adapter.ObjectAdapter; /** * 俄罗斯插座 * PS: 已存在的、具有特殊功能、但不符合我们既有的标准接口的类 * @author Json */ public class RussiaSocket { //双脚圆形充电 public void specificCharge(){ System.out.println("充电中..."); } }
package com.designpattern.Adapter.ObjectAdapter; /** * 自带的充电插头 - 双脚扁头 * PS: 目标接口,或称为标准接口 * @author Json */ public interface MyCharger { public void charge(); }
package com.designpattern.Adapter.ObjectAdapter; /** * 电源适配器 * PS: 适配器类,直接关联被适配类,同时实现标准接口 * @author Json */ public class PowerAdapter implements MyCharger{ // 直接关联被适配类 private RussiaSocket russiaSocket; // 可以通过构造函数传入具体需要适配的被适配类对象 public PowerAdapter(RussiaSocket russiaSocket) { this.russiaSocket = russiaSocket; } @Override public void charge(){ // 这里是使用委托的方式完成特殊功能 System.out.println("我是适配类:双脚扁头充电->可以在->双脚圆形插孔充电."); this.russiaSocket.specificCharge(); } }
测试:
package com.designpattern.Adapter.ObjectAdapter; /** * 测试 * @author Json */ public class Test { public static void main(String[] args) { //使用特殊功能类,即适配类, // 需要先创建一个被适配类的对象作为参数 PowerAdapter adapter = new PowerAdapter(new RussiaSocket()); adapter.charge(); } }
结果:
我是适配类:双脚扁头充电->可以在->双脚圆形插孔充电. 充电中...
5-2、对象适配器的优缺点
优点:
把多个不同的适配者适配到同一个目标,也就是说,同一个适配器可以把适配者类和他的子类都适配到目标接口。
缺点:
与类适配器模式相比,要想置换适配者类的方法就不容易。
6、使用场景
1、 系统需要使用现有的类,而此类的接口不符合系统的需要。
2、 想要建立一个可以重用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。
3、 两个类所做的事情相同或相似,但是具有不同接口的时候。
4、 旧的系统开发的类已经实现了一些功能,但是客户端却只能以另外接口的形式访问,但我们不希望手动更改原有类的时候。
5、 使用第三方组件,组件接口定义和自己定义的不同,不希望修改自己的接口,但是要使用第三方组件接口的功能。
具体应用举例:
适配器模式在Java SDK中的运用:
java.io.InputStreamReader(InputStream) (返回一个Reader)
java.io.OutputStreamWriter(OutputStream) (返回一个Writer)
7、关于缺省适配模式
简介:
缺省适配(Default Adapter)模式为一个接口提供缺省实现,这样子类型可以从这个缺省实现进行扩展,而不必从原有接口进行扩展。作为适配器模式的一个特例,缺省是适配模式在JAVA语言中有着特殊的应用。
就是一个抽象类对功能接口的所有功能做空实现 然后子类继承这个抽象类 这样就可以对部分功能进行实现或拓展了;
代码示例:
package com.designpattern.Adapter.DefaultAdapter; /** * 关于人的接口 * @author Json */ public interface Person { public String getName(); //工作 public void work(); //学习 public void study(); //吃饭 public void eat(); //睡觉 public void sleep(); //...很多方法省略 }
package com.designpattern.Adapter.DefaultAdapter; /** * 抽象的缺省适配器 这里全部空实现 * @author Json */ public abstract class PersonAdapter implements Person{ @Override public String getName() { // TODO Auto-generated method stub return null; } @Override public void work() { // TODO Auto-generated method stub } @Override public void study() { // TODO Auto-generated method stub } @Override public void eat() { // TODO Auto-generated method stub } @Override public void sleep() { // TODO Auto-generated method stub } }
package com.designpattern.Adapter.DefaultAdapter; /** * 定义一个学生 继承了交往PersonAdapter抽象类 * 学生 并没有继承 所有Person的行为,比如现在 还不用work * @author Json */ public class Student extends PersonAdapter{ private String name; public Student(String name){ this.name = name; } public String getName() { return name; } public void study() { System.out.println("学习"); } public void eat() { System.out.println("吃饭"); } public void sleep() { System.out.println("睡觉"); } }
测试:
package com.designpattern.Adapter.DefaultAdapter; /** * 测试 * @author Json */ public class Test { public static void main(String[] args) { Person p = new Student("张三"); p.study(); p.eat(); p.sleep(); System.out.println("我是"+p.getName()); } }
结果:
学习
吃饭
睡觉
我是张三
场景:
缺省适配模式可以很好的处理这一情况。可以设计一个抽象的适配器类实现接口,此抽象类要给接口所要求的每一种方法都提供一个空的 方法。就像具体学生类一样 只实现某一部分功能,因为他只用到了某一部分功能而已,如果以后要用,再实现其他功能就是,但是今天他不用,就没必要实现该功能了!
在很多情况下,必须让一个具体类实现某一个接口,但是这个类又用不到接口所规定的所有的方法。通常的处理方法是,这个具体类要实现所有的方法,那些有用的方法要有实现,那些没有用的方法也要实现。
这些空的方法是一种浪费,有时也是一种混乱。除非看过这些空方法的代码,程序员可能会以为这些方法不是空的。即便他知道其中有一些方法是空的,也不一定知道哪些方法是空的,哪些方法不是空的,除非看过这些方法的源代码或是文档。
小结:
适配器模式的用意是要改变原有的接口,以便于适应目标接口。缺省适配的用意稍有不同,它是为了方便建立一个不平庸的适配器类而提供的一种平庸实现。
在任何时候,如果不准备实现一个接口的所有方法时,就可以使用“缺省适配模式”制造一个抽象类,给出所有方法的平庸的具体实现。这样,从这个抽象类再继承下去的子类就不必实现所有的方法了;
8、与其他模式的对比
外观模式 VS 适配器模式:
外观模式(Facade模式):适配器模式的重点是改变一个单独类的API。外观模式的目的是给由许多对象构成的整个子系统,提供更为简洁的接口。而适配器模式就是封装一个单独类,适配器模式经常用在需要第三方API协同工作的场合,设法把你的代码与第三方库隔离开来。
适配器模式与外观模式都是对现相存系统的封装。但这两种模式的意图完全不同,前者使现存系统与正在设计的系统协同工作而后者则为现存系统提供一个更为方便的访问接口。简单地说,适配器模式为事后设计,而外观模式则必须事前设计,因为系统依靠于外观。总之,适配器模式没有引入新的接口,而外观模式则定义了一个全新的接口。
外观模式: 其主要是提供一个整洁的一致的接口给客户端。
适配器模式:将一个接口通过适配来间接转换为另一个接口。
装饰者模式 VS 适配器模式:
装饰者模式(decorator模式):装饰模式增强了其他对象的功能而同时又不改变它的接口。因此装饰模式对应用的透明性比适配器更好。结果是装饰者模式支持递归组合,而纯粹使用适配器是不可能实现这一点的。
装饰者模式:不会改变接口,而是将一个一个的接口进行装饰,也就是添加新的功能。
适配器模式:将一个接口通过适配来间接转换为另一个接口。
桥梁模式 VS 适配器模式:
桥梁模式(bridge模式):桥梁模式与对象适配器类似,但是桥梁模式的出发点不同:桥梁模式目的是将接口部分和实现部分分离,从而对它们可以较为容易也相对独立的加以改变。而对象适配器模式则意味着改变一个已有对象的接口。
9、总结
适配器模式不并是那种会让架构变得更合理的模式,更多的时候它只是充当救火队员的角色,帮助解决由于前期架构设计不合理导致的接口不匹配的问题。
PS:源码地址 https://github.com/JsonShare/DesignPattern/tree/master
PS:原文地址 http://www.cnblogs.com/JsonShare/p/7170267.html