当前位置: 首页 > 科技观察

设计模式系列—适配器模式

时间:2023-03-21 21:51:29 科技观察

前言23种设计模式速记单例模式工厂方法模式抽象工厂模式Builder/Builder模式原型模式分享享元模式和门面模式持续更新中...23种设计模式快速记忆,请参阅上面的第一篇文章。本文将与您一起学习适配器模式。适配器模式包括类的适配器模式和对象的适配器模式。模式定义将类的接口转换为客户期望的另一个接口。Adapter模式使那些由于接口不兼容而无法一起工作的类能够一起工作。适配器模式的形式分为:类适配器模式&对象适配器模式。类的适配器模式类的适配器模式是将适配类的API转换为目标类的API。上图可以看出:冲突:Target希望调用操作方法,而Adaptee没有(这就是所谓的不兼容)。解决方案:为了使Target能够使用Adaptee类中的SpecificOperation方法,提供了一个中间链接Adapter类(继承Adaptee&实现Target接口),将Adaptee的API与Target的API连接起来(适配)。Adapter和Adaptee有继承关系,决定了这个adapter模式是一个类的使用步骤(代码分析)第一步:创建Target接口;interfaceTarget{//这是源类Adaptee没有的方法voidoperation();}Step2:创建Source类(Adaptee)classAdaptee{publicvoidSpecificOperation(){}}Step3:创建适配器类(Adapter)//AdapterAdapter继承自Adaptee,同时实现了目标(Target)接口。classAdapterextendsAdapteeimplementsTarget{//目标接口需要调用operation()的方法名,但是源类Adaptee没有方法operation()//所以适配器添加了这个方法名//但实际上operation()只是调用了SpecificOpertaion()源类Adaptee方法的内容//所以适配器只是将SpecificOpertaion()方法封装成一个可以被Target调用的operation()@Overridepublicvoidoperation(){this.SpecificOperation();}}Step4:定义目标类的具体使用,通过Adapter类调用需要的方法实现目标publicclassAdapterPattern{publicstaticvoidmain(String[]args){TargetmAdapter=newAdapter();mAdapter.operation();}}适配器模式对象的模式与类的适配器模式相同。适配器模式也是将适配类的API转换成目标类的API。与类适配器模式不同,对象适配器模式不使用继承来连接Adaptee类,而是使用委托连接Adaptee类。上图可以看出:冲突:Target希望调用操作方法,而Adaptee没有(这就是所谓的不兼容)。解决方案:为了使Target能够使用Adaptee类中的SpecificOperation方法,提供了一个中间链接Adapter类(封装了一个Adaptee的实例),用于连接Adaptee的API和Target的API(适配)。Adapter和Adaptee是委托关系,这就决定了Adapter模式是一个对象。使用步骤(代码分析)第一步:创建Target接口;interfaceTarget{//这是源类Adaptee没有的方法voidoperation();}第二步:创建源类(Adaptee)classAdaptee{publicvoidSpecificOpertaion(){}}第三步:创建适配器类(Adapter)(继承不适用但委托)classAdapterimplementsTarget{//直接关联适配类privateAdapteeadaptee;//具体需要适配的适配类对象可以通过构造函数传入publicAdapter(Adapteeadaptee){this.adaptee=adaptee;}@Overridepublicvoidoperation(){//这里是使用委托来完成特殊功能this.adaptee.SpecificOpertaion();}}第四步:定义具体的使用目标类,通过Adapter类调用需要的方法所以如达到目的publicclassAdapterPattern{publicstaticvoidmain(String[]args){//第四步:定义目标类的具体使用,通过Adapter类调用需要的方法达到目的//需要创建一个objec适配类的t作为参数TargetmAdapter=newAdapter(newAdapter(newAdaptee());mAdapter.operation();}}两种适配器比较对象适配器:使用组合,不仅可以适配一个适配类,还可以适应它的任何子类类;类适配器:只能适配一个特定的类,但不需要重新实现整个适配器的功能。并且它也可以覆盖被适应者的行为;对象适配器:使用组合而不是继承,通过多写几行代码将事情委托给适配器。这是非常灵活的;类适配器:需要一个适配器和一个适配器,只需要一个类;对象适配器:任何添加到适配器上的行为都对适配器及其子类起作用;...解决的问题从模式的定义可以看出,适配器模式是用来转换接口,解决不兼容问题的。想想我们现实生活中的适配器。最常用的是手机充电器,也叫电源适配器,把家里的强交流电转换成手机用的弱直流电。其中,交流电源是适配器,充电器是适配器,手机是用电设备。由于接口不兼容而无法一起工作的类可以一起工作。ModeComposition组合(角色)角色客户端(Client)只能调用目标接口的函数,不能直接使用适配器,但可以通过适配器的接口转换间接使用适配器。目标接口(Target)客户端看到的接口,适配器必须实现这个接口才能被客户端使用。适配器(Adapter)适配器将适配器接口转换成目标接口,提供给客户。Adaptee(适配器)适配器接口与目标接口不兼容,需要将适配器转换成目标接口的子类才能被客户端使用。实例说明这里以类适配器模式为例,对象适配器模式只是在实现适配类时将“继承”改为“在内部委托Adaptee类”。实例概览背景:隔壁老王买了一台进口电视机。冲突:进口电视要求的电压(110V)与国产插头的标准输出电压(220V)不兼容。解决方法:设置一个适配器,将插头输出的220V转换成110V。在adapter模式下使用类的adapter模式的步骤Step1:CreateaTargetinterface(theexpectedplug):canoutput110V(convert220Vto110V)interfaceTarget{//convert220Vtooutput110V(原插头(Adaptee)doesnot(of)voidconvert_110v();第二步:创建源类(原插头)classPowerPort220V{//原插头只能输出220Vpublicvoidoutput_220v(){}}第三步:创建适配器类(Adapter)classAdapter220VextendsPowerPort220VimplementsTarget{//预期的plug需要调用convert_110v(),但是原来的plug没有//所以adapter加了这个方法名//但实际上convert_110v()只是调用了convert_220v()方法的内容originalplug//所以adapter只是把output_220v()作为一层封装,封装成可以被Target调用的convert_110v()@Overridepublicvoidconvert_110v(){this.output_220v();}}第四步:定义具体使用目标类,并通过调用所需的方法实现目标的Adapter类(无需通过原插件)//导入TV类通过Adapter类的方法publicclassAdapterPattern{publicstaticvoidmain(String[]args){TargetmAdapter220V=newAdapter220V();ImportedMachinemImportedMachine=newImportedMachine();//用户拿着进口电视插上适配器(调用Convert_110v()方法)//然后将适配器插入原装插头(Convert_110v()方法内部调用Output_220v()方法输出220V)//适配器只是一个外壳,对外提供110V,但本质还是220V供电,适配器让不兼容的接口兼容,解耦客户端实现的接口。有了适配器,客户端每次调用不兼容的接口时都不需要修改自己的代码,而只需要调用合适的适配器即可。使用对象组合设计原则。Adaptee采用组合方式封装,Adaptee的任何子类都可以与同一个Adapter一起使用。体现了“开闭”的原则。适配器模式将客户端绑定到接口,而不是特定的实现。我们可以使用多个适配器来转换多个后台类,也可以方便地添加新的适配器。缺点每个适配者都需要一个适配器。当适配器过多时,会增加系统复杂度,降低运行时性能。实现适配器可能需要大量工作,增加了开发难度。应用场景当要使用的两个类做相同或相似的事情,但具有不同的接口时,请考虑使用适配器模式。**适配器模式用于客户端调用接口的代码需要统一,调用的接口存在不兼容问题的情况。**这样客户端只需要调用一个接口,可以更简单、更直接、更紧凑。建议尽量使用对象的适配器模式,多使用组合/聚合,少使用继承。当然具体问题具体分析,根据需要选择合适的实现方式。源码中的应用)(returnssaWriter)java.util.collections#enumeration(),从迭代器到枚举的改编。#Springorg.springframework.context.event.GenericApplicationListenerAdapterArrays.asList()在使用工具类Arrays.asList()将数组转换为集合时,不能使用它来修改集合相关的方法。它的add/remove/clear方法会抛出UnsupportedOperationException异常。说明:asList的返回对象是一个Arrays内部类,没有实现集合的修改方法。Arrays.asList体现的是适配器模式,只转换接口,后台的数据还是数组。GenericApplicationListenerAdapterspring架构体系中的事件模型,面向事件的编程可以让你的应用程序更具扩展性,更美观,更有设计感。这也是最常见的解耦方式。首先看类图。ApplicationListener事件监听接口,基于观察者模式实现。GenericApplicationListener处理是基于一个通用的事件监听器接口,提供一个基于事件类型的监听,如下:booleansupportsEventType(ResolvableTypeeventType);是SmartApplicationListener的改进版本。SmartApplicationListener基于事件的监听器接口如下:booleansupportsEventType(ClasseventType);ApplicationListenerMethodAdapterGenericApplicationListener适配器实现如下:publicclassApplicationListenerMethodAdapterimplementsGenericApplicationListener可以看出是通过实现接口的适配器方式实现的。为什么实现接口的方式比继承类的方式更具扩展性?Java是单继承。使用实现接口的方法可以间接实现多重继承,更具扩展性。SourceFilteringListener是基于GenericApplicationListener、SmartApplicationListener的装饰器模式实现的,从指定的事件源中过滤事件,并调用其委托监听器匹配应用事件对象。GenericApplicationListenerAdapterGenericApplicationListener适配器模式实现。PS:以上代码提交在Github上:https://github.com/Niuh-Study/niuh-designpatterns.git