文章目录
问题引入
在学习适配器模式的时候我看到了这么一个有趣的问题,和这么有张有趣的图片。
如何让汽车可以在铁轨上跑起来?
我们知道铁轨上是用来跑火车的,而火车的轮子是和铁轨匹配的,所以火车可以跑在铁轨上。那我们现在想让汽车也可以跑在铁轨上有哪些办法呢?
我先说一个那就是把汽车的轮子换成火车的轮子,这样的话汽车也就可以在火车上跑起来了可是这样一来要想汽车再次在马路上行驶的话,还需要再次更换,将轮子在换回其车辆的轮胎,不难看出,这确实一个解决问题的办法,可是这样做不仅麻烦,而且总是更换轮子可能也会影响汽车的使用寿命,那还有没有什么好的办法呢?请看下图
嘿嘿,非常有趣的一个想法,在汽车和铁轨之间通过添加了一辆适配器(Adapter)就可以解决上面提到的问题,而且不需要频繁地更改轮胎,保障了汽车的寿命。在这个问题中就用到了适配器的思想。
现实案例
其实在我们生活中也遇到过使用适配器的例子,下面是我们常用的一个插头。
然后这是一个普通的插座
现在我们当然的插头可以轻松的插到插座上,可是现在你出国旅游了,去哪旅游呢?emm,假如说你去了美国旅游,带着笔记本和充电器喜滋滋的打算好好旅游一番,结果就在你打算给电脑充电的时候,你看着眼前的插座陷入了沉思。
emm,怎么出门的时候没人告诉我美国的插座长这个模样,你回头看着自己的电壶,手机。电视再次陷入了思考之中。
好了现在场景假设已经完成了,面对这样的情况该怎么做呢?最简单的办法就是淘汰掉所有的电子设备,重新购买匹配新插座的电子设备(土豪办法)
那还有没有什么其他的办法呢?当然是有的万能的淘宝总是能帮到你,你只需要购每一个插座转接头就可以用低成本未解决插座不兼容的问题。而在设计模式中,这种转接头就被叫[适配器](Adapter)。
Java中的适配器
通过上面的例子我们大致了解了适配器是什么,可是在我们Java程序设计中适配器又是怎么样的呢?
适配器模式的核心思想
在不同的代码中,经常会存在现有的程序(我国的插头)无法直接使用,需要做适当的变换后(连接上转接头)才能使用的情况。它能使不兼容的对象能够相互合作,并且作为两个对象间的封装器, 它会接收对于一个对象的调用, 并将其转换为另一个对象可识别的格式和接口。
Java中的一些适配器
- java.util.Array 类中的asList()方法
- java.util.Collections 类中的 list( )方法
- java.util.Collections 类中的 enumeration()方法
- java.io.InputstreamReader( Inputstream)(返回Reader对象)
- java.io.OutputStreamwriter(outputStream)(返回Writer对象)
- javax.xml.bind.annotation.adapters.XmlAdapter 中的marshal() 和 unmarshal()方法
适配器模式中的角色和实现方式
适配器模式中角色
在适配器模式中,主要存在四个角色,弄清楚这四个角色的作用和关系有助于我们更好的去理解和使用适配器模式
Target(目标/标准)
Target定义了Client所要完成功能所要具备的一些要求,是一种标准(比如转换器两边的具体形状),而这个标准的具体实现(比如插头使用的什么金属)可以交由不同的人采用不同的方法进行实现,但是这些具体实现都应该符合Target中定义的标准。
Client(请求者/客户端)
这个应该比较容易理解,就是对适配器有需求的人联系到上面转接口的例子,Client指的就是需要使用转换接头的人。Client就是Target接口的调用者,Client通过调用Target接口就能够完成他希望的功能(能在美国使用我国的家电),而不需要关心功细节标准(不需要知道这个转换器是通过电流的转换,还有两边的插口具体的大小形状)。
Adapter(适配器)
负责真实实现转换功能的对象(转换头),同时其功能的实现需要符合Target中定义的标准。
Adaptee(被适配的对象)
需要被适配器转换的对象(我国的插头)。
实现方式
适配器模式有两种事项方式
- 类适配器模式
- 对象适配器模式
两种方式各有优劣,在Java中由于不支持多继承,使用类适配器模式就不如使用对象适配器模式更加灵活,所以我这里重点介绍下对象适配模式的实现。
- 确保至少有两个类的接口不兼容:
- 一个无法修改 (通常是第三方、 遗留系统或者存在众多已有依赖的类) 的功能性服务类。
- 一个或多个将受益于使用服务类的客户端类。
- 声明客户端接口, 描述客户端如何与服务交互。
- 创建遵循客户端接口的适配器类。 所有方法暂时都为空。
- 在适配器类中添加一个成员变量用于保存对于服务对象(Adaptee被适配的对象,在上面的例子里指的是我国的插头)的引用。 通常情况下会通过构造函数对该成员变量进行初始化, 但有时在调用其方法时将该变量传递给适配器会更方便。
- 依次实现适配器类客户端接口的所有方法。 适配器会将实际工作委派给服务对象, 自身只负责接口或数据格式的转换。
- 客户端必须通过客户端接口使用适配器。 这样一来, 你就可以在不影响客户端代码的情况下修改或扩展适配器。
适配器的代码实现
Adaptee
public class Adaptee {
/**
* 需要被适配的功能
* 这里用插座转换举例
*/
public void commonThreeHoleSocket(){
System.out.println("hi,我是一个祖国的三孔插头");
}
}
Target
public interface Target {
/**
* 定义美国插头
*/
public void AmericanHoleSocket();
}
对象适配器
public class Adapter implements Target {
/**
* 这是两种实现方式唯一的不同之处
*/
private Adaptee adaptee;
public Adapter() {
this.adaptee = new Adaptee();
}
/**
* 采用组合的方式实现转换功能
*/
public void AmericanHoleSocket() {
// 调用组合成员的方法,对其进行增强或处理
this.adaptee.commonThreeHoleSocket();
System.out.println("==========开始转换==========");
System.out.println("oh,我变成了美国插头");
}
}
类适配器
public class Adapter extends Adaptee implements Target {
/**
* 采用继承的方式实现转换功能
*/
@Override
public void AmericanHoleSocket() {
// 调用继承的方法,对其进行增强或处理
this.commonThreeHoleSocket();
System.out.println("==========开始转换==========");
System.out.println("oh,我变成美国的插头");
}
}
Client
public class Client {
public static void main(String[] args) {
Target newPlug = new Adapter();
// 对咱们来说需要知道适配器提供了一个美国插头即可
newPlug.AmericanHoleSocket();
System.out.println("这是一个美国插头");
}
}
聚思广义
经典的圆孔圆钉问题
上面我们写了一个插头转换的案例来理解适配器模型,我想现在大家已经迫不及待的想趁热打铁,自己完成一个案例了,大家可以自己思考可以用到适配器模型的例子,也可以按照我我给出的案例来写,下面就是我给的案例:
问题描述
在制作家具的时候,我们有时候需要使用到将圆钉打入圆孔来完成一些固定操作,这个时候要求圆钉的直径要略小于圆孔(方便打入圆孔而且可以解决热胀冷缩的问题),加入这个时候没有圆钉,只有方钉,只要方钉可以进入圆孔也可以完成固定操作,要求写一个适配器,完成方钉和圆孔的适配问题
适配器模式的优缺点
优点
- 单一职责原则你可以将接口或数据转换代码从程序主要业务逻辑中分离。
- 开闭原则。 只要客户端代码通过客户端接口与适配器进行交互, 你就能在不修改现有客户端代码的情况下在程序中添加新类型的适配器。
缺点
- 代码整体复杂度增加, 因为你需要新增一系列接口和类。 有时直接更改服务类使其与其他代码兼容会更简单。
什么时候使用适配器
- 当你希望使用某个类(别人写好的现成的类),但是其接口与自己所写的代码不兼容时,可以使用适配器类。
- 版本升级,在版本升级的时候,想要维持兼容性,使用适配器模式使新版本和旧版本兼容,可以让开发者们更少的关注旧版本的细节,使其更加关注新版本的开发。
与其他模式的关系
- 桥接模式通常会于开发前期进行设计, 使你能够将程序的各个部分独立开来以便开发。 另一方面, 适配器模式通常在已有程序中使用, 让相互不兼容的类能很好地合作。
- 适配器可以对已有对象的接口进行修改, 装饰模式则能在不改变对象接口的前提下强化对象功能。 此外, 装饰还支持递归组合, 适配器则无法实现。
- 适配器能为被封装对象提供不同的接口, 代理模式能为对象提供相同的接口, 装饰则能为对象提供加强的接口。
- 外观模式为现有对象定义了一个新接口, 适配器则会试图运用已有的接口。 适配器通常只封装一个对象, 外观通常会作用于整个对象子系统上。
- 桥接、 状态模式和策略模式 (在某种程度上包括适配器) 模式的接口非常相似。 实际上, 它们都基于组合模式——即将工作委派给其他对象, 不过也各自解决了不同的问题。 模式并不只是以特定方式组织代码的配方, 你还可以使用它们来和其他开发者讨论模式所解决的问题。