design pattern adapter pattern

设计模式-适配器模式-adapter pattern

[TOC]

Overview

  • 适配器模式(Adapter Pattern)是一种结构性设计模式
  • 用于使不兼容的接口能够一起工作
  • 通过一个中间层(即适配器)来转换一个类的接口,使得原本由于接口不兼容而不能一起工作的类可以协同工作。

1.适配器模式(Adapter Pattern)

适配器模式(Adapter Pattern)是一种结构性设计模式,用于使不兼容的接口能够一起工作。它主要通过一个中间层(即适配器)来转换一个类的接口,使得原本由于接口不兼容而不能一起工作的类可以协同工作。

1.1.适配器模式的主要角色包括

  1. 目标(Target)接口

    • 定义客户端使用的特定领域相关的接口。
  2. 适配者(Adaptee)类

    • 一个已经存在的类,需要适配,它拥有与目标接口不兼容的接口。
  3. 适配器(Adapter)类

    • 通过在内部包装一个适配者对象,把源接口转换成目标接口。

1.2.适配器模式的分类

  1. 类适配器模式

    • 使用对象组合,通过继承实现目标接口,并包含一个适配者的实例。
  2. 对象适配器模式

    • 使用对象组合,通过一个单独的类来实现转换,这个类包含一个指向适配者对象的引用。
  3. 接口适配器模式

    • 通过一个接口来适配,通常用于实现多个接口的情况。

1.3.类适配器 对象适配器 接口适配器 c++示例

类适配器、对象适配器和接口适配器是适配器模式的三种形式,它们用于解决接口不兼容的问题。以下是它们之间的主要区别以及C++示例:

1.3.1.类适配器(Class Adapter)

  • 使用继承来实现。
  • 适配器类继承自目标接口和适配者类。

C++ 示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// 目标接口
class ITarget {
public:
    virtual void request() = 0;
};

// 适配者类
class Adaptee {
public:
    void specificRequest() {
        std::cout << "Adaptee specific request" << std::endl;
    }
};

// 类适配器
class ClassAdapter : public ITarget, private Adaptee {
public:
    void request() override {
        specificRequest(); // 调用继承自 Adaptee 的方法
    }
};

1.3.2.对象适配器(Object Adapter)

  • 使用组合来实现。
  • 适配器类包含一个适配者类的实例,并在内部委托调用。

C++ 示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 对象适配器
class ObjectAdapter : public ITarget {
private:
    Adaptee adaptee; // 组合 Adaptee

public:
    void request() override {
        adaptee.specificRequest(); // 委托调用 Adaptee 的方法
    }
};

1.3.3.接口适配器(Interface Adapter)

  • 通常指适配多个接口的情况。
  • 适配器类实现多个源接口,并将它们的调用适配到目标接口。

C++ 示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 另一个适配者接口
class IAnotherAdaptee {
public:
    virtual void anotherRequest() = 0;
};

// 另一个适配者实现
class AnotherAdapteeImpl : public IAnotherAdaptee {
public:
    void anotherRequest() override {
        std::cout << "Another Adaptee request" << std::endl;
    }
};

// 接口适配器实现 ITarget 和 IAnotherAdaptee
class InterfaceAdapter : public ITarget, public IAnotherAdaptee {
public:
    void request() override {
        anotherRequest(); // 实现 ITarget 的请求,委托给 IAnotherAdaptee 的实现
    }

    void anotherRequest() override {
        // ... 实现 IAnotherAdaptee 的请求
    }
};

1.3.4.区别总结

  • 类适配器通过继承实现适配,适用于适配者接口较为固定的情况。
  • 对象适配器通过组合实现适配,提供了更高的灵活性,可以在运行时动态改变适配者对象。
  • 接口适配器通常用于适配多个具有不同接口的适配者,通过实现多个接口来统一调用。

在实际开发中,选择哪种适配器模式取决于具体的设计需求和现有的代码结构。

1.4.适配器模式的应用场景

  • 当你希望使用一个已经存在的类,但这个类的接口与你需要的不兼容时。
  • 当你想创建一个可以复用的类,用于与一个或多个不兼容的接口进行交互时。
  • 当你想提供一个统一的高层接口,用于访问不同的子系统或类库时。

适配器模式使得你可以在不修改原有类代码的前提下,通过引入一个中间层来适配不同类的接口,提高了代码的灵活性和复用性。

2.适配器模式优缺点

  • 优点
    • 单一职责原则你可以将接口或数据转换代码从程序主要业务逻辑中分离。
    • 开闭原则。 只要客户端代码通过客户端接口与适配器进行交互, 你就能在不修改现有客户端- 代码的情况下在程序中添加新类型的适配器。
  • 缺点
    • 代码整体复杂度增加, 因为你需要新增一系列接口和类。 有时直接更改服务类使其与其他代码兼容会更简单。

