设计模式:(Adapter Pattern) 适配器模式

133 阅读3分钟

欢迎来到 TypeScript 中的设计模式系列,它介绍了使用TypeScript进行Web开发的一些有用的设计模式。

系列文章如下:

设计模式对 Web 开发人员来说非常重要,我们可以通过掌握它们来编写更好的代码。在本文中,我将使用TypeScript来介绍责任链模式

适配器模式的目的是允许两个由于接口不匹配而不能一起工作的对象一起工作。它就像胶水,改变不同的东西,使它们可以一起工作。

在Web系统中,邮件服务是一种非常常用的服务。在 Node.js 平台上,我们可以使用一个nodemailer模块来轻松实现发送邮件的功能。成功安装nodemailer模块后,您可以按照以下步骤发送邮件:

let transporter = nodemailer.createTransport(transport[, defaults]);
transporter.sendMail(data[, callback])

为了避免将邮件服务绑定到特定的服务提供者,在开发邮件服务之前,我们先定义与邮件提供者相关的接口:

interface EmailProvider {
  sendMail(options: EmailOptions): Promise<EmailResponse>;
}

interface EmailOptions {
  to: string | string[];
  subject: string;
  html: string;
  from?: string;
  text?: string;
}

interface EmailResponse {}

根据接口,我们可以很容易的创建一个邮件服务:

class EmailService {
  constructor(public emailProvider: EmailProvider) {}  
  async sendMail(options: EmailOptions): Promise<EmailResponse> {
    const result = await this.emailProvider.sendMail(options);
    return result;
  }
}

目前这个解决方案问题不大,但是如果有一天我们需要使用第三方邮件云服务商。如sendgridmailersend等。您会发现 SDK 用于发送邮件的方法名称为send。所以我们继续定义一个CloudEmailProvider接口:

interface CloudEmailProvider {
    send(options: EmailOptions): Promise<EmailResponse>;
}

对比之前定义的EmailProvider接口,会发现两个方法是不一样的

因此,我们不能直接用于EmailService访问第三方电子邮件云服务。要解决这个问题,有很多方法。下面介绍如何使用适配器模式来解决上述问题。

适配器模式包含以下角色:

  • Client(EmailService) :需要使用 Target 接口的对象;
  • Target(EmailProvider)  : 定义客户端期望的接口;
  • Adapter(CloudEmailAdapter) :将 Adaptee 接口适配到 Target 接口;
  • Adaptee(CloudEmailProvider) :定义需要适配的接口。

在了解了适配器模式之后,让我们创建CloudEmailAdapter类:

class CloudEmailAdapter implements EmailProvider {
  constructor(public emailProvider: CloudEmailProvider) {}  
  
  async sendMail(options: EmailOptions): Promise<EmailResponse> {
    const result = this.emailProvider.send(options);
    return result;
  }
}

在上面的代码中,因为两个接口EmailProviderCloudEmailProvider不匹配,所以我们引入CloudEmailAdapter类来解决兼容性问题。

接下来,我们以 sendgrid 为例来实现一个SendgridEmailProvider

import { MailService } from "@sendgrid/mail";

class SendgridEmailProvider implements CloudEmailProvider {
  private sendgridMail: MailService;  
  
  constructor(private config: { apiKey: string; from: string; }) {
    this.sendgridMail = new MailService();
    this.sendgridMail.setApiKey(this.config.apiKey);
  }  
  
  async send(options: EmailOptions): Promise<EmailResponse> {
    const result = await this.sendgridMail.send(options);
    return result;
  }
}

使用如下:

const sendgridMail = new SendgridEmailProvider({
  apiKey: "******",
  from: "bytefer@gmail.com",
});

const cloudEmailAdapter = new CloudEmailAdapter(sendgridMail);
const emailService = new EmailService(cloudEmailAdapter);

emailService.sendMail({
  to: "******",
  subject: "Adapter Design Pattern",
  html: "<h3>Adapter Design Pattern</h3>",
  from: "juejin@gmail.com",
});

最后总结一下适配器模式的使用场景:

  • 系统需要使用已有的类,该类的接口不符合系统的需要,即接口不兼容;
  • 使用第三方提供的服务,但服务接口定义与自己需要的接口定义不同。