C++设计模式(6)——适配器模式

news2024/12/22 20:49:36

亦称: 封装器模式、Wrapper、Adapter

意图

适配器模式是一种结构型设计模式, 它能使接口不兼容的对象能够相互合作。
在这里插入图片描述

问题

假如你正在开发一款股票市场监测程序, 它会从不同来源下载 XML 格式的股票数据, 然后向用户呈现出美观的图表。

在开发过程中, 你决定在程序中整合一个第三方智能分析函数库。 但是遇到了一个问题, 那就是分析函数库只兼容 JSON 格式的数据。
在这里插入图片描述
你无法 “直接” 使用分析函数库, 因为它所需的输入数据格式与你的程序不兼容。

你可以修改程序库来支持 XML。 但是, 这可能需要修改部分依赖该程序库的现有代码。 甚至还有更糟糕的情况, 你可能根本没有程序库的源代码, 从而无法对其进行修改。

解决方案

你可以创建一个适配器。 这是一个特殊的对象, 能够转换对象接口, 使其能与其他对象进行交互。

适配器模式通过封装对象将复杂的转换过程隐藏于幕后。 被封装的对象甚至察觉不到适配器的存在。 例如, 你可以使用一个将所有数据转换为英制单位 (如英尺和英里) 的适配器封装运行于米和千米单位制中的对象。

适配器不仅可以转换不同格式的数据, 其还有助于采用不同接口的对象之间的合作。 它的运作方式如下:

1、适配器实现与其中一个现有对象兼容的接口。
2、现有对象可以使用该接口安全地调用适配器方法。
3、适配器方法被调用后将以另一个对象兼容的格式和顺序将请求传递给该对象。

有时你甚至可以创建一个双向适配器来实现双向转换调用。
在这里插入图片描述
让我们回到股票市场程序。 为了解决数据格式不兼容的问题, 你可以为分析函数库中的每个类创建将 XML 转换为 JSON 格式的适配器, 然后让客户端仅通过这些适配器来与函数库进行交流。 当某个适配器被调用时, 它会将传入的 XML 数据转换为 JSON 结构, 并将其传递给被封装分析对象的相应方法。

真实世界类比

在这里插入图片描述
出国旅行前后的旅行箱。
如果你是第一次从美国到欧洲旅行, 那么在给笔记本充电时可能会大吃一惊。 不同国家的电源插头和插座标准不同。 美国插头和德国插座不匹配。 同时提供美国标准插座和欧洲标准插头的电源适配器可以解决你的难题。

适配器模式结构

在这里插入图片描述

1、客户端 (Client) 是包含当前程序业务逻辑的类。

2、客户端接口 (Client Interface) 描述了其他类与客户端代码合作时必须遵循的协议。

3、服务 (Service) 中有一些功能类 (通常来自第三方或遗留系统)。 客户端与其接口不兼容, 因此无法直接调用其功能。

4、适配器 (Adapter) 是一个可以同时与客户端和服务交互的类: 它在实现客户端接口的同时封装了服务对象。 适配器接受客户端通过适配器接口发起的调用, 并将其转换为适用于被封装服务对象的调用。

5、客户端代码只需通过接口与适配器交互即可, 无需与具体的适配器类耦合。 因此, 你可以向程序中添加新类型的适配器而无需修改已有代码。 这在服务类的接口被更改或替换时很有用: 你无需修改客户端代码就可以创建新的适配器类。

类适配器

这一实现使用了继承机制: 适配器同时继承两个对象的接口。 请注意, 这种方式仅能在支持多重继承的编程语言中实现, 例如 C++。
在这里插入图片描述

类适配器不需要封装任何对象, 因为它同时继承了客户端和服务的行为。 适配功能在重写的方法中完成。 最后生成的适配器可替代已有的客户端类进行使用。

伪代码

下列适配器模式演示基于经典的 “方钉和圆孔” 问题。
在这里插入图片描述
让方钉适配圆孔。
适配器假扮成一个圆钉 (Round­Peg), 其半径等于方钉 (Square­Peg) 横截面对角线的一半 (即能够容纳方钉的最小外接圆的半径)。

// 假设你有两个接口相互兼容的类:圆孔(Round­Hole)和圆钉(Round­Peg)。
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
        // 返回钉子的半径。


// 但还有一个不兼容的类:方钉(Square­Peg)。
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、如果您需要复用这样一些类, 他们处于同一个继承体系, 并且他们又有了额外的一些共同的方法, 但是这些共同的方法不是所有在这一继承体系中的子类所具有的共性。

你可以扩展每个子类, 将缺少的功能添加到新的子类中。 但是, 你必须在所有新子类中重复添加这些代码, 这样会使得代码有坏味道。