3.适配器模式应用场景

适配器模式在实际开发中的应用非常广泛,以下是一些常见的应用场景:

  1. 第三方库的集成: 当需要使用一个第三方库,但其接口与现有系统不兼容时,可以创建一个适配器来转换接口。

  2. 新旧系统的迁移: 在新旧系统迁移过程中,旧系统可能使用一些特定的接口,通过适配器模式可以逐步将旧接口替换为新接口。

  3. 硬件设备的控制: 在硬件设备通信中,不同的设备可能有不同的通信协议,适配器模式可以用来统一这些协议,简化控制逻辑。

  4. UI组件的复用: 在图形用户界面开发中,不同厂商提供的组件可能有不同的接口,使用适配器模式可以使这些组件能够在不同的环境中复用。

  5. 支付网关集成: 集成多个支付平台时,每个平台的API可能不同,通过适配器模式可以为应用程序提供一个统一的支付接口。

  6. 数据访问层: 在数据访问层,不同的数据库可能有不同的访问接口,适配器模式可以用来创建一个统一的数据访问接口。

  7. 中间件开发: 中间件通常需要与不同的系统或应用程序交互,适配器模式可以用来适配这些不同的接口。

  8. Web服务的消费者: 当使用多个Web服务时,每个服务可能使用不同的数据格式或通信协议,适配器模式可以用来标准化这些服务的调用。

  9. 遗留代码的重构: 在处理遗留代码时,可能需要修改接口以适应新的需求,适配器模式可以在不修改原有代码的情况下实现这一点。

  10. 模块化设计: 在模块化设计中,不同的模块可能使用不同的接口,适配器模式可以用来连接这些模块,实现松耦合。

  11. 测试驱动开发: 在测试驱动开发中,适配器模式可以用来模拟外部系统的接口,方便进行单元测试。

  12. 云服务集成: 集成不同的云服务时,每个服务可能有不同的API,适配器模式可以用来提供一个统一的接口。

适配器模式通过提供一个中间层来转换接口,使得原本不兼容的接口可以协同工作,从而提高了系统的灵活性和可扩展性。

4.类适配器 对象适配器 接口适配器区别

类适配器、对象适配器和接口适配器都是适配器模式的实现方式,它们用于解决接口不兼容的问题。以下是这三种适配器模式的主要区别:

4.1.类适配器模式(Class Adapter Pattern)

  • 使用继承来实现。
  • 将目标接口作为基类,适配器类继承目标接口并包含一个适配者对象。
  • 由于使用了继承,类适配器模式可能会受限于现有类的继承结构,并且可能违反里氏替换原则。
  • 通常用于适配者和目标接口之间有较大差异的情况。

4.2.对象适配器模式(Object Adapter Pattern)

  • 使用组合来实现。
  • 适配器类包含一个指向适配者对象的引用,并通过委托方式调用适配者的方法。
  • 适配器类实现目标接口,将调用转发给内部的适配者对象。
  • 对象适配器模式更加灵活,不受限于继承结构,可以同时适配多个适配者类。
  • 通常用于需要适配多个具有不同接口的适配者类的情况。

4.3.接口适配器模式(Interface Adapter Pattern)

  • 这个术语在设计模式的官方分类中并不常见,它通常指的是使用接口来实现适配的功能。
  • 接口适配器模式不直接继承或包含适配者,而是定义一个新的接口,该接口将多个不兼容的接口统一起来。
  • 这种模式通常涉及到多个接口的适配,而不是单个类或对象。
  • 它允许一个类实现多个接口,并将这些接口的方法映射到一个统一的接口上。

4.4.应用场景

  • 类适配器适用于你想要使用一个已有的类,并且这个类的方法可以作为基类的一部分时。
  • 对象适配器适用于你想要使用一个已有的类,但是不想通过继承改变其结构时,或者需要适配多个不同的类时。
  • 接口适配器适用于需要将多个不兼容的接口统一为一个接口,以便可以在不同的上下文中使用它们时。

4.5.举例说明

  • 类适配器:假设有一个MediaPlayer接口和一个AudioPlayer类,AudioPlayer有一个播放MP3的方法。如果需要让AudioPlayer播放视频,可以创建一个MediaAdapter类,继承自MediaPlayer,内部包含一个AudioPlayer实例,并添加播放视频的方法。
  • 对象适配器:在上述例子中,如果不希望继承MediaPlayer接口,可以创建一个MediaAdapter类,包含一个AudioPlayer的实例,并实现MediaPlayer接口,将播放视频的方法委托给AudioPlayer的播放MP3方法。
  • 接口适配器:如果存在多个播放接口,如PlayVideoPlayAudio,可以创建一个UnifiedPlayer接口,它将这些播放方法统一起来,然后实现这个接口,将不同的播放请求映射到相应的播放方法。

