概述

        在大型项目中,我们可能会遇到以下一些典型场景。

        1、需要使用一些现有的类,但其接口不符合要求。

        2、想要创建一个可以重复使用的类,该类可以与其他不相关的类或不可预见的类(即接口可能并不兼容的类)协同工作。

        3、需要使用第三方库或框架中的类,但是它们的接口与系统的其他部分不兼容。

        此时,如果直接修改这些类、库、组件或框架的源代码来适应新的要求,可能会破坏原有的功能或者违反开闭原则。为了解决这个问题,我们可以使用适配器模式。

        电源适配器是运用适配器模式的一个典型例子:大多数国家的电网提供的交流电电压很高(比如:中国的标准家庭用电电压为220V,美国则为120V),但很多便携式电子设备(比如:笔记本电脑、手机等)内部使用的却是直流电,并且需要较低的工作电压(比如:5V、12V等);电源适配器的核心任务就是将电网提供的较高交流电压,转换为设备所需的较低直流电压。

基本原理

        适配器模式是一种结构型设计模式,它允许将一个类的接口转换成客户端所期望的另一个接口。适配器模式的作用就像上面提到的电源适配器一样,能够使两个原本不兼容的接口协同工作。通过适配器模式,我们可以重用那些不符合当前需求接口的现有类,而无需修改它们的内部实现。适配器模式包括如下三个核心组件。

        1、目标接口。客户端期望使用的接口,这个接口定义了客户端想要调用的方法。

        2、适配者。已有的类或对象,其接口与目标接口不兼容,我们希望通过适配器模式让这个类能够被使用。

        3、适配器。负责将适配者的接口转换为目标接口,使得原本由于接口不匹配而无法直接使用的适配者可以被客户端调用。

        适配器模式的基本原理是:包装。适配器模式并不改变适配者的行为或逻辑,而是“包装”或提供一个新的接口。这个接口符合客户端的需求,并且内部会调用适配者的原有方法。适配器模式有如下两种主要形式。

        1、类适配器。通过多重继承的方式实现,适配器类既实现了目标接口,又继承了适配者类。类适配器可以直接访问适配者的所有成员方法和属性,无需额外的代理或包装逻辑。但在某些面向对象的语言中,一个类只能有一个父类,这意味着:如果使用类适配器,则无法再从其他类继承。另外,这种方式的灵活性较差。一旦适配器被定义,它只能适应特定类型的适配者,难以灵活应对多种不同的适配者类型。

        2、对象适配器。通过组合的方式实现,适配器类包含一个适配者的实例作为成员变量,并通过委托的方式调用适配者的方法。这种方式遵循了“组合/聚合优于继承”的面向对象设计原则,提高了代码的可维护性和扩展性,并有效避免了多重继承的问题。

        基于上面的核心组件,适配器模式使用对象适配器方式的实现主要有以下四个步骤。

        1、定义目标接口。创建一个接口,定义客户端希望调用的方法。

        2、创建适配者类。编写一个或多个现有的类,它们具有与目标接口不兼容的接口。

        3、编写适配器类。创建一个适配器类,该类实现目标接口并持有一个适配者类型的成员变量。在适配器类中,重写目标接口的方法,并在这些方法中调用适配者相应的方法。

        4、使用适配器。客户端只需要知道目标接口,就可以通过适配器来间接使用适配者的功能。

实战解析

        在下面的实战代码中,我们使用适配器模式模拟了电源适配器的应用场景。

        首先,我们定义了一个目标接口CDCPowerSupply。它代表了客户端期望使用的接口,即提供5V直流电源的装置。

        然后,我们创建一个适配者类CACPowerSource。它代表了一个标准的220V交流电源,这是许多家庭和办公环境中常见的电源类型。

        接下来,我们编写一个适配器类CVoltageAdapter。它实现了目标接口CDCPowerSupply,并且内部持有一个CACPowerSource实例,负责将220V的交流电转换为5V的直流电。

        最后,我们在客户端代码中演示了如何使用适配器。这里假设我们需要为一个手机充电,而手机需要的是5V的直流电,但我们只有220V的交流电源。可以看到,CVoltageAdapter类充当了CACPowerSource和CDCPowerSupply之间的桥梁,使得原本不兼容的两个组件 —— 220V交流电源和5V直流电源能够协同工作。

#include <iostream>
using namespace std;

// 目标接口:提供5V直流电源的装置
class CDCPowerSupply
{
public:
    virtual void Provide5VDC() = 0;
};

// 适配者:220V交流电源
class CACPowerSource
{
public:
    void Provide220VAC()
    {
        cout << "Providing 220V AC power" << endl;
    }
};

// 适配器:将220V交流电源适配为5V直流电源
class CVoltageAdapter : public CDCPowerSupply
{
public:
    CVoltageAdapter(CACPowerSource* source) : m_pACPowerSource(source) {}

    void Provide5VDC() override
    {
        // 模拟电压转换过程
        cout << "Converting 220V AC to 5V DC..." << endl;
        m_pACPowerSource->Provide220VAC();
        cout << "Now providing 5V DC power" << endl;
    }

    ~CVoltageAdapter()
    {
        delete m_pACPowerSource;
    }

private:
    CACPowerSource* m_pACPowerSource;
};

int main()
{
    // 220V交流电源
    CACPowerSource* pACPowerSource = new CACPowerSource();

    // 使用适配器将220V交流电源转换为5V直流电源
    CDCPowerSupply* pDCPowerSupply = new CVoltageAdapter(pACPowerSource);

    // 直接使用适配器来为手机充电
    pDCPowerSupply->Provide5VDC();

    // 清理资源
    delete pDCPowerSupply;
    return 0;
}

总结

        适配器模式允许现有类在不修改其源代码的情况下被重用,这对于集成第三方库或框架尤其有用,可以避免直接依赖特定的实现细节。另外,对象适配器模式允许一个适配器与多个适配者一起工作,只要它们遵循相同的接口即可,这种灵活性使得系统更容易适应变化的需求。

        但过度使用适配器模式,可能会导致系统的类层次结构过于复杂,增加理解和维护的难度,因为每一个新的适配器都可能引入额外的间接层。在某些情况下,适配器不仅负责接口转换,还可能承担了一些业务逻辑。当适配器开始处理过多的责任时,它的职责边界就变得模糊不清,从而违背了单一职责原则。

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