回到博客导航
设计用意
适配器模式(Adapter Pattern)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它联合了两个独立接口的性能。
在某些时候,客户冀望取得某种性能接口但现有的接口无奈满足客户的需要,例如美国的失常供电电压为110V,一个中国人带了一款中国制作电器去美国,这个电器必须要在220V电压下能力充电应用。这种状况下,客户(中国人)的冀望接口是有一个220V的电压为电器充电
,但理论的接口是仅有一个110V的电压供电器充电
,这种状况下就须要采纳一根电压转换器(适配器)使得110V的电压可能转换为220V的电压,供客户应用。
将一个类的接口转换成客户心愿的另外一个接口,这就是适配器须要做的事件,适配器模式使得本来因为接口不兼容而不能一起工作的那些类能够一起工作。
实用条件
- 零碎须要应用现有的类,而此类的接口不合乎零碎的须要(外围需要)。
- 想要建设一个能够重复使用的适配器类,用于与一些彼此之间没有太大关联的一些类,包含一些可能在未来引进的类一起工作,这些源类不肯定有统一的接口,但通过适配器使得它们都具备统一的接口。
- 通过接口转换,将一个类插入另一个类系中。(比方老虎和走兽,当初多了一个飞虎,在不减少实体的需要下,减少一个适配器,在外面容纳一个虎对象,实现飞的接口。)
设计
通常有两种形式实现适配器模式,一种是类适配器,类适配器目前已不太应用,另一种实现形式是对象适配器,通常状况下采纳对象适配器会使得代码更易扩大与保护。
不论采纳何种形式,其根本的实现思维都是:对现有接口的实现类进行扩大,使其实现客户冀望的指标接口。
类适配器通过继承现有接口类并实现目标接口,这样的话会使得现有接口类齐全对适配器裸露,使得适配器具备现有接口类的全副性能,毁坏了封装性。此外从逻辑上来说,这也是不合乎常理的,适配器要做的是扩大现有接口类的性能而不是代替,类适配器只有在特定条件下会被应用。
对象适配器持有现有接口类一个实例,并扩大其性能,实现目标接口。这是举荐的形式,优先采纳组合而不是继承,会使得代码更利于保护。此外,这也是十分合乎常理的——“给我一根线,让我来给他加长到5m,我并不需要晓得这跟线是什么组成的,因为我的工作就是让线加长到5m”——咱们扩大了相应性能而并不关怀其具体实现。
类适配器结构图:
对象适配器结构图:
- Target:客户冀望取得的性能接口(220V电压供电)。
- Cilent:客户,冀望拜访Target接口(客户冀望能有220V电压)。
- Adaptee:现有接口,这个接口须要被适配(现有110V电压供电,须要被适配至220V)。
- Adapter:适配器类,适配现有接口使其合乎客户需要接口(适配110V电压,使其变为220V电压)。
在适配器模式中,Cilent调用Adapter以取得相应性能,Adapter扩大Adaptee以实现对应性能。
代码示例
类适配器:
//客户冀望的接口——220V的电压充电interface Target { void chargeBy220V();}//现有接口——只能通过110V电压充电interface Adaptee { void chargeBy110V();}//现有接口的具体实现类,美国供电器——通过110V电压供电class americanCharger implements Adaptee { @Override public void chargeBy110V() { System.out.println("美国供电器,只为你服务,正在通过110V电压为您充电"); }}//类适配器,通过继承现有接口来实现对现有接口的扩大class Adapter extends americanCharger implements Target { @Override public void chargeBy220V() { super.chargeBy110V();//现有性能 System.out.println("再加110V,达到220V,冲鸭!");//对现有性能扩大 }}//测试类public class Test { public static void main(String[] args) throws FileNotFoundException { //类适配器使得代码逻辑凌乱 //这种状况下好像Adapter是一种110V的美国供电器能够间接应用不须要其余信息 //具体能够和对象适配器比照以下 new Adapter().chargeBy220V(); }}//输入/*美国供电器,只为你服务,正在通过110V电压为您充电再加110V,达到220V,冲鸭!*/
对象适配器:
//客户冀望的接口——220V的电压充电interface Target { void chargeBy220V();}//现有接口——只能通过110V电压充电interface Adaptee { void chargeBy110V();}//现有接口的具体实现类,美国供电器——通过110V电压供电class americanCharger implements Adaptee { @Override public void chargeBy110V() { System.out.println("美国供电器,只为你服务,正在通过110V电压为您充电"); }}//类适配器,通过继承现有接口来实现对现有接口的扩大,使得可能110V供电class Adapter implements Target { Adaptee adaptee;//持有现有接口具体实现对象的援用 public Adapter(Adaptee adaptee) { this.adaptee = adaptee; } @Override public void chargeBy220V() { adaptee.chargeBy110V();//该对象的现有性能 System.out.println("再加110V,达到220V,冲鸭!");//对现有性能扩大 }}//测试类public class Test { public static void main(String[] args) throws FileNotFoundException { //当初咱们有一个美国110V供电站,但咱们无奈应用 Adaptee adaptee = new americanCharger(); //咱们将这个供电器交给适配器,适配器转换为220V供电器 Adapter adapter = new Adapter(adaptee); //接下来咱们通过适配器充电就好了 adapter.chargeBy220V(); }}//输入同上
对象适配器采纳组合的形式实现对现有接口的扩大以达到客户冀望的接口。
让咱们来看JavaIO流中的一个实例:
FileInputStream fis = new FileInputStream("qe");InputStreamReader isrAdapter = new InputStreamReader(fis);BufferedReader bf = new BufferedReader(isrAdapter);
BufferedReader(此处为客户)须要读取文件字符流进行工作,读取文件字符流就是客户的需要局部,然而依据现有的接口,想要读取文件就只能读取字节流,FileInputStream就是现有接口的一个具体实现类,为了满足客户的需要,咱们要对现有的接口进行适配,InputStreamReader就是一个适配器,它持有一个现有接口类的实例,通过这个实例读取文件字节流并将其扩大为字符流以满足客户的需要,这是规范的对象适配器模式。如果认真钻研源码,发现JavaIO库将适配器定义为形象的,并由具体的适配器继承该形象适配器,如这里的InputStreamReader就是具体的适配器之一。
如果实现适配有多种形式的话,咱们能够将适配器类Adapter申明为抽象类,并由子类扩大它:
//客户冀望的接口——220V的电压充电interface Target { void chargeBy220V();}//现有接口——只能通过110V电压充电interface Adaptee { void chargeBy110V();}//现有接口的具体实现类,美国供电器——通过110V电压供电class americanCharger implements Adaptee { @Override public void chargeBy110V() { System.out.println("美国供电器,只为你服务,正在通过110V电压为您充电"); }}//抽象类适配器,通过继承现有接口来实现对现有接口的扩大abstract class Adapter implements Target { Adaptee adaptee;//持有现有接口具体实现对象的援用 public Adapter(Adaptee adaptee) { this.adaptee = adaptee; }}//中国自制class ChinaMakeAdapter extends Adapter { public ChinaMakeAdapter(Adaptee adaptee) { super(adaptee); } @Override public void chargeBy220V() { adaptee.chargeBy110V();//该对象的现有性能 System.out.println("再加110V,达到220V,认准中国制作,冲鸭!");//对现有性能扩大 }}//测试类public class Test { public static void main(String[] args) throws FileNotFoundException { //当初咱们有一个美国110V供电站,但咱们无奈应用 Adaptee adaptee = new americanCharger(); //咱们将这个供电站交给中国制作的适配器 Adapter adapter = new ChinaMakeAdapter(adaptee); //接下来咱们通过适配器充电就好了 adapter.chargeBy220V(); }}//输入同上
此外能够适配器还通过实现两个接口以达到双向适配的目标,即从接口A能够适配到接口B,从接口B也能够适配到接口A,这种状况并不常见。
//接口A——220V的电压供电interface A { void chargeBy220V();}//接口A的具体实现类,中国供电器——通过220V电压供电class ChinaCharger implements A { @Override public void chargeBy220V() { System.out.println("220V电压中国充电,值得信赖"); }}//接口B——110V电压供电interface B { void chargeBy110V();}//接口B的具体实现类,美国供电器——通过110V电压供电class AmericanCharger implements B { @Override public void chargeBy110V() { System.out.println("美国充电器,只为你服务,正在通过110V电压为您充电"); }}//双向适配器class Adapter implements A, B { A a; //220V充电 B b; //110V充电 public Adapter(A a) { this.a = a; } public Adapter(B b) { this.b = b; } @Override public void chargeBy220V() { b.chargeBy110V(); //以后接口 System.out.println("加码,加到220V!!");//适配指标接口 } @Override public void chargeBy110V() { a.chargeBy220V();//以后接口 System.out.println("缓冲电压,当初是110V了"); }}//测试类public class Test { public static void main(String[] args) throws FileNotFoundException { //咱们去美国,酒店里有一个美国110V充电站,咱们须要220V的电压 B b = new AmericanCharger(); //咱们将这个充电站交给适配器以获取220V电压充电 Adapter adapter1 = new Adapter(b); //接下来咱们通过适配器充电就好了 adapter1.chargeBy220V(); System.out.println(); //美国人来中国,酒店里有一个中国220V充电站,但他须要110V的电压 A a = new ChinaCharger(); //将这个充电站交给适配器以获取110V电压充电 Adapter adapter2 = new Adapter(a); //接下来咱们通过适配器充电就好了 adapter2.chargeBy110V(); }}//输入/*美国充电器,只为你服务,正在通过110V电压为您充电加码,加到220V!!220V电压中国充电,值得信赖缓冲电压,当初是110V了Process finished with exit code 0*/
通过实现两个接口的形式,达到不同接口的双向适配,在某些状况下还是很实用的,例如TypeC—USB接口转换器,既能从typeC转USB也能从USB转typeC。
适配器模式总结
长处:
- 能够让任何两个没有关联的类一起运行。
- 进步了类的复用,能够统一化多个不同接口。
- 将现有接口实现类暗藏,减少了类的透明度。
- 灵活性高,可自在适配。
毛病:
- 过多地应用适配器,会让零碎十分零乱,不易整体进行把握。比方,明明看到调用的是 A 接口,其实外部被适配成了 B 接口的实现,一个零碎如果太多呈现这种状况,无异于一场劫难。因而如果不是很有必要,能够不应用适配器,而是间接对系统进行重构。
- 某些适配工作可能十分艰难,例如让房子飞起来。
当咱们有动机地批改一个失常运行的零碎的接口,这时应该思考应用适配器模式。
注意事项:适配器不是在具体设计时增加的,而是解决正在退役的我的项目的问题,即现有接口可能无奈扭转(去美国不可能把人家110V电压供应改成220V电压供应)。