JS `Adapter Pattern`:封装不兼容接口,使其协同工作

各位观众,大家好!我是今天的讲座主持人,很高兴和大家一起聊聊设计模式中的一位“和事佬”——Adapter Pattern(适配器模式)。 别看它名字洋气,其实作用很简单,就是把两个原本八竿子打不着的家伙,硬生生撮合到一起,让他们能好好合作。

咱们今天就来深入剖析一下这个模式,看看它到底是怎么做到“化干戈为玉帛”的。

一、 啥是适配器模式? 为什么要它?

设想一下,你家里有个老式的插座,只有两孔,可是你新买了个电器,偏偏是三孔插头,这可咋办? 你总不能把电器扔了吧? 这时候,你就需要一个“转换插头”,把三孔插头转换成两孔插头,这样就能正常使用了。

Adapter Pattern 的作用和这个“转换插头”类似。 它可以将一个类的接口转换成客户希望的另外一个接口。 Adapter 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

简单来说,就是:“让不兼容的接口兼容起来”

那么,为什么要用适配器模式呢? 原因有很多:

  • 系统需要使用现有的类,而此类的接口不符合系统的需求。 比如,我们公司之前用的是A公司的支付接口,现在要换成B公司的,但是B公司的接口和我们现有的系统不兼容,这时候就可以用适配器模式。
  • 想要创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类协同工作。 比如,你写了一个处理数据的类,但是你不知道以后会用到哪些数据源,这时候可以用适配器模式,让你的类可以适应不同的数据源。
  • 需要使用几个现有的类,但他们的接口并不完全一致。 通过 Adapter 模式,可以对多个类进行统一的抽象,让它们看起来像一个整体。

总而言之,当你的系统需要和一些接口不兼容的外部系统或者类库打交道的时候,适配器模式就派上用场了。

二、 适配器模式的两种实现方式:类适配器和对象适配器

适配器模式主要有两种实现方式:

  1. 类适配器(Class Adapter): 基于继承来实现。 适配器类继承了被适配的类,同时实现了目标接口。

  2. 对象适配器(Object Adapter): 基于组合来实现。 适配器类持有一个被适配类的实例,然后实现目标接口,并在目标接口的方法中调用被适配类的方法。

这两者有什么区别呢? 咱们用表格来对比一下:

特性 类适配器 对象适配器
实现方式 继承 组合
优点 简单直接,容易理解 更加灵活,可以适配多个被适配类,符合合成复用原则
缺点 只能适配一个类,耦合度较高,违反合成复用原则 实现稍微复杂一点
使用场景 被适配的类接口稳定,不需要适配多个类 需要适配多个类,或者被适配的类接口可能会变化

三、 代码实战: 模拟不同格式的日志记录器

为了更好地理解这两种实现方式,我们来模拟一个场景: 系统需要支持多种日志记录器,但是这些日志记录器的接口不一致。 我们需要用适配器模式来统一这些日志记录器的接口,方便系统使用。

假设我们有以下几种日志记录器:

  • LegacyLogger: 一个老式的日志记录器,只有一个log方法,接收一个字符串参数。
  • ModernLogger: 一个现代的日志记录器,有一个record方法,接收一个对象参数,对象包含messagelevel属性。

我们的目标是创建一个统一的ILogger接口,包含一个logMessage方法,接收一个字符串参数,然后用适配器模式来适配这两种日志记录器。

1. 定义目标接口 ILogger

// 目标接口: ILogger
class ILogger {
  logMessage(message) {
    throw new Error("Method 'logMessage()' must be implemented.");
  }
}

2. 定义需要被适配的类 LegacyLogger

// 需要被适配的类: LegacyLogger
class LegacyLogger {
  log(message) {
    console.log(`[Legacy] ${message}`);
  }
}

3. 定义需要被适配的类 ModernLogger

// 需要被适配的类: ModernLogger
class ModernLogger {
  record(logData) {
    console.log(`[Modern] Level: ${logData.level}, Message: ${logData.message}`);
  }
}

4. 实现类适配器 LegacyLoggerAdapter