每种适配器模式都有其适用场景和优缺点,开发者需要根据具体情况选择最合适的适配器模式。

5.在实际项目中,如何决定使用类适配器还是对象适配器?

在实际项目中,决定使用类适配器还是对象适配器取决于多种因素,包括设计要求、项目结构、代码复用性、灵活性需求以及维护性等。以下是一些指导原则,可以帮助你做出决定:

  1. 复用现有子类

    • 如果你需要复用现有的适配者子类,类适配器模式可能更合适,因为它通过继承来扩展现有类的功能。
  2. 多重继承问题

    • 如果你的编程语言不支持多重继承,或者多重继承在项目中导致问题,对象适配器模式是更好的选择,因为它使用组合而非继承。
  3. 修改现有类

    • 如果你不能或不想修改现有的适配者类(例如,当它是第三方库的一部分时),对象适配器模式允许你在不修改原有代码的情况下进行适配。
  4. 灵活性和扩展性

    • 对象适配器模式提供了更高的灵活性,因为它允许你在运行时更换适配者对象,而类适配器则在编译时就确定了适配者。
  5. 单一职责原则

    • 如果希望遵循单一职责原则,避免在类适配器中引入多个适配者接口的实现,对象适配器模式允许你将不同的适配者职责分离到不同的适配器类中。
  6. 接口转换

    • 如果需要将多个不同的接口转换为一个统一的接口,对象适配器模式可能更合适,因为它可以包含多个适配者对象并统一它们的接口。
  7. 代码清晰性和可维护性

    • 如果使用类适配器会使代码变得复杂难以理解,对象适配器模式可能更易于维护,因为它将适配逻辑封装在单独的类中。
  8. 性能考虑

    • 在某些对性能要求极高的场景下,继承(类适配器)可能比组合(对象适配器)有更小的运行时开销。但这种差异通常非常小,不应成为主要决策因素。
  9. 设计一致性

    • 考虑项目中的其他设计模式和原则,选择与项目整体设计风格一致的适配器模式。
  10. 团队熟悉度

    • 考虑团队对两种模式的熟悉程度和使用经验,选择团队更熟悉和更易于维护的模式。

在实际应用中,两种适配器模式都可以用来解决接口不兼容的问题,选择哪一种取决于具体的上下文和上述提到的考虑因素。有时,为了保持设计的一致性或者满足特定的技术需求,项目可能会选择使用其中一种模式。在其他情况下,两种模式都适用,选择哪一种更多地取决于个人或团队的偏好。

6.接口适配器在多线程环境下使用时需要注意哪些问题?

在多线程环境下使用接口适配器时,需要注意以下几个问题:

  1. 线程安全性: 确保适配器实现或继承的接口或类是线程安全的。如果适配器中包含共享资源,需要使用适当的同步机制(如互斥锁、信号量等)来避免竞态条件和数据不一致问题 。

  2. 避免死锁: 在使用多个锁或资源时,要注意避免死锁的发生。确保以一致的顺序获取锁,或者使用锁管理工具来帮助避免死锁 。

  3. 资源管理: 适配器在多线程环境中可能会涉及到资源的分配和释放,需要确保资源的正确管理,避免资源泄露或重复释放 。

  4. 性能考虑: 适配器可能会引入额外的同步开销,特别是在高并发场景下。需要评估并优化适配器的性能,以避免成为系统的性能瓶颈 。

  5. 异常处理: 适配器在多线程环境中可能会遇到线程中断或异常情况,需要妥善处理这些异常,确保系统的稳定性和健壮性 。

  6. 线程局部变量: 如果适配器需要为每个线程维护独立的状态,可以使用线程局部变量(Thread Local Storage, TLS)来避免共享状态的问题 。

  7. 并发集合类: 考虑使用Java提供的并发集合类,如ConcurrentHashMap,这些集合类在多线程环境下自动处理线程安全问题,可以减少适配器实现的复杂性 。

  8. 锁的粒度和公平性: 选择合适的锁粒度和公平性策略,以平衡性能和线程调度的公平性,避免线程饥饿或锁竞争 。

  9. 线程池的使用: 在多线程环境中,使用线程池来管理线程的创建和销毁,可以有效减少线程创建和销毁的开销,提高资源利用率 。

通过考虑这些方面,可以确保接口适配器在多线程环境下的安全性、稳定性和性能。


关于作者

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy