适配器是一种结构型设计模式,用于将一个接口转换为另一个客户端所需要的接口。该模式通过创建一个适配器对象,使不兼容的接口可以协同工作。
适配器模式主要分为三个角色:适配器类、目标类、适配者类。
适配器模式分为对象适配器和类适配器。
对象适配器是基于组合实现,而类适配器通过多继承方式实现。在类适配器模式中,适配器类即继承目标类,又继承适配者类,从而能够调用适配者类的方法,并将其适配到目标类身上。相比之下,对象适配器更加灵活,因为它是继承目标类、定义适配者对象,在目标类方法中调用适配者对象的方法,这种方法支持我们可以适配多个适配者类(也就是定义多个适配者类),所以更加灵活。
主要区别包括:
1、实现方式:对象适配器使用对象组合(composition)实现适配器功能,而类适配器使用多重继承(multiple inheritance)实现适配器功能。
2、关系:对象适配器通过持有适配者对象的引用来连接适配器与适配者,类适配器通过同时继承目标类和适配者类来连接适配器与适配者。
3、灵活性:对象适配器更加灵活,可以适配多个适配者类,并且可以动态切换适配者;类适配器在编译时就确定了适配者类,无法在运行时动态切换适配者。
因为类适配器是在编译时确定的,所以基本没有优点,我们这里举一个对象适配器的例子。
假设我们现在有一个模拟动物各种行为的机器人,它定义了叫喊和奔跑两种方法,我们现在希望不修改现有代码的基础上让机器人模仿狗的叫声和跑步姿势。
#include <iostream>
#include <memory>
// 目标抽象类Robot
class IRobot
{
public:
virtual ~IRobot() {}
virtual void Cry() = 0;
virtual void Run() = 0;
};
// 适配者类 狗
class Dog
{
public:
void Wang()
{
std::cout << "狗叫" << std::endl;
}
void Run()
{
std::cout << "狗跑" << std::endl;
}
};
// 适配器类
class DogAdapter
: public IRobot
{
public:
DogAdapter(std::shared_ptr<Dog> _dog)
: dog_(_dog)
{}
virtual void Cry() override
{
std::cout << "机器人模仿: ";
if (dog_)
dog_->Wang();
}
virtual void Run() override
{
std::cout << "机器人模仿: ";
if (dog_)
dog_->Run();
}
private:
std::shared_ptr<Dog> dog_;
};
测试:
void TestAdapter()
{
std::shared_ptr<IRobot> robot = std::make_shared<DogAdapter>(std::make_shared<Dog>());
robot->Cry();
robot->Run();
}
输出:
机器人模仿: 狗叫
机器人模仿: 狗跑
为了实现这个示例,我们创建了一个抽象目标类 IRobot,定义了叫和跑两种方法。创建了一个具体适配者类 Dog,同样实现了叫和跑两种方法,创建了一个适配器类 DogAdapter继承IRobot,并且定义一个适配者对象,重写这两种方法,在这两种方法里调用适配者对象的叫和跑两种方法到达机器人模拟狗叫和狗跑的目的。
适配器模式遵循以下设计原则:
1、单一职责原则:适配器类的主要责任是将不兼容的接口转换为兼容的接口,它应该只关注适配工作,不涉及其他功能。
2、开闭原则:适配器模式允许系统在不修改现有代码的情况下引入新的适配器类,以适应不同的适配需要。
优点:
1、增加代码的可重用性:适配器模式可以重用现有的适配者类,无需修改其代码,增加系统的可维护性和灵活性。
2、系统的解耦:适配器模式将客户端和适配者类进行解耦,使得客户端不需要关心具体适配者类的实现细节。而且适配者类发生改变不影响客户端。
3、提高系统的扩展性:适配者模式允许在不修改现有代码的情况下增加新的适配器类以适应不同的接口需求。
缺点:
1、增加系统的复杂性:因为引入了适配器类,增加了系统的复杂性,对于简单系统可能显得过于繁琐。
2、增加运行时的开销:由于适配器需要进行接口转换和数据转换,可能会引入一些系统运行时的开销。