当前位置: 首页 > 后端技术 > Java

深入理解适配器模式(AdapterPattern)

时间:2023-04-02 00:23:18 Java

文章已收录到我的仓库:Java学习笔记和免费书籍分享设计意图适配器模式(AdapterPattern)作为两个不兼容接口之间的桥梁。这种类型的设计模式是一种结构模式,结合了两个独立界面的功能。在某个时刻,客户期望获得某种功能接口,但现有的接口不能满足客户的需求。比如美国正常的电源电压是110V,一个中国人把一个中国制造的电器带到美国。只能在220V电压下充电。本案例中,客户(中国人)期望接口有220V电压给电器充电,但实际接口只有110V电压电源充电。在这种情况下,需要电压转换器。(适配器)使110V的电压转换为220V供客户使用。将一个类的接口转换成客户想要的另一个接口是适配器需要做的事情。适配器模式使由于接口不兼容而无法一起工作的类能够一起工作。适用条件系统需要使用已有的类,该类的接口不满足系统的需求(核心需求)。我想创建一个可重用的适配器类来处理一些彼此关系不大的类,包括一些将来可能会引入的类。这些源类不一定具有一致的接口,而是通过适配器使它们都具有一致的接口。通过接口转换,一个类被插入到另一个类族中。(比如老虎和鸟,现在有飞虎,在不增加实体要求的情况下,增加一个适配器,里面包含一个老虎对象,实现飞翔的接口。)适配器通常有两种实现方式pattern在设计上,一个是类适配器。类适配器目前用的不多。另一种实现方法是对象适配器。通常,使用对象适配器会使代码更易于扩展和维护。无论采用哪种方式,基本的实现思路都是:扩展已有接口的实现类,使其实现客户期望的目标接口。类适配器继承现有接口类并实现目标接口。这样,现有的接口类就会完全暴露给适配器,使适配器拥有现有接口类的所有功能,破坏了封装性。另外,从逻辑上讲,这也是不合理的。适配器需要做的是扩展现有接口类的功能,而不是替换它。类适配器只会在特定条件下使用。对象适配器持有现有接口类的实例并扩展其功能以实现目标接口。这是推荐的方式,更喜欢组合而不是继承,这将使代码更易于维护。另外,这也是很常识——“给我一根线,让我加长到5m,我不需要知道线是用什么做的,因为我的工作就是把线加长到5m”——我们扩展相应的功能,并不关心它的具体实现。类适配器结构图:对象适配器结构图:对象:客户期望的功能接口(220V电压供电)。Cilent:客户希望接入Target接口(客户希望有220V电压)。Adaptee:现有接口,此接口需要适配(现有110V电压电源需要适配为220V)。Adapter:Adapter类,将现有接口适配为客户需求接口(适配110V电压变成220V电压)。Adapter模式下,Cilent调用Adapter获取相应功能,Adapter扩展Adaptee实现相应功能。代码示例类适配器://客户期望的接口-220V电压充电接口Target{voidchargeBy220V();}//现有接口-只能用110V电压充电接口Adaptee{voidchargeBy110V();}//具体实现类现有接口的美式电源——通过110V电压等级供电americanChargerimplementsAdaptee{@OverridepublicvoidchargeBy110V(){System.out.println("美式电源,只为你,正在通过110V电压为你充电");}}//类适配器,通过继承现有接口完成对现有接口的扩展"add110V,reach220V,punchduck!");//扩展现有功能}}//测试类publicclassTest{publicstaticvoidmain(String[]args)throwsFileNotFoundException{//类适配器使代码逻辑confusing//这里Adapter好像是110V的美式电源,没有其他信息可以直接使用//具体可以和objectadapter对比如下newAdapter().chargeBy220V();}}//Output/*美式电源,只为你,是通过110V电压给你充电,然后加110V达到220V,冲鸭!*/ObjectAdapter://客户期望的接口——220V电压充电接口Target{voidchargeBy220V();}//现有接口——只能用110V电压接口充电Adaptee{voidchargeBy110V();}//现有接口的具体实现类,美式电源——供电110V电压类americanChargerimplementsAdaptee{@OverridepublicvoidchargeBy110V(){System.out.println("美式电源,只为你,用110V电压给你充电");}}//类适配器,通过继承现有接口完成对现有接口的扩展,启用110V供电.adaptee=适应者;}@OverridepublicvoidchargeBy220V(){adaptee.chargeBy110V();//对象已有函数System.out.println("加110V,达到220V,打鸭子!");//扩展已有函数}}//TestclasspublicclassTest{publicstaticvoidmain(String[]args)throwsFileNotFoundException{//现在我们有一个美国110V的供电站,但是我们不能使用Adapteeadaptee=newamericanCharger();//我们将这个电源给适配器,适配器将转换为220V电源Adapteradapter=newAdapter(adaptee);//然后我们通过适配器充电Adapter.chargeBy220V();}}//输出同上。对象适配器以组合的方式实现现有接口的扩展,实现客户期望的接口让我们看一个JavaIO流中的例子:FileInputStreamfis=newFileInputStream("qe");InputStreamReaderisrAdapter=newInputStreamReader(fis);BufferedReaderbf=newBufferedReader(isrAdapter);BufferedReader(这里是client)需要读取文件字符流工作,读取文件字符流是客户需求的一部分,但是按照现有的接口,如果要读取文件,只能读取字节流。FileInputStream是现有接口的具体实现类。为了满足客户的需求,我们需要适配现有的界面。InputStreamReader是一个适配器,它包含一个现有接口类的实例。通过这个实例,读取文件的字节流,并扩展为字符流,以满足客户端的需求,这就是标准的对象适配器模式。如果仔细研究源码,你会发现JavaIO库将适配器定义为抽象的,具体的适配器继承了抽象适配器。比如这里的InputStreamReader就是具体的适配器之一。如果有多种实现适配的方式,我们可以将适配器类Adapter声明为抽象类,通过子类对其进行扩展//客户期望的接口——220V电压充电接口Target{voidchargeBy220V();}//现有接口-只能通过110V电压接口充电Adaptee{voidchargeBy110V();}//现有接口的具体实现类,美式电源-通过110V电压类供电americanChargerimplementsAdaptee{@OverridepublicvoidchargeBy110V(){System.out.println("美式电源,只为你充电,110V电压给你充电");}}//抽象类适配器,通过继承现有接口abstractclassAdapterimplementsTarget完成对现有接口的扩展{Adapteeadaptee;//持有现有接口具体实现对象的引用publicAdapter(Adapteeadaptee){this.adaptee=适应者;}}//中文自制类ChinaMakeAdapterextendsAdapter{publicChinaMakeAdapter(Adapteeadaptee){super(adaptee);}@OverridepublicvoidchargeBy220V(){adaptee.chargeBy110V();//对象已有函数System.out.println("加110V达到220V,找madeinChina,chargeDuck!");//扩展已有函数functions}}//TestclasspublicclassTest{publicstaticvoidmain(String[]args)throwsFileNotFoundException{//现在我们有一个美国110V的电站,但是我们不能使用它Adapteeadaptee=newamericanCharger();//我们将这个电站给中国制造的适配器Adapteradapter=newChinaMakeAdapter(adaptee);//接下来我们通过适配器进行充电adapter.chargeBy220V();}}//输出同上。另外,适配器可以通过实现两个接口来达到双向适配的目的,即从接口A到接口B,从接口B到接口A,这种情况并不常见//接口A——220V电压供电接口A{voidchargeBy220V();}//接口A的具体实现类,中国电源——通过220V电压等级供电ChinaCharger实现了A{@OverridepublicvoidchargeBy220V(){System.out.println("中国220V电压充电,值得信赖");}}//接口B——110V电压供电接口B{voidchargeBy110V();}//接口B具体实现类,美式电源————供电通过110V电压类AmericanCharger实现B{@OverridepublicvoidchargeBy110V(){System.out.println("美式充电器,只为你,正在通过110V电压给你充电");}}//双向适配器类AdapterimplementsA,B{Aa;//220V充电Bb;//110V充电公用适配器(Aa){this.a=a;}publicAdapter(Bb){this.b=b;}@OverridepublicvoidchargeBy220V(){b.chargeBy110V();//当前接口System.out.println("添加代码,增加到220V!!");//适配目标接口}@OverridepublicvoidchargeBy110V(){a.chargeBy220V();//当前接口System.out.println("缓冲电压,现在110V");}}//测试类publicclassTest{publicstaticvoidmain(String[]args)throwsFileNotFoundExceptionon{//我们要去美国,酒店有美式110V充电站,我们需要220V电压Bb=newAmericanCharger();//我们将这个充电站给适配器,得到220V电压充电Adapteradapter1=newAdapter(b);//然后我们通过适配器充电adapter1.chargeBy220V();System.out.println();//美国人来中国,酒店有中国220V充电站,但他需要110V电压Aa=newChinaCharger();//把这个充电站给适配器,得到110V电压充电适配器adapter2=newAdapter(a);//接下来我们通过适配器充电adapter2.chargeBy110V();}}//Output/*美式充电器,只为你,正在通过110V电压给你充电,加到220V!!220V电压中国充电,值得信赖的缓冲电压,现在是110VProcessfinishedwithexitcode0*/通过实现两个接口,实现不同接口的双向适配,在某些情况下还是很实用的,比如TypeC—USB接口转换器,可以从typeC转USB,从USB转typeC适配器模式。优点总结:任意两个不相关的类都可以跑在一起。它提高了类的重用性,可以统一多个不同的接口。隐藏已有的接口实现类,增加类的透明度。灵活性高,自由适配。缺点:过度使用适配器会使系统非常混乱,难以整体掌握。比如,明明是调用了A接口,实际上是内部适配了B接口的实现。如果这种情况在一个系统中发生太多,无异于一场灾难。因此,如果没有必要,可以不使用适配器直接重构系统。有些适应可能非常困难,例如让家蝇飞起来。当我们有修改功能系统接口的动机时,应该考虑适配器模式。注:适配器不是在详细设计时添加的,而是为了解决项目在役的问题,即现有的接口可能不会改变(在美国不可能将110V电压供电改为220V电压供电)状态)。