掘金 后端 ( ) • 2024-04-22 09:24

1. 适配器模式介绍

适配器模式(Adapter Pattern)是一种结构型设计模式,用于将一个类的接口转换成客户端所期望的另一种接口。它允许原本由于接口不匹配而无法一起工作的类能够协同工作。适配器模式通常涉及一个称为适配器的类,它充当两个不兼容接口之间的桥梁。

2. 关键思想

适配器模式的关键思想是允许接口不兼容的类能够协同工作。这是通过引入一个适配器来实现的,适配器充当两个不同接口之间的桥梁,使得它们能够一起工作。

  1. 接口不匹配: 适配器模式通常应用于一个类的接口与另一个类所期望的接口不匹配的情况。这种不匹配可能涉及方法的名称、参数类型、返回类型等方面的差异。
  2. 适配器类: 适配器模式引入了一个适配器类,该类负责将被适配类的接口转换为客户端所期望的接口。适配器可以是类适配器(使用继承)或对象适配器(使用组合)。
  3. 保持兼容性: 适配器的目标是保持被适配类的现有功能,同时使其与目标接口兼容。这样,现有的代码可以继续使用被适配类,而不需要修改。
  4. 解耦性: 适配器模式可以帮助解耦系统中的组件,因为它允许客户端代码与适配器的接口进行交互,而不直接与被适配类交互。这提高了系统的灵活性和可维护性。
  5. 透明性: 对于客户端代码而言,适配器应该是透明的,即客户端代码不应该感知到适配器的存在。客户端只需要与目标接口交互,而不需要关心被适配类的具体实现。

总的来说,适配器模式的关键思想是通过引入适配器,使得原本不兼容的接口能够协同工作,同时保持现有代码的稳定性。这有助于系统的扩展和维护。

3. 实现方式:

适配器模式可以通过两种主要的实现方式来实现:类适配器和对象适配器。这两种方式都允许将一个类的接口转换成另一个类的接口,但它们在实现上有一些不同。

我们使用一个生活中的例子来说明适配器模式。假设我们有一个英国插头(被适配类),而我们在中国(目标接口)需要使用中国插座。我们可以创建一个适配器,将英国插头适配到中国插座上。

3.1. 类适配器(Class Adapter):

在类适配器中,适配器类通过继承被适配类,并同时实现目标接口,从而将被适配类的接口转换为目标接口。以下是类适配器的基本结构和代码实现:

示例代码

// 被适配的类 - 英国插头
class BritishPlug {
    public void plugIn() {
        System.out.println("插上英国插头");
    }
}
// 目标接口 - 中国插座
interface ChineseSocket {
    void plugIn();
}
// 类适配器
class SocketAdapter extends BritishPlug implements ChineseSocket {
    @Override
    public void plugIn() {
        plugIn(); // 调用英国插头的方法
        System.out.println("通过适配器转接,插上中国插座");
    }
}
// 客户端代码
public class Client {
    public static void main(String[] args) {
        ChineseSocket chineseSocket = new SocketAdapter();
        chineseSocket.plugIn();
    }
}

要点:

  1. 继承被适配类: 类适配器通过继承被适配类,同时实现目标接口,使得适配器类既能访问被适配类的方法,又能符合目标接口的规范。
  2. 单继承限制: 类适配器的一个限制是被适配类必须是一个类而不能是接口,因为Java不支持多重继承。如果被适配类是一个接口,应该考虑使用对象适配器。
  3. 适配器重写方法: 在适配器中,需要重写目标接口的方法,并在方法中调用被适配类的相应方法。这样可以实现对目标接口的适配。

注意事项:

  1. 对被适配类的依赖: 类适配器会对被适配类进行继承,导致适配器类与被适配类紧密耦合。这意味着如果被适配类发生变化,适配器类可能也需要进行相应的调整。
  2. 不适用于所有情况: 由于类适配器采用继承,因此如果被适配类有final修饰或者它的一些方法是final的,那么就无法通过继承进行适配。
  3. 无法适配被适配类的子类: 类适配器适配的是被适配类本身,而不是它的子类。如果有其他子类需要适配,可能需要创建新的适配器。
  4. 不够灵活: 类适配器的设计决定了它在适配过程中是静态的,因此无法在运行时改变适配器的行为。如果需要更灵活的适配器,可以考虑使用对象适配器。

总的来说,类适配器是一种简单的适配器模式实现,但在某些情况下可能存在一些限制。在选择适配器实现方式时,需要根据具体的需求和设计上下文来决定是否使用类适配器。

优点:

  1. 复用已有代码: 类适配器通过继承被适配类,能够复用被适配类的代码,不需要重新实现被适配类的方法。
  2. 适配多个适配者: 由于类适配器继承了被适配类,可以同时适配被适配类及其子类,实现对多个适配者的适配。
  3. 能够重定义行为: 类适配器可以在适配器中重定义被适配类的方法,从而改变或扩展原有的行为。

缺点:

  1. 单一继承限制: 类适配器采用继承方式,因此被适配类必须是一个类而不能是接口,这限制了适配器对于被适配类的选择。
  2. 与被适配类耦合紧密: 类适配器继承了被适配类,导致适配器与被适配类之间的耦合较为紧密。如果被适配类发生变化,适配器类可能需要相应调整。
  3. 不够灵活: 类适配器的适配过程是静态的,无法在运行时动态改变适配器的行为。这可能不够灵活,尤其在需要动态适配的情况下。

