适配器模式
泰国插座用的是两孔的(欧标),可以买个多功能转换插头(适配器),这样就可以使用了。
基本介绍
-
适配器模式(Adapter Pattern)将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)
-
适配器模式属于结构型模式
-
主要分为三类:类适配器模式、对象适配器模式、接口适配器模式
该模式的主要优点如下
-
客户端通过适配器可以透明地调用目标接口
-
复用了现存的类,程序员不需要修改原有代码而重用现有的适配者类
-
将目标类和适配者类解耦,解决了目标类和适配者类接口不一致的问题
其缺点是
对类适配器来说,更换适配器的实现过程比较复杂。
工作原理
-
适配器模式:将一个类的接口转换成另一种接口,让原本接口不兼容的类可以兼容
-
从用户的角度看不到被适配者,是解耦的
-
用户调用适配器转化出来的目标接口方法,适配器再调用被适配者的相关接口方法
-
用户收到反馈结果,感觉只是和目标接口交互,如图
Adapter 类(适配器),通过继承 src 类(被适配者),实现 dst 类接口(适配接口),完成 src → dst 的适配。
类适配器模式
适配器模式(Adapter)包含以下主要角色:
-
目标(Target)接口:当前系统业务所期待的接口,它可以是抽象类或接口
-
适配者(Adaptee)类:它是被访问和适配的现存组件库中的组件接口
-
适配器(Adapter)类:它是一个转换器,通过继承或引用适配者的对象,把适配者接口转换成目标接口,让客户按目标接口的格式访问适配者
应用实例说明
以生活中充电器的例子来讲解适配器,充电器本身相当于 Adapter(即适配器),220V 交流电相当于 src(即被适配者),我们的目标 dst(即目标)是 5V 直流电,使用类适配器模式完成。
代码示例:
1 package com.atguigu.adapter.classadapter; 2 3 // 客户端 4 public class Client { 5 6 public static void main(String[] args) { 7 System.out.println(" === 类适配器模式 ==== "); 8 Phone phone = new Phone(); 9 phone.charging(new VoltageAdapter()); 10 } 11 }
1 package com.atguigu.adapter.classadapter; 2 3 // 目标接口 4 public interface IVoltage5V { 5 6 public int output5V(); 7 }
1 package com.atguigu.adapter.classadapter; 2 3 public class Phone { 4 5 // 充电 6 public void charging(IVoltage5V iVoltage5V) { 7 if(iVoltage5V.output5V() == 5) { 8 System.out.println("电压为 5V, 可以充电~~"); 9 } else if (iVoltage5V.output5V() > 5) { 10 System.out.println("电压大于 5V, 不能充电~~"); 11 } 12 } 13 }
1 package com.atguigu.adapter.classadapter; 2 3 // 被适配的类 4 public class Voltage220V { 5 6 // 输出 220V 的电压 7 public int output220V() { 8 int src = 220; 9 System.out.println("电压=" + src + "伏"); 10 return src; 11 } 12 }
1 package com.atguigu.adapter.classadapter; 2 3 // 适配器类 4 public class VoltageAdapter extends Voltage220V implements IVoltage5V { 5 6 // 通过实现接口的方法将电压转化 7 @Override 8 public int output5V() { 9 // 获取到 220V 电压 10 int srcV = output220V(); 11 // 转成 5V 12 intdstV = srcV / 44; 13 return dstV; 14 } 15 }
类适配器模式注意事项和细节
-
Java 是单继承机制,所以类适配器需要继承 src 类这一点算是一个缺点,因为这要求 dst 必须是接口,有一定局限性
-
src 类的方法在 Adapter 中都会暴露出来,也增加了使用的成本
-
由于其继承了 src 类,所以它可以根据需求重写 src 类的方法,使得 Adapter 的灵活性增强了
对象适配器模式
基本介绍
-
基本思路和类的适配器模式相同,只是将 Adapter 类作修改,不是继承 src 类,而是持有 src 类的实例,以解决兼容性的问题。 即:持有 src 类,实现 dst 类接口,完成 src → dst 的适配
-
根据“合成复用原则”,在系统中尽量使用关联关系(聚合)来替代继承关系
-
对象适配器模式是适配器模式常用的一种
应用实例说明
以生活中充电器的例子来讲解适配器,充电器本身相当于 Adapter (即适配器),220V 交流电相当于 src (即被适配者),我们的目 dst(即目标)是 5V 直流电,使用对象适配器模式完成。
代码示例:
1 package com.atguigu.adapter.objectadapter; 2 3 // 客户端 4 public class Client { 5 6 public static void main(String[] args) { 7 System.out.println(" === 对象适配器模式 ===="); 8 Phone phone = new Phone(); 9 phone.charging(new VoltageAdapter(new Voltage220V())); 10 } 11 }
1 package com.atguigu.adapter.objectadapter; 2 3 // 目标接口 4 public interface IVoltage5V { 5 6 public int output5V(); 7 }
1 package com.atguigu.adapter.objectadapter; 2 3 public class Phone { 4 5 // 充电 6 public void charging(IVoltage5V iVoltage5V) { 7 if(iVoltage5V.output5V() == 5) { 8 System.out.println("电压为 5V, 可以充电~~"); 9 } else if (iVoltage5V.output5V() > 5) { 10 System.out.println("电压大于 5V, 不能充电~~"); 11 } 12 } 13 }
1 package com.atguigu.adapter.objectadapter; 2 3 // 被适配的类 4 public class Voltage220V { 5 6 // 输出 220V 的电压,不变 7 public int output220V() { 8 int src = 220; 9 System.out.println("电压=" + src + "伏"); 10 return src; 11 } 12 }
1 package com.atguigu.adapter.objectadapter; 2 3 // 适配器类 4 public class VoltageAdapter implements IVoltage5V { 5 6 // 关联关系-聚合 7 private Voltage220V voltage220V; 8 9 // 通过构造器,传入一个 Voltage220V 实例 10 public VoltageAdapter(Voltage220V voltage220v) { 11 this.voltage220V = voltage220v; 12 } 13 14 @Override 15 public int output5V() { 16 int dst = 0; 17 if(null != voltage220V) { 18 // 获取 220V 电压 19 int src = voltage220V.output220V(); 20 System.out.println("使用对象适配器,进行适配~~"); 21 dst = src / 44; 22 System.out.println("适配完成,输出的电压为=" + dst); 23 } 24 return dst; 25 } 26 }
对象适配器模式注意事项和细节
-
对象适配器和类适配器其实算是同一种思想,只不过实现方式不同
-
根据合成复用原则,使用组合替代继承,所以它解决了类适配器必须继承 src 的局限性问题,也不再要求 dst 必须是接口
-
使用成本更低,更灵活
接口适配器模式
基本介绍
-
一些书籍称为:适配器模式(Default Adapter Pattern)或缺省适配器模式
-
核心思路:当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求
-
适用于一个接口不想使用其所有的方法的情况
应用实例说明
一、Android 中的属性动画 ValueAnimator 类可以通过 addListener(AnimatorListener listener)方法添加监听器,那么常规写法如下
二、有时候我们不想实现 Animator.AnimatorListener 接口的全部方法,我们只想监听onAnimationStart,我们会如下写:
三、AnimatorListenerAdapter 类,就是一个接口适配器,代码如下图:它空实现了 Animator.AnimatorListener 类(src)的所有方法
四、AnimatorListener 是一个接口,如下:
五、程序里的匿名内部类就是 Listener 具体实现类:
代码示例
1 package com.atguigu.adapter.interfaceadapter; 2 3 public interface Interface4 { 4 5 public void m1(); 6 7 public void m2(); 8 9 public void m3(); 10 11 public void m4(); 12 }
1 package com.atguigu.adapter.interfaceadapter; 2 3 // 在 AbsAdapter 我们将 Interface4 的方法进行默认实现 4 public abstract class AbsAdapter implements Interface4 { 5 6 // 默认实现 7 public void m1() { 8 9 } 10 11 public void m2() { 12 13 } 14 15 public void m3() { 16 17 } 18 19 public void m4() { 20 21 } 22 }
1 package com.atguigu.adapter.interfaceadapter; 2 3 public class Client { 4 5 public static void main(String[] args) { 6 7 AbsAdapter absAdapter = new AbsAdapter() { 8 9 // 只需要去覆盖我们需要使用的接口方法 10 @Override 11 public void m1() { 12 System.out.println("使用了 m1 的方法"); 13 } 14 }; 15 absAdapter.m1(); 16 } 17 }
模式的应用场景
适配器模式(Adapter)通常适用于以下场景
-
以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致
-
使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同
模式的扩展
适配器模式(Adapter)可扩展为双向适配器模式,双向适配器类既可以把适配者接口转换成目标接口,也可以把目标接口转换成适配者接口,其结构图如图所示:
代码示例
1 package adapter; 2 3 // 目标接口 4 interface TwoWayTarget { 5 6 public void request(); 7 } 8 9 // 适配者接口 10 interface TwoWayAdaptee { 11 12 public void specificRequest(); 13 } 14 15 // 目标实现 16 class TargetRealize implements TwoWayTarget { 17 18 public void request() { 19 System.out.println("目标代码被调用!"); 20 } 21 } 22 23 // 适配者实现 24 class AdapteeRealize implements TwoWayAdaptee { 25 26 public void specificRequest() { 27 System.out.println("适配者代码被调用!"); 28 } 29 } 30 31 // 双向适配器 32 class TwoWayAdapter implements TwoWayTarget,TwoWayAdaptee { 33 34 private TwoWayTarget target; 35 private TwoWayAdaptee adaptee; 36 37 public TwoWayAdapter(TwoWayTarget target) { 38 this.target = target; 39 } 40 41 public TwoWayAdapter(TwoWayAdaptee adaptee) { 42 this.adaptee = adaptee; 43 } 44 45 public void request() { 46 adaptee.specificRequest(); 47 } 48 49 public void specificRequest() { 50 target.request(); 51 } 52 } 53 54 // 客户端代码 55 public class TwoWayAdapterTest { 56 57 public static void main(String[] args) { 58 System.out.println("目标通过双向适配器访问适配者:"); 59 TwoWayAdaptee adaptee = new AdapteeRealize(); 60 TwoWayTarget target = new TwoWayAdapter(adaptee); 61 target.request(); 62 System.out.println("-------------------"); 63 System.out.println("适配者通过双向适配器访问目标:"); 64 target = new TargetRealize(); 65 adaptee = new TwoWayAdapter(target); 66 adaptee.specificRequest(); 67 } 68 }
程序的运行结果如下
-
目标通过双向适配器访问适配者:
-
适配者代码被调用!
-
-------------------
-
适配者通过双向适配器访问目标:
-
目标代码被调用!
适配器模式在 SpringMVC 框架应用的源码剖析
SpringMVC 中的 HandlerAdapter,就是用来适配器模式,回顾 SpringMVC 处理请求流程
使用 HandlerAdapter 的原因分析:
可以看到处理器的类型不同,有多重实现方式,那么调用方式就不是确定的,如果需要直接调用 Controller 方 法,需要调用的时候就得不断是使用 if else 来进行判断是哪一种子类然后执行。那么如果后面要扩展 Controller, 就得修改原来的代码,这样违背了 OCP 原则。
源码分析:
动手写SpringMVC通过适配器设计模式获取到对应的Controller的源码
适配器模式的注意事项和细节
-
三种命名方式,是根据 src 是以怎样的形式给到 Adapter (在 Adapter 里的形式)来命名的。
-
类适配器:以类给到,在 Adapter 里,就是将 src 当做类,继承
-
对象适配器:以对象给到,在 Adapter 里,将 src 作为一个对象,持有
-
接口适配器:以接口给到,在 Adapter 里,将 src 作为一个接口,实现
-
Adapter 模式最大的作用还是将原本不兼容的接口融合在一起工作
-
实际开发中,实现起来不拘泥于我们讲解的三种经典形式
原文链接:https://blog.csdn.net/qq784515681/article/details/105648659