全部 / 前端 / 技术 · 2022年8月12日 0

设计模式 – JavaScript 中的适配器模式

简约防控要求公众号首图__2022-08-14+17_44_25

原文地址:https://dev.to/carlillo/design-patterns—adapter-2pi3

在这篇文章中,我将介绍什么是 Adapter Pattern 以及怎样和什么时候使用应该使用它。

适配器模式:基础概念

适配器模式使得两个互不相融的接口可以一起使用,这是现实世界对适配器的定义。接口可能互不相融,但内部功能应该满足需求。适配器设计模式通过将一个类的接口转换为用户所期望的接口,使本不兼容的类可以一起工作。 — Wikipedia

转换一个类的接口以达到用户期待的另一个接口。适配器让那些接口不兼容的类可以一起工作。 –Design Patterns: Elements of Reusable Object-Oriented Software

这种设计模式的主要特征是使拥有不同接口的类可以一起工作。

这种模式有两个版本:

  • 运行时 object adapter 通过代理到 adaptee 对象上来实现 target 接口。
  • 编译时 class adapter 通过从 adaptee 类继承来实现 target 接口。

由于包括Java在内的许多语言都不支持多重继承,而且与许多问题有关,我们没有显示使用类适配器模式的实现。

对象适配器(又叫适配器)包含它所包装的类的一个实例。这种设计模式的 UML 图如下:

Adaptor 类包含 Adaptee 类,意味着 Adaptee 类将会被 Adaptor 使用。

适配器模式:什么时候使用

  • 有一个类的接口并不满足你的需求。
  • 有几个子类,但通过子类化来适配它们的接口是不实际的。

适配器模式:优势

适配器模式有一些优势,总结以下几点:

  • 代码更具有复用性和弹性。
  • 简洁的代码因为用户端/上下文在每个具体类中没有使用不同的接口而是利用多态性来交换不同的适配器。

适配器模式 – 例子1:用 JavaScript / TypeScript 实现的一个机器人来到城市的例子

我将会用 JavaScript / TypeScript 给你演示如何实现此设计模式。请记住 JavaScript 缺少接口和抽象类。因此,理解此模式最好的方式是通过例子和 TypeScript。在我们这个例子中,我设想了一个名为 Warrior 的抽象类来定义 Warrior 的问题。一个 warrior 有许多但和问题并不相关的属性以及一个名为 attack 的方法。warriors 有类型分类,比如 SaiyanNamekian 定义了 attack 方法的具体实现,但是有一个新类 Android 进入了这个系统。它对 Warrior 的接口并不满意,以至于在内部用不同的方法实现。下面的 UML 图展示了我刚描述的场景。

解决方案是使用一个适配器模式,它包含一个有 Android 对象的类(AndroidAdapter),并使用它来做与 warriors 的接口兼容。使用适配器模式的 UML 图如下:

与模型和接口相关的代码如下:

export interface Warrior {
  ATTACK_BASE: number;
  attack(): number;
}
import { Warrior } from '../interfaces/warrior.interface';

export class Saiyan implements Warrior {
  public ATTACK_BASE = 100;
  public attack(): number {
    return Math.random() * 100 + this.ATTACK_BASE;
  }
}
import { Warrior } from '../interfaces/warrior.interface';

export class Namekian implements Warrior {
  public ATTACK_BASE = 50;
  public attack(): number {
    return Math.random() * 50 + this.ATTACK_BASE;
  }
}
export class Android {
  public punch(): number {
    return 10;
  }
  public kick(): number {
    return Math.random() * this.punch() + this.punch();
  }
}

就像你看到的 Android 类并没有实现 warrior 的接口。所以,AndroidAdapter 类就是用来解决此问题的。

import { Warrior } from '../interfaces/warrior.interface';
import { Android } from './android.model';

export class AndroidAdapter implements Warrior {
  constructor(private android: Android) {}
  public ATTACK_BASE = 50;
  public attack(): number {
    return this.android.kick() + this.android.punch() + this.ATTACK_BASE;
  }
}

最后,用户端/上下文若想使用这三个类,就像下面的代码一样,需要 AndroidAdapterSaiyanNamekian 一起使用。

import { Saiyan } from './models/saiyan.model';
import { Namekian } from './models/namekian';
import { AndroidAdapter } from './models/android-adapter.model';
import { Android } from './models/android.model';

const goku = new Saiyan();
const vegeta = new Saiyan();
const piccolo = new Namekian();
const C17 = new AndroidAdapter(new Android());

console.log(`Goku attack: ${goku.attack()}`);
console.log(`Vegeta attack: ${vegeta.attack()}`);
console.log(`Piccolo attack: ${piccolo.attack()}`);
console.log(`C17 attack: ${C17.attack()}`);

适配器模式 – 例子2:用 JavaScript / TypeScript 来实现多个种族一起工作

适配器模式可以解决的另一个有趣问题是,当有多个不同接口的类但它们需要一起工作。在下面的 UML 图你可以看到此情景:

每个类有一个新的适配器类,适配器类包含最初的对象和从接口实现的方法,这个例子的代码很简单:

export class Human {
  public sharedPower(): number {
    return 10;
  }
}
export class Namekian {
  public getPower(): number {
    return Math.random() * 20 + 20;
  }
}
export class Saiyan {
  myPowerPart1(): number {
    return Math.random() * 100 + 100;
  }
  myPowerPart2(): number {
    return Math.random() * 1000 + 500;
  }
}

以及它们各自的适配器如下:

import { PureRace } from '../interfaces/pure-race.interface';
import { Human } from './human.model';

export class HumanAdapter implements PureRace {
  constructor(private human: Human) {}

  public genki(): number {
    return this.human.sharedPower();
  }
}

/**/

import { PureRace } from '../interfaces/pure-race.interface';
import { Namekian } from './namekian';

export class NamekianAdapter implements PureRace {
  constructor(private namekian: Namekian) {}
  public genki(): number {
    return this.namekian.getPower();
  }
}

/**/

import { PureRace } from '../interfaces/pure-race.interface';
import { Saiyan } from './saiyan.model';

export class SaiyanAdapter implements PureRace {
  constructor(private saiyan: Saiyan) {}
  public genki(): number {
    return this.saiyan.myPowerPart1() + this.saiyan.myPowerPart2();
  }
}

最后,所有的对象都在为打击邪恶和实现伟大的力量贡献能量。

import { Saiyan } from './models/saiyan.model';
import { Namekian } from './models/namekian';
import { Human } from './models/human.model';
import { SaiyanAdapter } from './models/saiyan-adapter.model';
import { NamekianAdapter } from './models/namekian-adapter.model';
import { HumanAdapter } from './models/human-adapter.model';
import { PureRace } from './interfaces/pure-race.interface';

const gohan = new SaiyanAdapter(new Saiyan());
const vegeta = new SaiyanAdapter(new Saiyan());
const piccolo = new NamekianAdapter(new Namekian());
const krilin = new HumanAdapter(new Human());

const everybody = [gohan, vegeta, piccolo, krilin];

const genki = everybody.reduce(
  (power: number, pureRace: PureRace) => power + pureRace.genki(),
  0
);
console.log(`everybody attack: ${genki}`);

总结

当有些类可以一起工作但它们的接口不兼容时,适配器模式会在你的项目中避免重复代码。这篇文章中你看到使用 JavaScript / TypeScript 实现的一个简单例子。

最重要的不是像我展示的那样实现模式,而是要能够认识到这个特定模式能够解决的问题,以及你何时可以或不可以使用此模式。这一点很关键,因为实现方式会因你使用的编程语言不同而不同。