跳到主要内容

27 篇博文 含有标签「JS」

Javascript

查看所有标签

适配器模式 (Adapter)

· 阅读需 3 分钟

适配器模式是一种结构性设计模式,能够使不兼容的对象相互合作。可以担任两个对象之间的封装器,将一个对象的调用转换为另一个对象可识别的调用。

适配器模式允许你创建一个中间层类, 其可作为代码与遗留类、 第三方类或提供怪异接口的类之间的转换器。

特点:

  • 单一职责原则,可以将接口或数据转换代码从程序主要业务逻辑中分离。
  • 开闭原则,只要客户端代码通过客户端接口与适配器进行交互,你就能在不修改现有客户端代码的情况下在程序中添加新类型的适配器。
  • 代码整体复杂度增加,因为需要新增一系列接口和类。有时直接更改服务类使其与其他代码兼容会更简单。

示例

客户端要通过标准 Requestable 接口规范来请求,像 request('Hi') 这样调用。但一个重要的功能,调用方式并不规范,已经过时,但又不好重写,需要传两个参数 request('Hi', '') 这样来调用,这时就需要适配器来解决。

interface Requestable {
request(str: string): void;
}

// 适配器遵循客户端要求的接口,将其转换为符合遗留类要求的调用方式,对遗留类的函数进行调用。
class Adapter implements Requestable {
private adaptee: Adaptee;

constructor(adaptee: Adaptee) {
this.adaptee = adaptee;
}

// 符合规范的调用,内部转为符合遗留标准的调用
public request(str: string): void {
this.adaptee.wierdRequest(str, '');
}
}

// 遗留的功能类
class Adaptee {
public wierdRequest(str1: string, str2: string) {
console.log(str1 + str2);
}
}

// 客户端代码,只会调 request('Hi') 这样,不会传两个参数
function clientCode(target: Requestable) {
target.request('Hi');
}

clientCode(new Adapter(new Adaptee()));

生成器模式 (Builder)

· 阅读需 4 分钟

生成器模式也叫建造者模式,这种模式可以分步骤创建复杂对象。该模式允许你使用相同的创建代码生成不同类型和形式的对象。

  • 你可以分步创建对象
  • 生成不同形式的产品时,可以复用相同的制造代码
  • 单一职责原则,你可以将复杂构造代码从产品的业务逻辑中分离出来
  • 由于该模式需要新增多个类,因此代码整体复杂程度会有所增加

示例

下列代码,根据 Builder 生成器接口,实现了 CarBuilder 来造车,CarBuilder 提供了造车的一系列步骤函数,并提供自己的 getCar() 方法来获取结果产品。

可选的主管类 Director 用来指导造车,它提供一个 makeCar(builder: Builder) 方法来按照一定工艺流程来调用一系列造车函数。

在造完车之后,调用汽车生成器的 getCar() 方法获取成品。

// 生成器接口指定了创建产品对象不同部分的一系列方法
interface Builder {
setEngine(engine: string): void;
setGPS(gps: string): void;
setWheels(wheels: string): void;
reset(): void;
}

// 特定生成器类根据生成器接口,实现一系列创建产品的步骤方法。
// 可以根据该接口,实现多个不同的生成器变体,例如 TruckBuilder、BusBuilder
class CarBuilder implements Builder {
private car;

// 一个生成器实例,应该包含一个空的产品对象,未来用于进一步的组合
constructor() {
this.car = new Car();
}

reset(): void {
this.car = new Car();
}
setEngine(engine: string): void {
this.car.addItem(engine);
}
setGPS(gps: string): void {
this.car.addItem(gps);
}
setWheels(wheels: string): void {
this.car.addItem(wheels);
}

/**
* 具体的生成器提供自己的方法来获取结果,因为不同类型的生成器可能创建出完全
* 不同的产品而且不实现同一接口,因此这种方法不能被声明在基础生成器接口中。
* 通常,在把结果返回给客户端之后,生成器实例会准备进行下一个产品的创建。
* 这就是为什么一般要在 getProduct 函数的末尾调用 reset 函数。
* 但这种做法并不是强制的,也可以在合适的时间,在客户端或者主管类代码来一次 reset 调用。
*/
getCar() {
const result = this.car;
this.reset();
return result;
}
}

// 最好只在创建产品很复杂而且需要大量配置的时候用生成器模式。
class Car {
private items: any[] = [];

public addItem(item: any) {
this.items.push(item);
}

public listParts() {
return this.items;
}
}

