TypeScript 实现适配器模式

565 阅读3分钟

这是我参与11月更文挑战的第23天,活动详情查看:2021最后一次更文挑战

适配器模式

今天我们来学习适配器模式,适配器在生活中很常见,比如电源适配器,就可以把外界的电压转变为设备能接受的电压。

当我们带着电脑出国时,只需要买一个适合国外电压和适配器,就可以接上自己的电脑了,而不需要对电脑进行改造。

适配器模式就是一种类似的思想,通过增加一种适配器,可以不必修改原有的逻辑,就能接受新的输入,下面我们来看看适配器模式,在 TS 中,是如何实现的。

假设我们有个库,提供 Api 这样一个类,放在 Api.ts 文件,可以通过对应的方法,获取到对应的信息。

export default class Api {
  getName() {}
  getAge() {}
  getGender() {}
}

然后我们封装一个函数 getInfo,放在 getInfo.ts。

getInfo 函数需要传入 api 的实例,接着会调用 api 的方法,最后返回一个对象。

import Api from './Api';

export default function getInfo(api: Api) {
  return {
    name: api.getName(),
    age: api.getAge(),
    gender: api.getGender()
  }
}

那我们怎么来使用他们呢?

首先要引入 api 和 getInfo,执行 getInfo 函数的时候,将 api 实例传入。

// index.ts
import Api from './Api';
import getInfo from './getInfo';

let api = new Api();
getInfo(api);

这样的话,我们预期就可以得到这个对象的结果。

可是经过某一次改造,原来的 Api 被改造为 newApi,并且里面的方法名字都变了。

// NewApi.ts
export default class NewApi {
  get_name() {}
  get_age() {}
  get_gender() {}
}

以前是驼峰式的,现在被改造为下划线的格式。那我们肯定无法沿用以前的 getInfo 了。

我们可以来试一下。

// index.ts
import Api from './Api';
import getInfo from './getInfo';
import NewApi from './NewApi';

let api = new Api();
getInfo(api);

let newApi = new NewApi();
getInfo(newApi); // 报错

出现报错,因为 newApi 和 api 里面的结构是不一样的。

image.png

如果我们希望不去改造这个 getInfo 方法,就可以直接接收传入的 newApi,那应该怎么办呢?

这个时候就可以创建一个适配器,首先它继承自 api,然后它的构建函数需要传入一个 newApi 的实例,接着会把这个 newApi 保存起来,然后它会覆盖 api 里面的那些方法,并且将它们改造为 newApi 的调用。

这样的话,就实现了将 NewApi 适配为 api 的适配器。

// NewApiAdapter.ts
import Api from './Api';
import NewApi from './NewApi';

export default class NewApiAdapter extends Api {
  newApi: NewApi;

  constructor(newApi: NewApi) {
    super();
    this.newApi = newApi;
  }

  getName() {
    return this.newApi.get_name();
  }
  getAge() {
    return this.newApi.get_age();
  }
  getGender() {
    return this.newApi.get_gender();
  }
}

下面我们来使用下。

// index.ts
import Api from './Api';
import getInfo from './getInfo';
import NewApi from './NewApi';
import NewApiAdapter from './NewApiAdapter';

let api = new Api();
getInfo(api);

let newApi = new NewApi();
let adaptedApi = new NewApiAdapter(newApi);
getInfo(adaptedApi);

这样的话,就不会报错了,为什么呢?

因为 NewApiAdapter 它是继承自 api 的,所以这个 getInfo,它接受的参数,能够接收到 NewApiAdapter 创建的实例,这样就实现了对 api 的适配。

习题:理解适配器模式,并计算下面结果

// 第一版
const sumV1 = (num1: number, num2: number) => {
  return num1 + num2;
}
sumV1(1, 2);

// 第二版
const sumV2 = (arr: number[]) => {
  return arr.reduce((prev, curr) => {
    return prev + curr;
  })
}
sumV2([1, 2, 3]);

// 适配器兼容版
const sumV3 = (...args: any[]) => {
  if (Object.prototype.toString.call(args[0]) === '[object Number]') {
    return sumV1(args[0], args[1]);
  } else if (Object.prototype.toString.call(args[0]) === '[object Array]') {
    return sumV2(args[0]);
  } else {
    throw Error('input error');
  }
}

sumV3(1, 2); // 结果?
sumV3([1, 2, 3]); // 结果?

答案:

3 6

解析:

适配器模式主要作用使两个不兼容的东西可以兼容工作。

第一版和第二版分别是两个版本的累加求和器,由于数据源数据类型不同,我们需要选择不同的版本。现在引入适配器兼容版,我们不需要关心数据源的数据类型,直接使用 sumV3 进行求和即可。sumV3 内部不做重新实现,直接使用原有的版本处理即可。答案比较简单 36