适配器模式(Adapter)

2023-11-03

适配器是一种结构型设计模式,它能使接口不兼容的对象能够相互合作。又称封装器模式(Wrapper)。

适配器模式通过封装对象将复杂的转换过程隐藏于幕后。被封装的对象甚至察觉不到适配器的存在。例如,你可以使用一个将所有数据转换为英制单位(如英尺和英里)的适配器封装运行于米和千米单位制中的对象。
适配器不仅可以转换不同格式的数据,其还有助于采用不同接口的对象之间的合作。它的运作方式如下:

  • 适配器实现与其中一个现有对象兼容的接口。

  • 现有对象可以使用该接口安全地调用适配器方法。

  • 适配器方法被调用后将以另一个对象兼容的格式和顺序将请求传递给该对象。
    在这里插入图片描述

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

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

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

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

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

实现方式

  1. 确保至少有两个类的接口不兼容:
  • 一个无法修改(通常是第三方、遗留系统或者存在众多已有依赖的类)的功能性服务类。
  • 一个或多个将受益于使用服务类的客户端类。
  1. 声明客户端接口,描述客户端如何与服务交互。
  2. 创建遵循客户端接口的适配器类。所有方法暂时都为空。
  3. 在适配器类中添加一个成员变量用于保存对于服务对象的引用。通常情况下会通过构造函数对该成员变量进行初始化,但有时在调用其方法时将该变量传递给适配器会更方便。
  4. 依次实现适配器类客户端接口的所有方法。适配器会将实际工作委派给服务对象,自身只负责接口或数据格式的转换。
  5. 客户端必须通过客户端接口使用适配器。这样一来,你就可以在不影响客户端代码的情况下修改或扩展适配器。

优点

  • 单一职责原则你可以将接口或数据转换代码从程序主要业务逻辑中分离。
  • 开闭原则。只要客户端代码通过客户端接口与适配器进行交互,你就能在不修改现有客户端代码的情况下在程序中添加新类型的适配器。

缺点

代码整体复杂度增加,因为你需要新增一系列接口和类。有时直接更改服务类使其与其他代码兼容会更简单。

与其他模式的关系

  1. 桥接通常会于开发前期进行设计,使你能够将程序的各个部分独立开来以便开发。另一方面,适配器通常在已有程序中使用,让相互不兼容的类能很好地合作。
  2. 适配器可以对已有对象的接口进行修改,装饰则能在不改变对象接口的前提下强化对象功能。此外,装饰还支持递归组合,适配器则无法实现。
    适配器能为被封装对象提供不同的接口,代理能为对象提供相同的接口,装饰则能为对象提供加强的接口。
  3. 外观为现有对象定义了一个新接口,适配器则会试图运用已有的接口。适配器通常只封装一个对象,外观通常会作用于整个对象子系统上。
  4. 桥接、状态和策略(在某种程度上包括适配器)模式的接口非常相似。实际上, 它们都基于组合模式——即将工作委派给其他对象,不过也各自解决了不同的问题。模式并不只是以特定方式组织代码的配方,你还可以使用它们来和其他开发者讨论模式所解决的问题。

示例一

方钉和圆孔的问题,我们要做的是让方钉适配圆孔。
适配器让方钉假扮成一个圆钉(RoundPeg),其半径等于方钉(SquarePeg)横截面对角线的一半(即能容纳方钉的最小外接圆的半径)。

// ClientInterface.h
#ifndef CLIENT_INTERFACE_H_
#define CLIENT_INTERFACE_H_

// 圆钉: 客户端接口, 在C++中定义成抽象基类
class RoundPeg {
 public:
    RoundPeg() {}
    virtual int get_radius() = 0;
};

#endif  // CLIENT_INTERFACE_H_

Adapter.h:

// Adapter.h:
#ifndef ADAPTER_H_
#define ADAPTER_H_

#include <cmath>
#include "Service.h"
#include "ClientInterface.h"

// 方钉适配器: 该适配器能让客户端将方钉放入圆孔中
class SquarePegAdapter : public RoundPeg {
 public:
    explicit SquarePegAdapter(SquarePeg* sp) : square_peg_(sp) {}
    int get_radius() override {
        return square_peg_->get_width() * sqrt(2) / 2;
    }

 private:
    SquarePeg* square_peg_;
};

#endif  // ADAPTER_H_

Service.h:

#ifndef SERVICE_H_
#define SERVICE_H_

// 方钉: 适配者类, 即和客户端不兼容的类
class SquarePeg {
 public:
    explicit SquarePeg(int w) : width_(w) {}
    int get_width() {
        return width_;
    }

 private:
    int width_;
};

#endif  // SERVICE_H_

Client.h:

#ifndef CLIENT_H_
#define CLIENT_H_

#include "ClientInterface.h"

// 圆孔: 客户端类
class RoundHole {
 public:
    explicit RoundHole(int r) : radius_(r) {}
    int get_radius() {
        return radius_;
    }
    bool isFit(RoundPeg* rp) {
        return radius_ >= rp->get_radius();
    }