将缺失功能添加到一个适配器类中是一种优雅得多的解决方案。 然后你可以将缺少功能的对象封装在适配器中, 从而动态地获取所需功能。 如要这一点正常运作, 目标类必须要有通用接口, 适配器的成员变量应当遵循该通用接口。 这种方式同装饰模式非常相似。

实现方式

1、确保至少有两个类的接口不兼容:

2、一个无法修改 (通常是第三方、 遗留系统或者存在众多已有依赖的类) 的功能性服务类。
一个或多个将受益于使用服务类的客户端类。
声明客户端接口, 描述客户端如何与服务交互。

3、创建遵循客户端接口的适配器类。 所有方法暂时都为空。

4、在适配器类中添加一个成员变量用于保存对于服务对象的引用。 通常情况下会通过构造函数对该成员变量进行初始化, 但有时在调用其方法时将该变量传递给适配器会更方便。

5、依次实现适配器类客户端接口的所有方法。 适配器会将实际工作委派给服务对象, 自身只负责接口或数据格式的转换。

6、客户端必须通过客户端接口使用适配器。 这样一来, 你就可以在不影响客户端代码的情况下修改或扩展适配器。

适配器模式优缺点

在这里插入图片描述

与其他模式的关系

桥接模式通常会于开发前期进行设计, 使你能够将程序的各个部分独立开来以便开发。 另一方面, 适配器模式通常在已有程序中使用, 让相互不兼容的类能很好地合作。

适配器可以对已有对象的接口进行修改, 装饰模式则能在不改变对象接口的前提下强化对象功能。 此外, 装饰还支持递归组合, 适配器则无法实现。

适配器能为被封装对象提供不同的接口, 代理模式能为对象提供相同的接口, 装饰则能为对象提供加强的接口。

外观模式为现有对象定义了一个新接口, 适配器则会试图运用已有的接口。 适配器通常只封装一个对象, 外观通常会作用于整个对象子系统上。

