设计模式
设计模式简介
看懂UML类图和时序图
UML统一建模语言
UML类图及类图之间的关系
类关系记忆技巧
如何正确使用设计模式
优秀设计的特征
面向对象设计原则
创建型设计模式
工厂模式
抽象工厂模式
简单工厂模式
静态工厂模式(Static Factory)
单例模式
建造者模式
原型模式
结构型设计模式
适配器模式
桥接模式
组合模式
装饰器模式
外观模式
享元模式
代理模式
过滤器模式
注册模式(Registry)
行为型设计模式
责任链模式
命令模式
解释器模式
中介者模式
备忘录模式
迭代器模式
观察者模式
状态模式
策略模式
模板模式
访问者模式
规格模式(Specification)
J2EE 设计模式
MVC 模式
业务代表模式
组合实体模式
数据访问对象模式(DAO模式)
前端控制器模式
拦截过滤器模式
空对象模式
服务定位器模式
传输对象模式
数据映射模式(Data Mapper)
依赖注入模式(Dependency Injection)
流接口模式(Fluent Interface)
其他模式
对象池模式(Pool)
委托模式
资源库模式(Repository)
实体属性值模式(EAV 模式)
反面模式
归纳设计模式
本文档使用 MrDoc 发布
-
+
首页
适配器模式
> 适配器模式是一种结构型设计模式, 它能使**接口不兼容的对象能够相互合作**。 ![适配器设计模式](/media/202203/2022-03-05_2142210.9849263620056302.png) 适配器模式(Adapter Pattern)(封装器模式、Wrapper、Adapter)是作为**两个不兼容的接口之间的桥梁**。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。 这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。举个真实的例子,读卡器是作为内存卡和笔记本之间的适配器。您将内存卡插入读卡器,再将读卡器插入笔记本,这样就可以通过笔记本来读取内存卡。 ## 目的 将一个类的接口转换成可应用的兼容接口。适配器使原本由于接口不兼容而不能一起工作的那些类可以一起工作。 ## 问题 假如你正在开发一款股票市场监测程序, 它会从不同来源下载 XML 格式的股票数据, 然后向用户呈现出美观的图表。 在开发过程中, 你决定在程序中整合一个第三方智能分析函数库。 但是遇到了一个问题, 那就是分析函数库只兼容 JSON 格式的数据。 你可以修改程序库来支持 XML。 但是, 这可能需要修改部分依赖该程序库的现有代码。 甚至还有更糟糕的情况, 你可能根本没有程序库的源代码, 从而无法对其进行修改。 ## 解决方案 你可以创建一个适配器。 这是一个特殊的对象, 能够转换对象接口, 使其能与其他对象进行交互。 适配器模式通过封装对象将复杂的转换过程隐藏于幕后。 被封装的对象甚至察觉不到适配器的存在。 例如, 你可以使用一个将所有数据转换为英制单位 (如英尺和英里) 的适配器封装运行于米和千米单位制中的对象。 适配器不仅可以转换不同格式的数据, 其还有助于采用不同接口的对象之间的合作。 它的运作方式如下: - 适配器实现与其中一个现有对象兼容的接口。 - 现有对象可以使用该接口安全地调用适配器方法。 - 适配器方法被调用后将以另一个对象兼容的格式和顺序将请求传递给该对象。 有时你甚至可以创建一个双向适配器来实现双向转换调用。 让我们回到股票市场程序。 为了解决数据格式不兼容的问题, 你可以为分析函数库中的每个类创建将 XML 转换为 JSON 格式的适配器, 然后让客户端仅通过这些适配器来与函数库进行交流。 当某个适配器被调用时, 它会将传入的 XML 数据转换为 JSON 结构, 并将其传递给被封装分析对象的相应方法。 ## 介绍 - **意图:** 将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。 - **主要解决:** 主要解决在软件系统中,常常要将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足的。 - **何时使用:** 1. 系统需要使用现有的类,而此类的接口不符合系统的需要。 2. 想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作,这些源类不一定有一致的接口。 3. 通过接口转换,将一个类插入另一个类系中。(比如老虎和飞禽,现在多了一个飞虎,在不增加实体的需求下,增加一个适配器,在里面包容一个虎对象,实现飞的接口。) - **如何解决:** 继承或依赖(推荐)。 - **关键代码:** 适配器继承或依赖已有的对象,实现想要的目标接口。 - **应用实例:** 1. 美国电器 110V,中国 220V,就要有一个适配器将 110V 转化为 220V。 2. JAVA JDK 1.1 提供了 Enumeration 接口,而在 1.2 中提供了 Iterator 接口,想要使用 1.2 的 JDK,则要将以前系统的 Enumeration 接口转化为 Iterator 接口,这时就需要适配器模式。 3. 在 LINUX 上运行 WINDOWS 程序。 4. JAVA 中的 jdbc。 5. 客户端数据库适配器。 6. 使用多个不同的网络服务和适配器来规范数据使得出结果是相同的 - **优点:** 1. 可以让任何两个没有关联的类一起运行。 2. 提高了类的复用。 3. 增加了类的透明度。 4. 灵活性好。 - **缺点:** 1. 过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。 2. 由于 JAVA 至多继承一个类,所以至多只能适配一个适配者类,而且目标类必须是抽象类。 - **使用场景:** 有动机地修改一个正常运行的系统的接口,这时应该考虑使用适配器模式。 - **注意事项:** 适配器不是在详细设计时添加的,而是解决正在服役的项目的问题。 ## 结构 ### 对象适配器 实现时使用了构成原则: 适配器实现了其中一个对象的接口, 并对另一个对象进行封装。 所有流行的编程语言都可以实现适配器。 ![适配器设计模式的结构(对象适配器)](/media/202203/2022-03-03_1109120.4103454212185177.png) - 客户端 (Client) 是包含当前程序业务逻辑的类。 - 客户端接口 (Client Interface) 描述了其他类与客户端代码合作时必须遵循的协议。 - 服务 (Service) 中有一些功能类 (通常来自第三方或遗留系统)。 客户端与其接口不兼容, 因此无法直接调用其功能。 - 适配器 (Adapter) 是一个可以同时与客户端和服务交互的类: 它在实现客户端接口的同时封装了服务对象。 适配器接受客户端通过适配器接口发起的调用, 并将其转换为适用于被封装服务对象的调用。 - 客户端代码只需通过接口与适配器交互即可, 无需与具体的适配器类耦合。 因此, 你可以向程序中添加新类型的适配器而无需修改已有代码。 这在服务类的接口被更改或替换时很有用: 你无需修改客户端代码就可以创建新的适配器类。 ### 类适配器 这一实现使用了继承机制: 适配器同时继承两个对象的接口。 请注意, 这种方式仅能在支持多重继承的编程语言中实现, 例如 C++。 ![适配器设计模式(类适配器)](/media/202203/2022-03-03_1115230.33404779338351387.png) - 类适配器不需要封装任何对象, 因为它同时继承了客户端和服务的行为。 适配功能在重写的方法中完成。 最后生成的适配器可替代已有的客户端类进行使用。 ### 实例 我们有一个 MediaPlayer 接口和一个实现了 MediaPlayer 接口的实体类 AudioPlayer。默认情况下,AudioPlayer 可以播放 mp3 格式的音频文件。 我们还有另一个接口 AdvancedMediaPlayer 和实现了 AdvancedMediaPlayer 接口的实体类。该类可以播放 vlc 和 mp4 格式的文件。 我们想要让 AudioPlayer 播放其他格式的音频文件。为了实现这个功能,我们需要创建一个实现了 MediaPlayer 接口的适配器类 MediaAdapter,并使用 AdvancedMediaPlayer 对象来播放所需的格式。 AudioPlayer 使用适配器类 MediaAdapter 传递所需的音频类型,不需要知道能播放所需格式音频的实际类。AdapterPatternDemo 类使用 AudioPlayer 类来播放各种格式。 ![适配器模式的 UML 图](/media/202203/2022-03-03_1120170.8836675682343841.png) ## 伪代码 下列适配器模式演示基于经典的 **“方钉和圆孔”** 问题。 ![适配器模式结构的示例](/media/202203/2022-03-03_1123150.3019223770483259.png) 让方钉适配圆孔。 适配器假扮成一个圆钉 **(**RoundPeg**)**, 其半径等于方钉 **(**SquarePeg**)** 横截面对角线的一半 **(**即能够容纳方钉的最小外接圆的半径**)**。 ``` // 假设你有两个接口相互兼容的类:圆孔(RoundHole)和圆钉(RoundPeg)。 class RoundHole is constructor RoundHole(radius) { ... } method getRadius() is // 返回孔的半径。 method fits(peg: RoundPeg) is return this.getRadius() >= peg.getRadius() class RoundPeg is constructor RoundPeg(radius) { ... } method getRadius() is // 返回钉子的半径。 // 但还有一个不兼容的类:方钉(SquarePeg)。 class SquarePeg is constructor SquarePeg(width) { ... } method getWidth() is // 返回方钉的宽度。 // 适配器类让你能够将方钉放入圆孔中。它会对 RoundPeg 类进行扩展,以接收适 // 配器对象作为圆钉。 class SquarePegAdapter extends RoundPeg is // 在实际情况中,适配器中会包含一个 SquarePeg 类的实例。 private field peg: SquarePeg constructor SquarePegAdapter(peg: SquarePeg) is this.peg = peg method getRadius() is // 适配器会假扮为一个圆钉, // 其半径刚好能与适配器实际封装的方钉搭配起来。 return peg.getWidth() * Math.sqrt(2) / 2 // 客户端代码中的某个位置。 hole = new RoundHole(5) rpeg = new RoundPeg(5) hole.fits(rpeg) // true small_sqpeg = new SquarePeg(5) large_sqpeg = new SquarePeg(10) hole.fits(small_sqpeg) // 此处无法编译(类型不一致)。 small_sqpeg_adapter = new SquarePegAdapter(small_sqpeg) large_sqpeg_adapter = new SquarePegAdapter(large_sqpeg) hole.fits(small_sqpeg_adapter) // true hole.fits(large_sqpeg_adapter) // false ``` ## 应用场景 - 当你希望使用某个类, 但是其接口与其他代码不兼容时, 可以使用适配器类。 适配器模式允许你创建一个中间层类, 其可作为代码与遗留类、 第三方类或提供怪异接口的类之间的转换器。 - 如果您需要复用这样一些类, 他们处于同一个继承体系, 并且他们又有了额外的一些共同的方法, 但是这些共同的方法不是所有在这一继承体系中的子类所具有的共性。 你可以扩展每个子类, 将缺少的功能添加到新的子类中。 但是, 你必须在所有新子类中重复添加这些代码。 将缺失功能添加到一个适配器类中是一种优雅得多的解决方案。 然后你可以将缺少功能的对象封装在适配器中, 从而动态地获取所需功能。 如要这一点正常运作, 目标类必须要有通用接口, 适配器的成员变量应当遵循该通用接口。 这种方式同装饰模式非常相似。 ## 实现方式 1. 确保至少有两个类的接口不兼容: - 一个无法修改 (通常是第三方、 遗留系统或者存在众多已有依赖的类) 的功能性服务类。 - 一个或多个将受益于使用服务类的客户端类。 2. 声明客户端接口, 描述客户端如何与服务交互。 3. 创建遵循客户端接口的适配器类。 所有方法暂时都为空。 4. 在适配器类中添加一个成员变量用于保存对于服务对象的引用。 通常情况下会通过构造函数对该成员变量进行初始化, 但有时在调用其方法时将该变量传递给适配器会更方便。 5. **依次实现适配器类客户端接口的所有方法。 适配器会将实际工作委派给服务对象, 自身只负责接口或数据格式的转换。** 6. 客户端必须通过客户端接口使用适配器。 这样一来, 你就可以在不影响客户端代码的情况下修改或扩展适配器。 ## 优点 - 单一职责原则。你可以将接口或数据转换代码从程序主要业务逻辑中分离。 - 开闭原则。 只要客户端代码通过客户端接口与适配器进行交互, 你就能在不修改现有客户端代码的情况下在程序中添加新类型的适配器。 ## 缺点 - 代码整体复杂度增加, 因为你需要新增一系列接口和类。 有时直接更改服务类使其与其他代码兼容会更简单。 ## 与其他模式的关系 - 桥接模式通常会于开发前期进行设计, 使你能够将程序的各个部分独立开来以便开发。 另一方面, 适配器模式通常在已有程序中使用, 让相互不兼容的类能很好地合作。 - 适配器可以对已有对象的接口进行修改, 装饰模式则能在不改变对象接口的前提下强化对象功能。 此外, 装饰还支持递归组合, 适配器则无法实现。 - 适配器能为被封装对象提供不同的接口, 代理模式能为对象提供相同的接口, 装饰则能为对象提供加强的接口。 - 外观模式为现有对象定义了一个新接口, 适配器则会试图运用已有的接口。 适配器通常只封装一个对象, 外观通常会作用于整个对象子系统上。 - 桥接、状态模式和策略模式 (在某种程度上包括适配器) 模式的接口非常相似。 实际上, 它们都基于组合模式——即将工作委派给其他对象, 不过也各自解决了不同的问题。 模式并不只是以特定方式组织代码的配方, 你还可以使用它们来和其他开发者讨论模式所解决的问题。 ## 示例代码 ### TS ```ts /** * The Target defines the domain-specific interface used by the client code. */ class Target { public request(): string { return 'Target: The default target\'s behavior.'; } } /** * The Adaptee contains some useful behavior, but its interface is incompatible * with the existing client code. The Adaptee needs some adaptation before the * client code can use it. */ class Adaptee { public specificRequest(): string { return '.eetpadA eht fo roivaheb laicepS'; } } /** * The Adapter makes the Adaptee's interface compatible with the Target's * interface. */ class Adapter extends Target { private adaptee: Adaptee; constructor(adaptee: Adaptee) { super(); this.adaptee = adaptee; } public request(): string { const result = this.adaptee.specificRequest().split('').reverse().join(''); return `Adapter: (TRANSLATED) ${result}`; } } /** * The client code supports all classes that follow the Target interface. */ function clientCode(target: Target) { console.log(target.request()); } console.log('Client: I can work just fine with the Target objects:'); const target = new Target(); clientCode(target); console.log(''); const adaptee = new Adaptee(); console.log('Client: The Adaptee class has a weird interface. See, I don\'t understand it:'); console.log(`Adaptee: ${adaptee.specificRequest()}`); console.log(''); console.log('Client: But I can work with it via the Adapter:'); const adapter = new Adapter(adaptee); clientCode(adapter); ``` 输出: ```txt Client: I can work just fine with the Target objects: Target: The default target's behavior. Client: The Adaptee class has a weird interface. See, I don't understand it: Adaptee: .eetpadA eht fo roivaheb laicepS Client: But I can work with it via the Adapter: Adapter: (TRANSLATED) Special behavior of the Adaptee. ```
追风者
2022年3月24日 13:16
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
关于 MrDoc
觅思文档MrDoc
是
州的先生
开发并开源的在线文档系统,其适合作为个人和小型团队的云笔记、文档和知识库管理工具。
如果觅思文档给你或你的团队带来了帮助,欢迎对作者进行一些打赏捐助,这将有力支持作者持续投入精力更新和维护觅思文档,感谢你的捐助!
>>>捐助鸣谢列表
微信
支付宝
QQ
PayPal
Markdown文件
分享
链接
类型
密码
更新密码