 private:
    int radius_;
};

#endif  // CLIENT_H_

main.cpp

#include <iostream>
#include "Client.h"
#include "Adapter.h"

int main() {
    // 半径为10的圆孔
    RoundHole* hole = new RoundHole(10);

    // 半径分别为5和20的大小方钉 + 它们的适配器
    SquarePeg* samll_square_peg = new SquarePeg(5);
    SquarePeg* large_square_peg = new SquarePeg(20);
    SquarePegAdapter* small_square_peg_adapter = new SquarePegAdapter(samll_square_peg);
    SquarePegAdapter* large_square_peg_adapter = new SquarePegAdapter(large_square_peg);

    // hole->isFit(samll_square_peg);  // 编译报错
    // hole->isFit(large_square_peg);  / / 编译报错
    if (hole->isFit(small_square_peg_adapter)) {
        std::cout << "small square peg fits the hole" << std::endl;
    } else {
        std::cout << "small square peg don't fit the hole" << std::endl;
    }
    if (hole->isFit(large_square_peg_adapter)) {
        std::cout << "large square peg fits the hole" << std::endl;
    } else {
        std::cout << "large square peg don't fit the hole" << std::endl;
    }
}

编译运行

$g++ -g main.cpp -o adapter -std=c++11
$./adapter
small square peg fits the hole
large square peg don't fit the hole

示例二

假设,现在有现有的各种动物,都是现成的,有猫、狗。他们都有各自的方法比如说,卧倒,打滚等,但是呢,他们的接口又不一样。为了使主人去让猫、狗做动作的接口一致,就需要适配器。因为语言不通,一个喵喵喵,一个汪汪汪。

adpter.h

#include <iostream>

class DOG {
 public:
  void Dog_Bedding() { std::cout << "小狗子,请卧倒" << std::endl; }

  void Dog_Roll() { std::cout << "小狗子,请打滚" << std::endl; }
};

class CAT {
 public:
  void Cat_Bedding() { std::cout << "小猫咪,请卧倒" << std::endl; }

  void Cat_Roll() { std::cout << "小猫咪,请打滚" << std::endl; }
};

class Target {
 public:
  virtual void Bedding()=0;  // 外部符号
  virtual void Roll()=0;
};

class adpter_cat : public Target {
 public:
  adpter_cat(CAT* c) : cat_(c) {}
  void Bedding() { cat_->Cat_Bedding(); }
  void Roll() { cat_->Cat_Roll(); }

 private:
  CAT* cat_;
};

class adpter_dog : public Target {
 public:
  adpter_dog(DOG* d) : dog_(d) {}
  void Bedding() { dog_->Dog_Bedding(); }
  void Roll() { dog_->Dog_Roll(); }

 private:
  DOG* dog_;
};

main.cpp

// #include "Adpter_cat.h"
// #include "Adpter_dog.h"
// #include "cat.h"
// #include "dog.h"

#include "adpter.h"

int main() {
  CAT* cat = new CAT();
  DOG* dog = new DOG();
  adpter_cat ac(cat);
  adpter_dog ad(dog);

  ac.Bedding();
  ac.Roll();
  ad.Bedding();
  ad.Roll();
}

运行结果

小猫咪,请卧倒
小猫咪,请打滚
小狗子,请卧倒
小狗子,请打滚
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

适配器模式(Adapter) 的相关文章

  • 【华为OD机试真题 python】最大数字【2023 Q1

    题目描述 最大数字 给定一个由纯数字组成以字符串表示的数值 现要求字符串中的每个数字最多只能出现2次 超过的需要进行删除 删除某个重复的数字后 其它数字相对位置保持不变 如 34533 数字3重复超过2次 需要删除其中一个3 删除第一个3后