桥接、 状态模式和策略模式 (在某种程度上包括适配器模式的接口非常相似。 实际上, 它们都基于组合模式——即将工作委派给其他对象, 不过也各自解决了不同的问题。 模式并不只是以特定方式组织代码的配方, 你还可以使用它们来和其他开发者讨论模式所解决的问题。

##C++ 适配器模式讲解和代码示例
适配器是一种结构型设计模式, 它能使不兼容的对象能够相互合作。

适配器可担任两个对象间的封装器, 它会接收对于一个对象的调用, 并将其转换为另一个对象可识别的格式和接口。

概念示例

本例说明了适配器设计模式的结构并重点回答了下面的问题:

它由哪些类组成?
这些类扮演了哪些角色?
模式中的各个元素会以何种方式相互关联?

main.cc: 概念示例

/**
 * The Target defines the domain-specific interface used by the client code.
 */
class Target {
 public:
  virtual ~Target() = default;

  virtual std::string Request() const {
    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:
  std::string SpecificRequest() const {
    return ".eetpadA eht fo roivaheb laicepS";
  }
};

/**
 * The Adapter makes the Adaptee's interface compatible with the Target's
 * interface.
 */
class Adapter : public Target {
 private:
  Adaptee *adaptee_;

 public:
  Adapter(Adaptee *adaptee) : adaptee_(adaptee) {}
  std::string Request() const override {
    std::string to_reverse = this->adaptee_->SpecificRequest();
    std::reverse(to_reverse.begin(), to_reverse.end());
    return "Adapter: (TRANSLATED) " + to_reverse;
  }
};

/**
 * The client code supports all classes that follow the Target interface.
 */
void ClientCode(const Target *target) {
  std::cout << target->Request();
}

int main() {
  std::cout << "Client: I can work just fine with the Target objects:\n";
  Target *target = new Target;
  ClientCode(target);
  std::cout << "\n\n";
  Adaptee *adaptee = new Adaptee;
  std::cout << "Client: The Adaptee class has a weird interface. See, I don't understand it:\n";
  std::cout << "Adaptee: " << adaptee->SpecificRequest();
  std::cout << "\n\n";
  std::cout << "Client: But I can work with it via the Adapter:\n";
  Adapter *adapter = new Adapter(adaptee);
  ClientCode(adapter);
  std::cout << "\n";

  delete target;
  delete adaptee;
  delete adapter;

  return 0;
}

Output.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.

多重继承的适配器模式

在 C++ 中, 可以使用多重继承来实现适配器模式。

main.cc: 多重继承

/**
 * The Target defines the domain-specific interface used by the client code.
 */
class Target {
 public:
  virtual ~Target() = default;
  virtual std::string Request() const {
    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:
  std::string SpecificRequest() const {
    return ".eetpadA eht fo roivaheb laicepS";
  }
};

/**
 * The Adapter makes the Adaptee's interface compatible with the Target's
 * interface using multiple inheritance.
 */
class Adapter : public Target, public Adaptee {
 public:
  Adapter() {}
  std::string Request() const override {
    std::string to_reverse = SpecificRequest();
    std::reverse(to_reverse.begin(), to_reverse.end());
    return "Adapter: (TRANSLATED) " + to_reverse;
  }
};

/**
 * The client code supports all classes that follow the Target interface.
 */
void ClientCode(const Target *target) {
  std::cout << target->Request();
}

int main() {
  std::cout << "Client: I can work just fine with the Target objects:\n";
  Target *target = new Target;
  ClientCode(target);
  std::cout << "\n\n";
  Adaptee *adaptee = new Adaptee;
  std::cout << "Client: The Adaptee class has a weird interface. See, I don't understand it:\n";
  std::cout << "Adaptee: " << adaptee->SpecificRequest();
  std::cout << "\n\n";
  std::cout << "Client: But I can work with it via the Adapter:\n";
  Adapter *adapter = new Adapter;
  ClientCode(adapter);
  std::cout << "\n";

  delete target;
  delete adaptee;
  delete adapter;

  return 0;
}

Output.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.

来源:https://refactoringguru.cn/design-patterns/adapter
仅供学习,非商业用途,侵删

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/173764.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

9.框架SpringMVC

一、基本概念 Spring MVC 是 Spring 提供的一个基于 MVC 设计模式的轻量级 Web 开发框架&#xff0c;本质上相当于 Servlet。Spring MVC 使用 MVC 架构模式的思想&#xff0c;将 Web 应用进行职责解构&#xff0c;把一个复杂的 Web 应用划分成模型&#xff08;Model&#xff09…

HashMap的使用:put、remove和get方法原理

关联项目需求进行FeatureAB上报的时候&#xff0c;我们使用HashSet的add方法存key值&#xff0c;如果key已存在&#xff0c;则add失败&#xff0c;返回false&#xff0c;如果key不存在&#xff0c;add成功&#xff0c;返回true。看源码中HashSet的add(E e)方法实现&#xff1a;…

【Git】IDEA 集成 GitHub

8、IDEA 集成 GitHub 8.1、设置 GitHub 账号 如果出现 401 等情况连接不上的&#xff0c;是因为网络原因&#xff0c;可以使用以下方式连接&#xff1a; 然后去 GitHub 账户上设置 token。 点击生成 token。 复制红框中的字符串到 idea 中。 点击登录。 8.2、分享工程到 GitHu…

【甄选靶场】Vulnhub百个项目渗透——项目五十五:SP-LEOPOLD v1.2(beef联动msf,脏牛提权)

Vulnhub百个项目渗透 Vulnhub百个项目渗透——项目五十五&#xff1a;SP-LEOPOLD v1.2&#xff08;beef联动msf&#xff0c;脏牛提权&#xff09; &#x1f525;系列专栏&#xff1a;Vulnhub百个项目渗透 &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&am…

ZooKeeper-集群搭建

5)ZooKeeper 集群搭建 5.1)Zookeeper集群介绍 Leader选举&#xff1a; •Serverid&#xff1a;服务器ID 比如有三台服务器&#xff0c;编号分别是1,2,3。 编号越大在选择算法中的权重越大。 •Zxid&#xff1a;数据ID 服务器中存放的最大数据ID.值越大说明数据 越新&…

剑指offer—day1.用两个栈实现队列、包含min函数的栈

1.用两个栈实现队列 本题来源&#xff1a;力扣 剑指 Offer 09. 用两个栈实现队列 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/题目描述 用两个栈实现一个队列。队列的声明如下&#xff0c;请实现它的两…

IP-guard如何映射到外网登录访问管理

终端安全管理(endpoint security management)是一种保护网络安全的策略式方法&#xff0c;它需要终端设备在得到访问网络资源的许可之前遵从特定的标准。随着企业信息化发展&#xff0c;终端安全管理系统需求不断扩大&#xff0c;相关系统软件被广泛应用。 IPguard即IP-guard&a…

线段树(重要!多加理解懒惰标记!)

基础概念&#xff1a; 线段&#xff08;区间&#xff09;[L,R] 所对应的线段树是由区间 [L,R] 及其子区间构成的二叉树&#xff08;如下图所示&#xff09; 线段树具有的特性&#xff1a; &#xff08;1&#xff09;线段树的叶结点为只有一个元素的区间&#xff0c;因此长度为…

最新版海豚调度dolphinscheduler-3.1.3安装部署详细教程

0 背景 本文基于Ambari集群搭建最新版本的海豚调度dolphinscheduler-3.1.3版本&#xff0c;后续会尝试整合到Ambari中。 1 安装准备 安装dolphinscheduler需要在环境中安装如下依赖 ① JDK8 下载JDK (1.8)&#xff0c;安装并配置 JAVA_HOME 环境变量&#xff0c;并将其下的 …

用 22 张照片打开 23 年

魔幻又带有现实主义色彩的三年似乎终将见底。这也为 2023 年赋予了一些新的意义&#xff0c;或许是充满生机、怀揣希望、满怀爱意&#xff0c;或许是重新启航、步履不停、勇敢探索……为此&#xff0c;我们收集了 22 位社区用户和公司小伙伴在过去一年的「特别 Moment」及新年愿…

你认为DAO是否可行?新年计划,卯足干劲,兔必No.1

文章目录&#x1f31f; 课前小差&#x1f31f; 聚沙成塔&#x1f31f; 社会价值&#x1f31f; DAO是什么&#x1f31f; 国产化&#x1f31f; 商业化回报&#x1f31f; 写在最后&#x1f31f; 课前小差 哈喽&#xff0c;大家好&#xff0c;我是几何心凉&#xff0c;这是一份全新…

Spring高级之BeanFactory功能

首先&#xff0c;我们想要知道一个接口有哪些功能&#xff0c;就必须要看这个接口的源代码&#xff0c;在idea中&#xff0c;选中这个接口CtrlF12&#xff0c;来查看这个接口里面有哪些方法&#xff1a; 表面上来看&#xff0c;功能其实很少&#xff0c;查看源码及其方法功能 …

来吧,Jenkins+git+mvn+shell一键部署实践起来

环境&#xff1a;centosJenkins-2.319系统自带gitmvn3.8.7jdk1.8一、安装jdk1、https://blog.csdn.net/codedz/article/details/124044974centos自带了openjdk&#xff0c;我是选择自己重新搞一个&#xff0c;用的上面链接地址的yum安装方式2、安装完成查看版本查看java安装路径…

优思学院|质量人对控制图中的规格线和控制线傻傻分不清?

质量人、六西格玛[1]人和很多不同类型的工程师都需要了解什么是控制图&#xff0c;而在控制图中的规格限制&#xff08;Specification Limit&#xff09;"和"控制限制&#xff08;Control Limit&#xff09;"原来对好多人来说都是傻傻分不清&#xff01; 规格限…

线段树入门

对于一个区间进行询问&#xff0c;进行修改&#xff0c;都是用线段树进行处理。线段树和普通的树不一样&#xff0c;普通的树的节点存的是一个编号&#xff0c;线段树存的是一个区间&#xff0c;而且线段树一定是一棵完全二叉树。例如&#xff1a;这就是一棵线段树。例如对于[1…

【Ajax】数据交换格式XML 和 JSON

一、什么是数据交换格式数据交换格式&#xff0c;就是服务器端与客户端之间进行数据传输与交换的格式。前端领域&#xff0c;经常提及的两种数据交换格式分别是 XML 和 JSON。其中 XML 用的非常少&#xff0c;所以&#xff0c;我们重点要学习的数据交换格式就是 JSON。二、XML1…

让交互更加生动!巧用CSS实现鼠标跟随 3D 旋转效果

简单分析一下&#xff0c;这个交互效果主要有两个核心&#xff1a; 借助了 CSS 3D 的能力 元素的旋转需要和鼠标的移动相结合 本人简单的说一下如何使用纯 CSS 实现类似的交互效果&#xff0c;以及&#xff0c;借助 JavaScript 绑定鼠标事件&#xff0c;快速还原上述效果。 …

数据结构---set篇

第一次超时是因为用memsetmemsetmemset不得不超时&#xff0c;第二次超时是我用vectorvectorvector数组的时候&#xff0c;然后以O(n)O(n)O(n)复杂度查找元素之后使用eraseeraseerase方法进行删除&#xff0c;第三次超时是我把查找元素改成了O(logn)O(logn)O(logn)之后用vector…

epoll的ET和LT模式

简介 epoll对fd的操作有两种模式&#xff1a;LT(Level Trigger&#xff0c;水平触发)模式&#xff0c;和ET&#xff08;Edge Trigger,边缘触发&#xff09;模式。 LT 模式是默认的工作模式&#xff0c;这种模式下&#xff0c;epoll相当于一个效率较高的poll&#xff1b; ET模…

89. 注意力机制以及代码实现Nadaraya-Waston 核回归

1. 心理学 动物需要在复杂环境下有效关注值得注意的点心理学框架&#xff1a;人类根据随意线索和不随意线索选择注意点 随意&#xff1a;随着自己的意识&#xff0c;有点强调主观能动性的意味。 2. 注意力机制 2. 非参注意力池化层 3. Nadaraya-Waston 核回归 4. 参数化的注意…