// 类适配器: LegacyLoggerAdapter (继承 LegacyLogger, 实现 ILogger)
class LegacyLoggerAdapter extends LegacyLogger extends ILogger{
  constructor() {
    super();
  }

  logMessage(message) {
    this.log(message); // 调用被适配类的 log 方法
  }
}

5. 实现对象适配器 ModernLoggerAdapter

// 对象适配器: ModernLoggerAdapter (组合 ModernLogger, 实现 ILogger)
class ModernLoggerAdapter extends ILogger {
  constructor(modernLogger) {
    super();
    this.modernLogger = modernLogger; // 持有被适配类的实例
  }

  logMessage(message) {
    this.modernLogger.record({ message: message, level: 'INFO' }); // 调用被适配类的 record 方法
  }
}

6. 使用适配器:

// 使用适配器
const legacyLogger = new LegacyLogger();
const legacyLoggerAdapter = new LegacyLoggerAdapter();
legacyLoggerAdapter.logMessage("This is a legacy log message."); // 输出: [Legacy] This is a legacy log message.

const modernLogger = new ModernLogger();
const modernLoggerAdapter = new ModernLoggerAdapter(modernLogger);
modernLoggerAdapter.logMessage("This is a modern log message."); // 输出: [Modern] Level: INFO, Message: This is a modern log message.

在这个例子中,LegacyLoggerAdapter 是一个类适配器,它继承了 LegacyLogger 类,并且实现了 ILogger 接口,将 LegacyLoggerlog 方法适配成了 ILoggerlogMessage 方法。

ModernLoggerAdapter 是一个对象适配器,它持有一个 ModernLogger 类的实例,并且实现了 ILogger 接口,将 ModernLoggerrecord 方法适配成了 ILoggerlogMessage 方法。

这样,我们就可以使用统一的 ILogger 接口来记录不同格式的日志了。

四、 适配器模式的优点和缺点

说了这么多,适配器模式也不是完美的,它也有自己的优缺点:

优点:

  • 提高了类的复用性: 通过适配器,我们可以将一些不兼容的类组合在一起,让他们能够协同工作。
  • 提高了系统的灵活性: 当需要更换或者增加新的类时,只需要修改或者增加适配器,而不需要修改原有的代码。
  • 符合开闭原则: 可以在不修改现有代码的情况下,引入新的适配器。

缺点:

  • 增加了系统的复杂性: 引入适配器类会增加系统的类数量,增加了系统的复杂性。
  • 可能会降低系统的性能: 适配器类需要进行额外的转换,可能会降低系统的性能。

五、 使用适配器模式的注意事项

在使用适配器模式时,需要注意以下几点:

  • 明确适配的目标: 在使用适配器模式之前,需要明确适配的目标,即需要将哪些类或者接口适配成什么样子。
  • 选择合适的适配器类型: 根据实际情况选择合适的适配器类型,类适配器还是对象适配器。
  • 避免过度使用: 不要为了适配而适配,只有在确实需要适配的情况下才使用适配器模式。

六、 适配器模式的应用场景

适配器模式在实际开发中有很多应用场景:

  • 数据库适配: 当需要更换数据库时,可以使用适配器模式来适配不同的数据库接口。
  • 第三方库适配: 当需要使用第三方库时,可以使用适配器模式来适配第三方库的接口。
  • 遗留系统改造: 当需要将遗留系统集成到新的系统中时,可以使用适配器模式来适配遗留系统的接口。
  • 不同格式的数据转换: 将不同格式的数据转换为统一的格式,方便系统处理。

七、 总结

Adapter Pattern 就像一个万能插座,能够让各种不同规格的插头都能顺利插上。 它通过封装不兼容的接口,使得原本无法协同工作的类可以一起工作,提高了代码的复用性和系统的灵活性。

当然,适配器模式也不是万能的,它也有自己的缺点,需要根据实际情况选择是否使用。 希望今天的讲座能够帮助大家更好地理解和使用适配器模式,让大家在编程的道路上更加得心应手!

感谢大家的观看! 下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注