适配器模式
适配器模式(Adapter Pattern) :将一个接口转换成客户希望的另一个接口,适配器模式使接口不兼容的那些类可以一起工作,其别名为包装器(Wrapper)。适配器模式既可以作为类结构型模式,也可以作为对象结构型模式。
现实世界的例子
假设您的存储卡上有一些照片,并且需要将它们传输到计算机上。要传输它们,您需要某种与计算机端口兼容的适配器,以便可以将存储卡连接到计算机。在这种情况下,读卡器是一个适配器。
另一个例子是著名的电源适配器;三脚插头不能连接两脚插座,需要使用兼容两脚插座的电源适配器。
另一个例子是翻译人员将一个人所说的单词翻译成另一个人所说的单词
用简单的话来说
适配器模式允许您将不兼容的对象包装在适配器中,以使其与另一个类兼容。
问题
假如你正在开发一款股票市场监测程序, 它会从不同来源下载 XML 格式的股票数据, 然后向用户呈现出美观的图表。
在开发过程中, 你决定在程序中整合一个第三方智能分析函数库。 但是遇到了一个问题, 那就是分析函数库只兼容 JSON 格式的数据。
你可以修改程序库来支持 XML。 但是, 这可能需要修改部分依赖该程序库的现有代码。 甚至还有更糟糕的情况, 你可能根本没有程序库的源代码, 从而无法对其进行修改。
解决方案
你可以创建一个适配器。 这是一个特殊的对象, 能够转换对象接口, 使其能与其他对象进行交互。
适配器模式通过封装对象将复杂的转换过程隐藏于幕后。 被封装的对象甚至察觉不到适配器的存在。 例如, 你可以使用一个将所有数据转换为英制单位 (如英尺和英里) 的适配器封装运行于米和千米单位制中的对象。
适配器不仅可以转换不同格式的数据, 其还有助于采用不同接口的对象之间的合作。 它的运作方式如下:
- 适配器实现与其中一个现有对象兼容的接口。
- 现有对象可以使用该接口安全地调用适配器方法。
- 适配器方法被调用后将以另一个对象兼容的格式和顺序将请求传递给该对象。
有时你甚至可以创建一个双向适配器来实现双向转换调用。
让我们回到股票市场程序。 为了解决数据格式不兼容的问题, 你可以为分析函数库中的每个类创建将 XML 转换为 JSON 格式的适配器, 然后让客户端仅通过这些适配器来与函数库进行交流。 当某个适配器被调用时, 它会将传入的 XML 数据转换为 JSON 结构, 并将其传递给被封装分析对象的相应方法。
适配器模式结构
实现时使用了构成原则: 适配器实现了其中一个对象的接口, 并对另一个对象进行封装。 所有流行的编程语言都可以实现适配器。
程序示例:
对象适配器模式,是通过组合的形式来完成适配,在实际开发中,如果能够适用组合的形式,就尽量不用要继承。
1 | package com.design.patterns.adapter; |
1 | package com.design.patterns.adapter; |
1 | package com.design.patterns.adapter; |
1 | package com.design.patterns.adapter; |
1 | package com.design.patterns.adapter; |
优点
- 将目标类和适配者类解耦,通过引入一个适配器类来重用现有的适配者类,而无须修改原有代码。
- 增加了类的透明性和复用性,将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性。
- 开闭原则。 只要客户端代码通过客户端接口与适配器进行交互, 你就能在不修改现有客户端代码的情况下在程序中添加新类型的适配器。
缺点
- 过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构
- 代码整体复杂度增加, 因为你需要新增一系列接口和类。 有时直接更改服务类使其与其他代码兼容会更简单。
已知使用:
java.util.Arrays#asList()
java.util.Collections#list()
java.util.Collections#enumeration()
javax.xml.bind.annotation.adapters.XMLAdapter