/**
* 主管只负责按照特定顺序执行生成步骤。其在根据特定步骤或配置来生成产品时
* 会很有帮助。由于客户端可以直接控制生成器,所以严格意义上来说,主管类并
* 不是必需的。
*/
class Director {
makeCar(builder: Builder) {
builder.setEngine('Engine');
builder.setGPS('GPS');
builder.setWheels('Wheels');
}
}

function ClientCode() {
// 主管类指导造车
const director = new Director();
const carBuilder = new CarBuilder();
director.makeCar(carBuilder);
console.log(carBuilder.getCar().listParts()); // ['Engine', 'GPS', 'Wheels']

// 再造一辆,不用主管类
carBuilder.setEngine('SuperEngine');
carBuilder.setWheels('AntiBullet');
console.log(carBuilder.getCar().listParts()); // ['SuperEngine', 'AntiBullet']
}

ClientCode();

原型模式 (Prototype)

· 阅读需 2 分钟

原型模式是一种创建型设计模式,使你能够复制已有对象,而又无需使代码依赖它们所属的类。

它将克隆过程委派给被克隆的实际对象,这样可以解决一些私有变量无法被外部访问就无法在外部复制的问题,让自身提供克隆方法来复制自己,特征是一个类包含 clone 或者 copy 函数,来复制一份自身对象。

示例

下面是一个 Prototype 类,里面有 clone 函数,会复制一份自身对象并返回。一般也可以,定义一个 Cloneable 接口,接口里仅包含一个 clone 函数的定义,让其他类来实现这个接口。

class Prototype {
public primitive: any;
public component: object | null = null;
public circularReference: ComponentWithBackReference | null = null;

/**
* 像实现一个深拷贝,把自身的成员变量都给拷贝一遍,考虑基本类型、引用类型、
* 循环引用的情况、日期类型、正则对象等等
*/
public clone(): this {
const clone = Object.create(this);
// 假设 component 对象只有一层,可以这样拷贝
clone.component = Object.create(this.component);
// 处理循环引用,先用扩展运算符把基本类型的成员复制出来,再单独处理 prototype
clone.circularReference = {
...this.circularReference,
prototype: { ...this },
};
return clone;
}
}

// 循环引用的类,prototype 里有它,它里面有 prototype
class ComponentWithBackReference {
public prototype: Prototype;

constructor(proto: Prototype) {
this.prototype = proto;
}
}

抽象工厂模式 (Abstract Factory)

· 阅读需 4 分钟

抽象工厂模式是一种创建型设计模式,它能创建一系列相关的对象,而无需指定其具体类。

优点:

  1. 你可以确保同一工厂生成的产品相互匹配。
  2. 你可以避免客户端和具体产品代码的耦合。
  3. 单一职责原则。 你可以将产品生成代码抽取到同一位置, 使得代码易于维护。
  4. 开闭原则。 向应用程序中引入新产品变体时, 你无需修改客户端代码。

缺点:

  1. 由于采用该模式需要向应用中引入众多接口和类, 代码可能会比之前更加复杂。

示例

抽象工厂模式的特点是,定义一个抽象工厂类或者接口,由此定义了规范的工厂方法(造桌子、造椅子),将此规范应用于具体的工厂类,例如现代风格工厂和复古风格工厂,生产出不同风格的桌子椅子,他们具有相同的规范,能坐、能放东西。想创建新的风格家具,只需要编写新风格的工厂类并实现抽象工厂接口,不用改动任何已有代码。

抽象工厂,构造方法返回的是抽象的产品。客户端代码,也是与抽象的产品进行交互,无需知道其内部细节,只需要知道椅子可以 sitOn,桌子可以 putSomething

interface AbstractFactory {
createChair(): AbstractChair;
createDesk(): AbstractDesk;
}

interface AbstractChair {
type: string;
sitOn(): void;
}

interface AbstractDesk {
type: string;
putSomething(): void;
mixedFurniture(chair: AbstractChair): void;
}

class MordernChair implements AbstractChair {
type = 'MordernChair';
sitOn() {
console.log('Sitting on Mordern chair.');
}
}

class ClassicChair implements AbstractChair {
type = 'ClassicChair';
sitOn() {
console.log('Sitting on Classic chair.');
}
}

