2.适配器模式分类
有三种分类:
- 类适配器 (通过引用适配者进行组合实现)
- 对象适配器(通过继承适配者进行实现)
- 接口适配器 (通过抽象类来实现适配)
前二者在实现上有些许区别,作用一样,第三个接口适配器差别较大。
3.适配器的实例讲解
(1)类适配器模式
原理:通过继承来实现适配器功能。
当我们要访问的接口A中没有我们想要的方法 ,却在另一个接口B中发现了合适的方法,我们又不能改变访问接口A,在这种情况下,我们可以定义一个适配器p来进行中转,这个适配器p要实现我们访问的接口A,这样我们就能继续访问当前接口A中的方法(虽然它目前不是我们的菜),然后再继承接口B的实现类,这样我们可以在适配器P中访问接口B的方法了,这时我们在适配器P中的接口A方法中直接引用B中的合适方法,这样就完成了一个简单的类适配器。
(2)对象适配器模式
原理:通过组合来实现适配器功能。
基本思路和类的适配器模式相同,只是将Adapter类作修改,这次不继承src类,而是持有src类的实例,以解决兼容性的问题。
即:持有 src类,实现 dst 类接口,完成src->dst的适配。
(根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系,因此大部分结构型模式都是对象结构型模式。)
设计模式六大原则——合成/聚合复用原则
为什么“要尽量使用合成和聚合,尽量不要使用继承”呢?
这是因为:
第一,继承复用破坏包装,它把父类的实现细节直接暴露给了子类,这违背了信息隐藏的原则;
第二:如果父类发生了改变,那么子类也要发生相应的改变,这就直接导致了类与类之间的高耦合,不利于类的扩展、复用、维护等,也带来了系统僵硬和脆弱的设计。而用合成和聚合的时候新对象和已有对象的交互往往是通过接口或者抽象类进行的,就可以很好的避免上面的不足,而且这也可以让每一个新的类专注于实现自己的任务,符合单一职责原则。
(3)接口适配器模式
原理:通过抽象类来实现适配,这种适配稍别于上面所述的适配。
当存在这样一个接口,其中定义了N多的方法,而我们现在却只想使用其中的一个到几个方法,如果我们直接实现接口,那么我们要对所有的方法进行实现,哪怕我们仅仅是对不需要的方法进行置空(只写一对大括号,不做具体方法实现)也会导致这个类变得臃肿,调用也不方便,这时我们可以使用一个抽象类作为中间件,即适配器,用这个抽象类实现接口,而在抽象类中所有的方法都进行置空,那么我们在创建抽象类的继承类,而且重写我们需要使用的那几个方法即可。
适配器模式应用场景
类适配器与对象适配器的使用场景一致,仅仅是实现手段稍有区别,二者主要用于如下场景:
(1)想要使用一个已经存在的类,但是它却不符合现有的接口规范,导致无法直接去访问,这时创建一个适配器就能间接去访问这个类中的方法。
(2)我们有一个类,想将其设计为可重用的类(可被多处访问),我们可以创建适配器来将这个类来适配其他没有提供合适接口的类。
以上两个场景其实就是从两个角度来描述一类问题,那就是要访问的方法不在合适的接口里,一个从接口出发(被访问),一个从访问出发(主动访问)。
接口适配器使用场景(重要例子):
(1)想要使用接口中的某个或某些方法,但是接口中有太多方法,我们要使用时必须实现接口并实现其中的所有方法,可以使用抽象类来实现接口,并不对方法进行实现(仅置空),然后我们再继承这个抽象类来通过重写想用的方法的方式来实现。这个抽象类就是适配器。
在spring的体现: Spring AOP 模块对 BeforeAdvice、 AfterAdvice、 ThrowsAdvice 三种通知类型的支持实际上是借助适配器模式来实现的, 这样的好处是使得框架允许用户向框架中加入自己想要支持的任何一种通知类型, 上述三种通知类型是 Spring AOP 模块定义的, 它们是 AOP 联盟定义的 Advice 的子类型。 在spring中 基本adapter结尾都是适配器
一 概述
定义:适配器模式将某个类的接口转换成客户端期望的另一个接口表示,主的目的是兼容性,让原本因接口不匹配不能一起工作的两个类可以协同工作。其别名为包装器(Wrapper)。
属于结构型模式
主要分为三类:类适配器模式、对象的适配器模式、接口的适配器模式。
本文定义:
需要被适配的类、接口、对象(我们有的),简称 src(source)
最终需要的输出(我们想要的),简称 dst (destination,即Target)
适配器称之为 Adapter 。
一句话描述适配器模式的感觉: src->Adapter->dst,即src以某种形式(三种形式分别对应三种适配器模式)给到Adapter里,最终转化成了dst。
拿我们Android开发最熟悉的展示列表数据的三大控件:ListView,GridView,RecyclerView的Adapter来说,它们三个控件需要的是View(dst),而我们有的一般是datas(src),所以适配器Adapter就是完成了数据源datas 转化成 ItemView的工作。
带入src->Adapter->dst中,即datas->Adapter->View.
使用场景:
1 系统需要使用现有的类,而这些类的接口不符合系统的需要。
2 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。
3 需要一个统一的输出接口,而输入端的类型不可预知。
二 类适配器模式:
一句话描述:Adapter类,通过继承 src类,实现 dst 类接口,完成src->dst的适配。
别的文章都用生活中充电器的例子来讲解适配器,的确,这是个极佳的举例,本文也不能免俗:
充电器本身相当于Adapter,220V交流电相当于src,我们的目dst标是5V直流电。
我们现有的src类:
public class Voltage220 { public int output220V() { int src = 220; System.out.println("我是" + src + "V"); return src; } }
我们想要的dst接口:
public interface Voltage5 { int output5V(); }
适配器类:
public class VoltageAdapter extends Voltage220 implements Voltage5 { @Override public int output5V() { int src = output220V(); System.out.println("适配器工作开始适配电压"); int dst = src / 44; System.out.println("适配完成后输出电压:" + dst); return dst; } }
Client类:
public class Mobile { /** * 充电方法 * * @param voltage5 */ public void charging(Voltage5 voltage5) { if (voltage5.output5V() == 5) { System.out.println("电压刚刚好5V,开始充电"); } else if (voltage5.output5V() > 5) { System.out.println("电压超过5V,都闪开 我要变成note7了"); } } }
测试代码:
System.out.println("===============类适配器=============="); Mobile mobile = new Mobile(); mobile.charging(new VoltageAdapter());
输出:
===============类适配器============== 我是220V 适配器工作开始适配电压 适配完成后输出电压:5 电压刚刚好5V,开始充电
类图如下:
小结:
Java这种单继承的机制,所有需要继承的我个人都不太喜欢。
所以类适配器需要继承src类这一点算是一个缺点,
因为这要求dst必须是接口,有一定局限性;
且src类的方法在Adapter中都会暴露出来,也增加了使用的成本。
但同样由于其继承了src类,所以它可以根据需求重写src类的方法,使得Adapter的灵活性增强了。
三 对象适配器模式(常用):
基本思路和类的适配器模式相同,只是将Adapter类作修改,这次不继承src类,而是持有src类的实例,以解决兼容性的问题。
即:持有 src类,实现 dst 类接口,完成src->dst的适配。
(根据“合成复用原则”,在系统中尽量使用关联关系来替代继承关系,因此大部分结构型模式都是对象结构型模式。)
设计模式六大原则——合成/聚合复用原则
为什么“要尽量使用合成和聚合,尽量不要使用继承”呢?
这是因为:
第一,继承复用破坏包装,它把父类的实现细节直接暴露给了子类,这违背了信息隐藏的原则;
第二:如果父类发生了改变,那么子类也要发生相应的改变,这就直接导致了类与类之间的高耦合,不利于类的扩展、复用、维护等,也带来了系统僵硬和脆弱的设计。而用合成和聚合的时候新对象和已有对象的交互往往是通过接口或者抽象类进行的,就可以很好的避免上面的不足,而且这也可以让每一个新的类专注于实现自己的任务,符合单一职责原则。
Adapter类如下:
public class VoltageAdapter2 implements Voltage5 { private Voltage220 mVoltage220; public VoltageAdapter2(Voltage220 voltage220) { mVoltage220 = voltage220; } @Override public int output5V() { int dst = 0; if (null != mVoltage220) { int src = mVoltage220.output220V(); System.out.println("对象适配器工作,开始适配电压"); dst = src / 44; System.out.println("适配完成后输出电压:" + dst); } return dst; } }
测试代码:
System.out.println(" ===============对象适配器=============="); VoltageAdapter2 voltageAdapter2 = new VoltageAdapter2(new Voltage220()); Mobile mobile2 = new Mobile(); mobile2.charging(voltageAdapter2);
输出:
===============对象适配器============== 我是220V 对象适配器工作,开始适配电压 适配完成后输出电压:5 电压刚刚好5V,开始充电
小结:
对象适配器和类适配器其实算是同一种思想,只不过实现方式不同。
根据合成复用原则,组合大于继承,
所以它解决了类适配器必须继承src的局限性问题,也不再强求dst必须是接口。
同样的它使用成本更低,更灵活。
(和装饰者模式初学时可能会弄混,这里要搞清,装饰者是对src的装饰,使用者毫无察觉到src已经被装饰了(使用者用法不变)。 这里对象适配以后,使用者的用法还是变的。
即,装饰者用法: setSrc->setSrc,对象适配器用法:setSrc->setAdapter.)
四 接口适配器模式
也有文献称之为认适配器模式(Default Adapter Pattern)或缺省适配器模式。
定义:
当不需要全部实现接口提供的方法时,可先设计一个抽象类实现接口,并为该接口中每个方法提供一个默认实现(空方法),那么该抽象类的子类可有选择地覆盖父类的某些方法来实现需求,它适用于一个接口不想使用其所有的方法的情况。
我们直接进入大家最喜爱的源码撑腰环节:
源码撑腰环节:
Android中的属性动画ValueAnimator
类可以通过addListener(AnimatorListener listener)
方法添加监听器,
那么常规写法如下:
ValueAnimator valueAnimator = ValueAnimator.ofInt(0,100); valueAnimator.addListener(new Animator.AnimatorListener() { @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { } @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } }); valueAnimator.start();
有时候我们不想实现Animator.AnimatorListener
接口的全部方法,我们只想监听onAnimationStart
,我们会如下写:
ValueAnimator valueAnimator = ValueAnimator.ofInt(0,100); valueAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { //xxxx具体实现 } }); valueAnimator.start();
显然,这个AnimatorListenerAdapter
类,就是一个接口适配器。
查看该Adapter类源码:
public abstract class AnimatorListenerAdapter implements Animator.AnimatorListener, Animator.AnimatorPauseListener { @Override public void onAnimationCancel(Animator animation) { } @Override public void onAnimationEnd(Animator animation) { } @Override public void onAnimationRepeat(Animator animation) { } @Override public void onAnimationStart(Animator animation) { } @Override public void onAnimationPause(Animator animation) { } @Override public void onAnimationResume(Animator animation) { } }
可见,它空实现了Animator.AnimatorListener
类(src)的所有方法.
对应的src类:
public static interface AnimatorListener { void onAnimationStart(Animator animation); void onAnimationEnd(Animator animation); void onAnimationCancel(Animator animation); void onAnimationRepeat(Animator animation); }
类图:
我们程序里的匿名内部类就是Listener1 2 这种具体实现类。
new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { //xxxx具体实现 } }
接口适配器模式很好理解,令我们的程序更加简洁明了。
五 总结
我个人理解,三种命名方式,是根据 src是以怎样的形式给到Adapter(在Adapter里的形式)来命名的。
类适配器,以类给到,在Adapter里,就是将src当做类,继承,
对象适配器,以对象给到,在Adapter里,将src作为一个对象,持有。
接口适配器,以接口给到,在Adapter里,将src作为一个接口,实现。
Adapter模式最大的作用还是将原本不兼容的接口融合在一起工作。
适配器模式应用场景
类适配器与对象适配器的使用场景一致,仅仅是实现手段稍有区别,二者主要用于如下场景:
(1)想要使用一个已经存在的类,但是它却不符合现有的接口规范,导致无法直接去访问,这时创建一个适配器就能间接去访问这个类中的方法。
(2)我们有一个类,想将其设计为可重用的类(可被多处访问),我们可以创建适配器来将这个类来适配其他没有提供合适接口的类。
以上两个场景其实就是从两个角度来描述一类问题,那就是要访问的方法不在合适的接口里,一个从接口出发(被访问),一个从访问出发(主动访问)。
接口适配器使用场景:
(1)想要使用接口中的某个或某些方法,但是接口中有太多方法,我们要使用时必须实现接口并实现其中的所有方法,可以使用抽象类来实现接口,并不对方法进行实现(仅置空),然后我们再继承这个抽象类来通过重写想用的方法的方式来实现。这个抽象类就是适配器。
总的来说,适配器模式属于补偿模式,专门用来在系统后期扩展、修改时使用,但要注意不要过度使用适配器模式。
https://blog.csdn.net/zxt0601/article/details/52848004
https://www.cnblogs.com/V1haoge/p/6479118.html