适配器模式
(Adapter)
适配器 是一种结构型设计模式,它能使接口不兼容的对象能够相互合作。
1. 问题
假如你正在开发一款股票市场监测程序,它会从不同来源下载 XML 格式的股票数据,然后向用户呈现出美观的图表。
在开发过程中, 你决定在程序中整合一个第三方智能分析函数库。 但是遇到了一个问题, 那就是分析函数库只兼容JSON 格式的数据。
你无法“直接”使用分析函数库,因为它所需的输入数据格式与你的程序不兼容。
你可以修改程序库来支持 XML。但是,这可能需要修改部分依赖该程序库的现有代码。甚至还有更糟糕的情况,你可能根本没有程序库的源代码,从而无法对其进行修改。
2. 解决方案
你可以创建一个适配器。这是一个特殊的对象,能够转换对象接口,使其能与其他对象进行交互。
适配器模式通过封装对象将复杂的转换过程隐藏于幕后。被封装的对象甚至察觉不到适配器的存在。例如,你可以使用一个将所有数据转换为英制单位(如英尺和英里)的适配器封装运行于米和千米单位制中的对象。
适配器不仅可以转换不同格式的数据,其还有助于采用不同接口的对象之间的合作。它的运作方式如下:
1. 适配器实现与其中一个现有对象兼容的接口。
2. 现有对象可以使用该接口安全地调用适配器方法。
3. 适配器方法被调用后将以另一个对象兼容的格式和顺序将请求传递给该对象。
有时你甚至可以创建一个双向适配器来实现双向转换调用。
让我们回到股票市场程序。为了解决数据格式不兼容的问题,你可以为分析函数库中的每个类创建将 XML 转换为 JSON 格式的适配器,然后让客户端仅通过这些适配器来与函数库进行交流。当某个适配器被调用时,它会将传入的 XML 数据转换为 JSON 结构,并将其传递给被封装分析对象的相应方法。
3. 结构
类适配器的结构
对象适配器的结构
类适配器使用多重继承对一个接口与另一个接口进行匹配。对象适配器依赖于对象组合。
其中:
*Target定义Client使用的与特定领域相关的接口。
*Client与符合Target接口的对象协同。
* Adaptee定义一个已经存在的接口,这个接口需要适配。
*Adapter对Adaptee的接口与Target接口进行适配。
4. 实现方式
1. 确保至少有两个类的接口不兼容:
◦ 一个无法修改(通常是第三方、遗留系统或者存在众多已有依赖的类)的功能性服务类。
◦ 一个或多个将受益于使用服务类的客户端类。
2. 声明客户端接口,描述客户端如何与服务交互。
3. 创建遵循客户端接口的适配器类。
所有方法暂时都为空。
4. 在适配器类中添加一个成员变量用于保存对于服务对象的引用。
通常情况下会通过构造函数对该成员变量进行初始化,但有时在调用其方法时将该变量传递给适配器会更方便。
5.依次实现适配器类客户端接口的所有方法。
适配器会将实际工作委派给服务对象,自身只负责接口或数据格式的转换。
6. 客户端必须通过客户端接口使用适配器。
这样一来,你就可以在不影响客户端代码的情况下修改或扩展适配器。
5. 代码示例
adapter.h
#ifndef DESIGN_PATTERNS_ADAPTER_H
#define DESIGN_PATTERNS_ADAPTER_H
//------------------------------//
#include <iostream>
#include <string>
using namespace std;
//------------------------------//
class Player //"球员"类
{
public:
Player() {}
Player(string);
virtual void Attack() = 0; //攻击指令
virtual void Defense() {} //防御指令
protected:
string name_;
};
class Forward : public Player //"前锋"
{
public:
Forward() {}
Forward(string);
void Attack();//攻击指令
void Defense();//防御指令
};
class Center : public Player //"中卫"
{
public:
Center() {}
Center(string);
void Attack();//攻击指令
void Defense();//防御指令
};
//------------------------------//
class ForeignCenter //"国外中卫"类
{
public:
ForeignCenter() {}
ForeignCenter(string);
void Gong();//攻击指令
void Shou();//防御指令
private:
string name_;
};
//------------------------------//
class Translator : public Player //"翻译"类
{
public:
Translator() {};
Translator(string);
~Translator();
void Attack();//攻击指令
void Defense();//防御指令
private:
ForeignCenter *foreign_center_;
};
//------------------------------//
#endif //DESIGN_PATTERNS_ADAPTER_H
adapter.c
#include "adapter.h"
//------------------------------//
Player::Player(string name) : name_(name) {}
//------------------------------//
Forward::Forward(string name) : Player(name) {}
void Forward::Attack()
{
cout << "前锋 " << name_ << " 进攻" << endl;
}
void Forward::Defense()
{
cout << "前锋 " << name_ << " 防守" << endl;
}
//------------------------------//
Center::Center(string name) : Player(name) {}
void Center::Attack()
{
cout << "中卫 " << name_ << " 进攻" << endl;
}
void Center::Defense()
{
cout << "中卫 " << name_ << " 防守" << endl;
}
//------------------------------//
ForeignCenter::ForeignCenter(string name) : name_(name) {}
void ForeignCenter::Gong()
{
cout << "国外中卫 " << name_ << " 攻。。。" << endl;
}
void ForeignCenter::Shou()
{
cout << "国外中卫 " << name_ << " 守。。。" << endl;
}
//------------------------------//
Translator::Translator(string name) : Player(name)
{
foreign_center_ = new ForeignCenter(name);
}
Translator::~Translator()
{
delete foreign_center_;
}
void Translator::Attack()
{
foreign_center_->Gong();
}
void Translator::Defense()
{
foreign_center_->Shou();
}
//------------------------------//
Main.c
//------------------------------//
#include <iostream>
#include "adapter.h"
using namespace std;
//------------------------------//
// Created by Cls on 2024/03/28.
//------------------------------//
int main(int argc, char *argv[])
{
Forward *forward_;
Center *center_;
Translator *translator_;
//-----------------//
forward_ = new Forward("巴蒂尔");
forward_->Attack();
forward_->Defense();
cout << "------------------" << endl;
//-----------------//
center_ = new Center("拉塞尔");
center_->Attack();
center_->Defense();
cout << "------------------" << endl;
//-----------------//
translator_ = new Translator("姚明");
translator_->Attack();
translator_->Defense();
cout << "------------------" << endl;
//-----------------//
delete forward_;
delete center_;
delete translator_;
//-----------------//
return 0;
}
//------------------------------//
打印输出
6. 应用场景
当你希望使用某个类,但是其接口与其他代码不兼容时,可以使用适配器类。
适配器模式允许你创建一个中间层类,其可作为代码与遗留类、第三方类或提供怪异接口的类之间的转换器。
如果您需要复用这样一些类,他们处于同一个继承体系,并且他们又有了额外的一些共同的方法,但是这些共同的方法不是所有在这一继承体系中的子类所具有的共性。
你可以扩展每个子类,将缺少的功能添加到新的子类中。但是,你必须在所有新子类中重复添加这些代码,这样会使得代码有坏味道。
将缺失功能添加到一个适配器类中是一种优雅得多的解决方案。然后你可以将缺少功能的对象封装在适配器中,从而动态地获取所需功能。如要这一点正常运作,目标类必须要有通用接口,适配器的成员变量应当遵循该通用接口。这种方式同装饰模式非常相似。
7. 优缺点
√ 单一职责原则_你可以将接口或数据转换代码从程序主要业务逻辑中分离。
√ 开闭原则。只要客户端代码通过客户端接口与适配器进行交互,你就能在不修改现有客户端代码的情况下在程序中添加新类型的适配器。
× 代码整体复杂度增加,因为你需要新增一系列接口和类。有时直接更改服务类使其与其他代码兼容会更简单。
8. 与其他模式的关系
• 桥接通常会于开发前期进行设计,使你能够将程序的各个部分独立开来以便开发。另一方面,适配器通常在已有程序中使用,让相互不兼容的类能很好地合作。
• 适配器可以对已有对象的接口进行修改,装饰则能在不改变对象接口的前提下强化对象功能。此外,装饰还支持递归组合,适配器则无法实现。
• 适配器能为被封装对象提供不同的接口,代理能为对象提供相同的接口,装饰则能为对象提供加强的接口。
• 外观为现有对象定义了一个新接口,适配器则会试图运用已有的接口。适配器通常只封装一个对象,外观通常会作用于整个对象子系统上。
• 桥接、状态和策略(在某种程度上包括适配器)模式的接口非常相似。实际上,它们都基于组合模式——即将工作委派给其他对象,不过也各自解决了不同的问题。模式并不只是以特定方式组织代码的配方,你还可以使用它们来和其他开发者讨论模式所解决的问题。