应用场景:

  1. 已有类库适配: 当你需要使用一个已经存在的类,但它的接口与你的需求不匹配时,可以使用类适配器来适配已有的类。
  2. 适配多个适配者: 当需要同时适配一个类及其子类时,类适配器是一种较为合适的选择。
  3. 重定义行为: 当你希望适配器在适配过程中可以重定义被适配类的方法时,类适配器是一个合适的选择。
  4. 系统设计中的统一接口: 在系统设计中,可能由于历史原因或者其他因素,存在不同的接口,而需要提供一个统一的接口给客户端使用,这时可以考虑使用适配器模式。

总的来说,类适配器适用于需要适配一个类及其子类,并且可以通过继承来复用已有代码的情况。在使用适配器模式时,需要根据具体情况权衡其优缺点,选择合适的实现方式。

3.2. 对象适配器

在对象适配器中,适配器类持有被适配类的实例,并同时实现目标接口。适配器类将目标接口的方法调用委托给被适配对象的相应方法。以下是对象适配器的基本结构和代码实现:

示例代码

// 对象适配器
class SocketAdapter implements ChineseSocket {
    private BritishPlug britishPlug;

    public SocketAdapter(BritishPlug britishPlug) {
        this.britishPlug = britishPlug;
    }

    @Override
    public void plugIn() {
        britishPlug.plugIn(); // 调用英国插头的方法
        System.out.println("通过适配器转接,插上中国插座");
    }
}
// 客户端类,用于演示适配器模式的应用
public class Client {
    public static void main(String[] args) {
        // 创建一个英国插头实例
        BritishPlug britishPlug = new BritishPlug();

        // 创建适配器并传入英国插头实例,以适配到中国插座
        ChineseSocket chineseSocket = new SocketAdapter(britishPlug);

        // 调用适配器的插入方法,实现英国插头插入中国插座的功能
        chineseSocket.plugIn();
    }
}

要点:

  1. 组合被适配对象: 对象适配器通过组合方式持有被适配对象的实例。这意味着适配器可以调用被适配对象的方法,从而实现适配。
  2. 实现目标接口: 对象适配器实现了目标接口,使得客户端代码可以通过目标接口与适配器交互,而不需要直接与被适配对象交互。
  3. 动态替换被适配对象: 由于采用组合的方式,对象适配器允许在运行时动态替换被适配对象,提供更高的灵活性。
  4. 降低耦合度: 对象适配器通过组合的方式,使得适配器与被适配对象之间的关系相对独立,从而降低了耦合度。

注意事项:

  1. 被适配对象的适配: 在对象适配器中,适配器是通过组合被适配对象来实现适配的,而不是继承。确保适配器正确地持有被适配对象的实例。
  2. 额外对象的引入: 对象适配器引入了额外的对象实例,可能会增加一些开销。在考虑使用对象适配器时,需要权衡额外对象的引入和适配的灵活性。
  3. 接口的一致性: 被适配对象和目标接口之间的接口一致性非常重要。确保适配器实现了目标接口,并能够正确地转发请求给被适配对象。
  4. 动态替换被适配对象的时机: 如果需要在运行时动态替换被适配对象,确保替换时不会影响正常的系统运行。考虑潜在的并发和状态问题。
  5. 适配器的一致性: 如果系统中存在多个适配器,确保它们之间的一致性,以避免在不同部分之间引入混乱和不一致的情况。

总体来说,对象适配器通过组合和实现目标接口的方式,提供了更灵活、更低耦合度的适配器模式实现。在使用对象适配器时,需要注意接口一致性、动态替换被适配对象的时机等方面。

优点:

  1. 更灵活的适配: 对象适配器通过组合方式持有被适配对象的实例,使得可以动态替换被适配对象,提供更灵活的适配能力。
  2. 不受单一继承限制: 对象适配器不受单一继承的限制,可以适配被适配类及其子类,提供更强大的适配能力。
  3. 降低耦合度: 由于对象适配器采用组合方式,适配器与被适配对象之间的关系相对独立,降低了耦合度。
  4. 支持适配器重定义行为: 对象适配器通过实现目标接口,可以在适配器中重定义被适配类的方法,从而改变或扩展原有的行为。

缺点:

  1. 需要额外对象: 对象适配器引入了额外的对象实例,可能会增加一些开销。
  2. 可能导致过多小对象: 如果每个被适配对象都有一个单独的适配器对象,可能会导致系统中存在过多小的适配器对象,影响性能。

适用场景:

  1. 适配多个适配者及其子类:
    • 情景: 你有多个类,它们都实现了相同的接口或继承自相同的类。
    • 解释: 对象适配器适用于需要适配多个适配者及其子类的情况,可以通过组合的方式进行适配。
  1. 需要动态替换被适配对象:
    • 情景: 你希望在运行时能够灵活地替换被适配对象。
    • 解释: 对象适配器允许在运行时替换被适配对象,提供更高的灵活性。
  1. 降低耦合度:
    • 情景: 你希望适配器与被适配对象之间的关系相对独立,降低耦合度。
    • 解释: 对象适配器通过组合方式,使得适配器与被适配对象之间的关系较为独立,适用于需要降低耦合度的场景。
  1. 支持适配器重定义行为:
    • 情景: 你需要在适配过程中重定义被适配类的方法,改变或扩展原有的行为。
    • 解释: 对象适配器通过实现目标接口,支持适配器重定义被适配类的方法,提供更灵活的行为定制。

总体来说,对象适配器适用于需要更灵活、更强大的适配能力,并且可以容忍一些额外对象的场景。在实际应用中,选择适配器实现方式需根据具体需求和设计上下文进行权衡。