Adapter 适配器模式

一.意图

适配器是一种结构设计模式,允许接口不兼容的对象协作。

将一个类的接口转换成客户希望的另一个接口。Adapter模式使得原本由于不兼容而不能一起工作的那些类可以一起工作。 ------《设计模式》GoF

二.问题

想象你正在创建一个股市监控应用。该应用会以XML格式从多个来源下载股票数据,然后为用户展示精美的图表和示意图。

某个时候,你决定通过集成一个智能的第三方分析库来改进应用。但有个问题:分析库只能处理JSON格式的数据。

你可以把库改成支持 XML。然而,这可能会破坏依赖库的现有代码。更糟的是,你可能根本无法访问库的源代码,这使得这种方法变得不可能。

三.解决方案

你可以创建一个适配器。这是一个特殊的对象,它会转换一个对象的接口,使另一个对象能够理解它。

适配器会包裹其中一个对象,以隐藏幕后转换的复杂性。包裹物体甚至不知道有适配器存在。例如,你可以用一个适配器将所有数据转换为英制单位(如英尺和英里)来包裹一个以米和公里为单位的对象。

适配器不仅可以将数据转换为多种格式,还能帮助具有不同接口的对象协作。具体流程如下:

  1. 适配器会获得一个接口,兼容现有对象之一。

  2. 通过该接口,现有对象可以安全地调用适配器的方法。

  3. 接到调用后,适配器会将请求传递给第二个对象,但以第二个对象预期的格式和顺序。

有时甚至可以创建一个双向适配器,实现呼叫的双向转换。

让我们回到我们的股市应用。为了解决格式不兼容的问题,你可以为代码直接使用的分析库中每个类别创建XML转JSON适配器。然后你调整代码,只通过这些适配器与库通信。当适配器接收调用时,它会将接收的XML数据转换为JSON结构,并将调用传递给相应的包裹分析对象的方法。

四.现实世界的类比

当你第一次从美国前往欧洲旅行时,尝试给笔记本电脑充电时可能会遇到意外。不同国家的电源插头和插座标准不同。这就是为什么你的美国插头装不上德国插座。这个问题可以通过使用带有美式插座和欧洲式插头的电源适配器来解决。

五.结构

对象适配器

该实现采用对象组合原则:适配器实现一个对象的接口,并封装另一个对象。它可以在所有流行的编程语言中实现。

类适配器

该实现采用继承:适配器同时继承两个对象的接口。注意,这种方法只能在支持多重继承的编程语言中实现,比如C++。

六.适合应用场景

  1. 当你想使用某些已有类,但其接口与代码其他部分不兼容时,可以使用 Adapter 类。

    Adapter 模式允许你创建一个中间层类,作为代码与遗留类、第三方类或其他界面奇特类之间的翻译器。

  2. 当你想重用几个缺少某些无法添加到超职业的共同功能的现有子职业时,可以使用这个模式。

    你可以扩展每个子类,把缺失的功能放到新的子类中。不过,你需要在所有这些新类上复制代码,这真的很难闻。

    更优雅的解决方案是将缺失的功能放入适配器类。然后你会在适配器内包裹缺少特征的对象,动态获得所需的特征。为了实现这一点,目标类必须有一个共同接口,适配器的字段也应遵循该接口。这种做法看起来和装饰者图案非常相似。

六.实现方式

  1. 确保你至少有两个接口不兼容的职业:

    • 一个有用的服务类,你无法更改(通常是第三方、遗留或带有大量依赖的)。

    • 一个或多个客户端类别,这些类别会从使用服务类中受益。

  2. 声明客户端接口,描述客户端如何与服务通信。

  3. 创建适配器类,并让它跟客户端界面。目前所有方法都保持空白。

  4. 在适配器类中添加字段以存储服务对象的引用。常见做法是通过构造函数初始化该字段,但有时调用适配器的方法时传递它更方便。

  5. 逐一实现适配器类客户端接口的所有方法。适配器应将大部分实际工作委托给服务对象,仅处理接口或数据格式转换。

  6. 客户端应通过客户端接口使用适配器。这样你就可以更改或扩展适配器,而不会影响客户端代码。

七.优缺点

  1. 优点:

    • 单一责任原则。你可以将接口或数据转换代码与程序的主要业务逻辑分离出来。

    • 开闭原则。只要适配器通过客户端接口与适配器工作,你可以在不破坏现有客户端代码的情况下引入新类型的适配器。

  2. 缺点

    • 代码的整体复杂度会增加,因为你需要引入一套新的接口和类。有时候,直接更改服务类别使其与代码其他部分匹配会更简单。

八.与其他模式的关系

  • 桥接系统通常在前期设计,允许你独立开发应用的各个部分。另一方面,Adapter 通常与现有应用结合使用,使一些本不兼容的类能够良好协同工作。

  • 适配器提供了完全不同的接口来访问现有对象。而装饰者模式的界面要么保持不变,要么被扩展。此外,Decorator支持递归合成,而使用适配器时无法实现。

  • 通过适配器,你可以通过不同的接口访问已有的对象。而Proxy的界面保持不变。使用Decorator时,你可以通过增强的界面访问该物品。

  • Facade 为现有对象定义了一个新的接口,而 Adapter 则试图使现有界面变得可用。适配器通常只包裹一个对象,而 Facade 则处理整个子系统。

  • 桥梁、国家、战略(以及某种程度上的适配器)结构非常相似。事实上,所有这些模式都基于构图,即将工作委托给其他对象。不过,它们各自解决的问题不同。模式不仅仅是用来构建代码的具体配方。它还能向其他开发者传达该模式所解决的问题。

九.示例代码

复制代码
/**
 * 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;
}

执行结果

复制代码
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.
相关推荐
冷崖5 小时前
适配器模式-结构型
适配器模式
进击的小头1 天前
结构型模式:适配器模式(C语言实现与底层实战)
c语言·适配器模式
数据与后端架构提升之路7 天前
TeleTron 源码揭秘:如何用适配器模式“无缝魔改” Megatron-Core?
人工智能·python·适配器模式
会员果汁15 天前
13.设计模式-适配器模式
设计模式·适配器模式
a35354138218 天前
设计模式-适配器模式
设计模式·适配器模式
阿闽ooo1 个月前
深入浅出适配器模式:从跨国插头适配看接口兼容的艺术
c++·设计模式·适配器模式
JavaBoy_XJ1 个月前
结构型-适配器模式
适配器模式
老朱佩琪!1 个月前
Unity适配器模式
unity·设计模式·游戏引擎·适配器模式
有一个好名字1 个月前
设计模式-适配器模式
设计模式·适配器模式