随机推荐

  • idea读取数据库乱码,Navicat正常(解决)

    乱码问题困扰了我2天 菜的抠脚 先说说问题吧 你如果不想看这些废话就直接去下面解决 我先创建了数据库 拷贝了sql语句运行之后 Navicat正常显示 但是页面显示乱码 其实是中文latin1编码 debug跟进程序 发现在hibernat
  • msvcp140_1.dll丢失怎样修复?快速修复dll文件缺失

    msvcp140 1 dll丢失怎样修复 关于msvcp140 1 dll丢失 其实和其他dll文件的修复方法是一模一样的 你缺失了什么dll文件 那么你就在百度搜索这个dll文件 然后放到指定的文件夹就好了 解决起来还是非常的简单的 ms
  • Cocos Creator资源管理AssetManager细说一二

    关于AssetManager Asset Manager 是 Creator 在 v2 4 新推出的资源管理器 用于替代之前的 cc loader 新的 Asset Manager 资源管理模块具备加载资源 查找资源 销毁资源 缓存资源 A
  • VUE搭建项目,配置本地IP地址其他人可访问项目(整理)

    1 首先找到config文件夹目录下的 index js文件 Various Dev Server settings host localhost 将localhost进行替换成 0 0 0 0 host 0 0 0 0 can be ov
  • 如何使用USB接口对C51单片机下载固件

    使用USB转UART芯片对单片机下载固件时会遇到的问题 C51系列单片机在下载固件的时候需要断电重启 在使用RS232接口的时候不会遇到什么困难 因为RS232不需要进行识别 但是现在使用USB转UART的芯片时会遇到问题 因为USB设备在
  • CIFAR-10训练模型(ResNet18)

    1 搭建环境 环境在实验进行时已经搭建完毕 具体步骤就不过多赘述 参考 https blog csdn net weixin 39574469 article details 117454061 接下来只需导入所需的包即可 import n
  • python中列表数据汇总和平均值_如何从记录列表中计算平均值

    所以我正在做一个作业 当从一个数据列表中计算一个平均值 数据是从一个外部的 txt文件中读取的 时 我似乎遇到了麻烦 具体来说 我要做的是从下面的数据列表中读取数据记录 在1 2 2014 Frankton 42305 67 23 12 4
  • 高德地图API INVALID_USER_SCODE问题以及keystore问题

    转载地址 http m blog csdn net article details id 50448014 请尊重原创 今天这篇文章会给大家介绍三个问题 1 接入API时出现invalid user scode问题 首先进行第一个大问题 接
  • python连接数据库设置编码_python连接mysql数据库——编码问题

    编码问题 1 连接数据库语句 在利用pycharm连接本地的mysql数据库时 要考虑到的是将数据库语句填写完整 困扰了一下午的问题就是连接语句并没有加入编码设置 db pymysql connect host localhost user
  • 如何利用计算机打印较大的字,如何在一张A4纸上打印一个超大字?

    是不是很想打印超大字 要是硬件上去了 就什么话也不用说了 可惜的是 手中只有一个A4的打印机 怎么办 还是有办法的 用Microsoft Office 2003就可以 我由于学校工作的原因 打印机只能打A4的纸 有时又想打超大字 不得不用现
  • TensorFlow零基础入门,实现手写数字识别

    TensorFlow 是一个用于人工智能的开源神器 主要为深度学习算法提供了很多函数 以及独特的运算支持 废话不多说直接上干货 我的环境 python3 7 tensorflow 1 13 2 numpy 1 20 2 1 入门示例 imp
  • 算法分享三个方面学习方法(做题经验,代码编写经验,比赛经验)

    目录 0 前言 遇到OI不要慌 只要道路对了 就不怕遥远 1 做题经验谈 1 1 做题的目的 1 2 我对于算法比赛的题目的看法 1 2 1 类似题 1 2 2 套模型 1 3 在训练过程中如何做题 1 4 一些建议 提高算法能力 1 5
  • AJAX分页以及IFRAME载入

    AJAX获取数据并分页显示 ul class movList ul div div
  • leetcode-03. 数组中重复的数字刷题笔记(c++)

    写在前面 难度 简单 unordered map 或 sort排序 大数组方法异常溢出 数据量 小数据量 数组元素作为下标 大数据量 无需map映射 耗费空间 sort排序 前后元素是否等值 题目详情 找出数组中重复的数字 在一个长度为 n
  • 一条慢SQL引发的改造

    前言 闲鱼服务端在做数据库查询时 对每一条SQL都需要仔细优化 尽可能使延时更低 带给用户更好的体验 但是在生产中偶尔会有一些情况怎么优化都无法满足业务场景 本文通过对一条慢SQL的真实改造 介绍解决复杂查询的一种思路 以及如何使得一条平均
  • Seata 处理分布式事务

    文章目录 1 Seata 简介2 2 Seata的安装 2 1 修改配置文件 2 2 在nacos上创建配置文件 seataServer yaml 2 3 安装路径seata seata server 1 6 0 seata script
  • C# —— 面向对象编程练习

    C 面向对象编程练习 基础题 代码如下 class Program static void Main string args rectangle r new rectangle Console WriteLine 调用不含参构造函数初始化后
  • [ElasticSearch]painless脚本获取字段方式及性能比较

    一 3种常见方式 doc fieldname 常用于在搜索或排序等无状态操作时进行使用 params source fieldname 也是常用在搜索或排序中 但更多用在获取额外字段时灵活操作时使用 如不获取某个字段直接使用params s
  • 基于图像形态学处理和边缘提取算法的路面裂痕检测matlab仿真

    目录 1 算法运行效果图预览 2 算法运行软件版本 3 部分核心程序 4 算法理论概述 5 算法完整程序工程 1 算法运行效果图预览 2 算法运行软件版本 matlab2022a 3 部分核心程序 Rr Cc size Image1 获取
  • 适配器模式(Adapter)

    适配器是一种结构型设计模式 它能使接口不兼容的对象能够相互合作 又称封装器模式 Wrapper 适配器模式通过封装对象将复杂的转换过程隐藏于幕后 被封装的对象甚至察觉不到适配器的存在 例如 你可以使用一个将所有数据转换为英制单位 如英尺和英