class MordernDesk implements AbstractDesk {
type = 'MordernDesk';
putSomething() {
console.log('Put something on Mordern desk.');
}
mixedFurniture(chair: AbstractChair): void {
console.log(`Combo taste: ${chair.type} ${this.type}`);
}
}
class ClassicDesk implements AbstractDesk {
type = 'ClassicDesk';
putSomething() {
console.log('Put something on Classic desk.');
}
mixedFurniture(chair: AbstractChair): void {
console.log(`Combo taste: ${chair.type} ${this.type}`);
}
}

class MordernStyleFactory implements AbstractFactory {
createChair(): AbstractChair {
return new MordernChair();
}
createDesk(): AbstractDesk {
return new MordernDesk();
}
}

class ClassicStyleFactory implements AbstractFactory {
createChair(): AbstractChair {
return new ClassicChair();
}
createDesk(): AbstractDesk {
return new ClassicDesk();
}
}

// 客户端代码通过抽象类型与工厂和产品进行交互,这使得耦合度很低
function ClientCode(factory: AbstractFactory) {
// 根据传入的工厂,创建对应风格的家具
const chair = factory.createChair();
const desk = factory.createDesk();
chair.sitOn();
// 桌椅混合套餐
desk.mixedFurniture(chair);
}

// 现代风格工厂
ClientCode(new MordernStyleFactory());

工厂方法模式 (Factory Method)

· 阅读需 4 分钟

工厂方法模式是一种创建型设计模式,在父类中提供一个创建对象的方法,允许子类决定实例化对象的类型。

客户端将由不同工厂创建的所有产品都视为抽象的,客户端知道所有产品都提供的交付方法,但是并不关心其具体实现方式。也就是说,产品要有统一的接口,让客户知道怎么用,但不需要知道所调用方法的内部逻辑。如果某一天需要添加新的产品,不需要破坏原有代码,写新的工厂类来创建新产品即可,产品仍然是遵循统一的接口,对客户来说,使用并没有变化。

优点:

  1. 可以避免创建者和具体产品之间的紧密耦合。
  2. 单一职责原则。可以将产品创建代码放在程序的单一位置,从而使得代码更容易维护。
  3. 开闭原则。无需更改现有客户端代码,你就可以在程序中引入新的产品类型。

缺点:

  1. 应用工厂方法模式需要引入许多新的子类,代码可能会因此变得更复杂。

示例

以下代码有两个重点,分别是工厂和产品。

定义了一个 Creator 抽象类,包含了抽象工厂方法,和一些业务代码;具体的 ConcreteCreatorA 和 ConcreteCreatorB 继承该抽象类,实现各自具体的工厂方法用于创建不同产品。

定义了一个 Product 接口,制定了产品的特征;规定产品有一个 operation() 函数;具体的产品 ConcreteProductA 和 ConcreteProductB 实现了该接口,各自的 operation() 内部代码都不相同。

// Product 产品接口,定义了产品具有 operation 特征
// ConcreteProductA ConcreteProductB 实现 Product 接口,是具体的产品实体类。
interface Product {
operation(): string;
}

class ConcreteProductA implements Product {
public operation(): string {
return '[Result of ConcreteProductA]';
}
}

class ConcreteProductB implements Product {
public operation(): string {
return '[Result of ConcreteProductB]';
}
}

/**
* 虽然名字叫创建者,但是它的主要责任并不是创建产品,
* 通常它包含一些依赖于 Product 对象的核心业务逻辑,Product 对象由工厂方法返回。
* 子类可以通过重写工厂方法、返回不同类型的 Product,间接地改变业务逻辑。
*/
abstract class Creator {
public abstract factoryMethod(): Product;

public someOperation() {
const product = this.factoryMethod();
console.log('Do something, ' + product.operation());
}
}

class ConcreteCreatorA extends Creator {
public factoryMethod(): ConcreteProductA {
return new ConcreteProductA();
}
}

class ConcreteCreatorB extends Creator {
public factoryMethod(): ConcreteProductB {
return new ConcreteProductB();
}
}

/**
* 客户端代码,无需了解 Creator 类的内部逻辑,直接无脑调用 someOperation 函数;
* 该函数写在了 Creator 类中,其业务逻辑依赖于工厂方法创建的 Product 对象;
*/
function ClientCode(creator: Creator) {
creator.someOperation();
}

ClientCode(new ConcreteCreatorA());
ClientCode(new ConcreteCreatorB());

输出结果:

Do something, [Result of ConcreteProductA]
Do something, [Result of ConcreteProductB]