本文最后更新于 2025-03-07,文章超过7天没更新,应该是已完结了~

一、建造者模式

建造者模式(Builder Pattern)将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成该复杂对象。

一辆小汽车 🚗 通常由 发动机、底盘、车身和电气设备 四大部分组成。汽车电气设备的内部构造很复杂,简单起见,我们只考虑三个部分:引擎、底盘和车身。

在现实生活中,小汽车也是由不同的零部件组装而成,比如上图中我们把小汽车分成引擎、底盘和车身三大部分。下面我们来看一下如何使用建造者模式来造车子。

1.1 实现代码
class Car {
    constructor(
        public engine: string,
        public chassis: string, 
        public body: string
    ) {}
}

class CarBuilder {
    engine!: string; // 引擎
    chassis!: string; // 底盘
    body!: string; // 车身

    addChassis(chassis: string) {
        this.chassis = chassis;
        return this;
    }

    addEngine(engine: string) {
        this.engine = engine;
        return this;
    }

    addBody(body: string) {
        this.body = body;
        return this;
    }

    build() {
        return new Car(this.engine, this.chassis, this.body);
    }
}

在以上代码中,我们定义一个 CarBuilder 类,并提供了 addChassisaddEngineaddBody 3 个方法用于组装车子的不同部位,当车子的 3 个部分都组装完成后,调用 build 方法就可以开始造车。

1.2 使用示例
const car = new CarBuilder()
.addEngine('v12')
.addBody('镁合金')
.addChassis('复合材料')
.build();
1.3 应用场景及案例
  • 需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员属性。

  • 需要生成的产品对象的属性相互依赖,需要指定其生成顺序。

  • 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。

二、工厂模式

在现实生活中,工厂是负责生产产品的,比如牛奶、面包或礼物等,这些产品满足了我们日常的生理需求。

在众多设计模式当中,有一种被称为工厂模式的设计模式,它提供了创建对象的最佳方式。工厂模式可以分为:简单工厂模式、工厂方法模式和抽象工厂模式

2.1 简单工厂

简单工厂模式又叫 静态方法模式,因为工厂类中定义了一个静态方法用于创建对象。简单工厂让使用者不用知道具体的参数就可以创建出所需的 ”产品“ 类,即使用者可以直接消费产品而不需要知道产品的具体生产细节。

在上图中,模拟了用户购车的流程,小王和小秦分别向 BMW 工厂订购了 BMW730 和 BMW840 型号的车型,接着工厂会先判断用户选择的车型,然后按照对应的模型进行生产并在生产完成后交付给用户。

下面我们来看一下如何使用简单工厂来描述 BMW 工厂生产指定型号车子的过程。

2.1.1 实现代码
abstract class BMW {
    abstract run(): void;
}

class BMW730 extends BMW {
    run(): void {
        console.log("BMW730 发动咯");
    }
}

class BMW840 extends BMW {
    run(): void {
        console.log("BMW840 发动咯");
    }
}

class BMWFactory {
    public static produceBMW(model: "730" | "840"): BMW {
    if (model === "730") {
        return new BMW730();
    } else {
        return new BMW840();
    }
}
}

在以上代码中,我们定义一个 BMWFactory 类,该类提供了一个静态的 produceBMW() 方法,用于根据不同的模型参数来创建不同型号的车子。

2.1.2 使用示例
const bmw730 = BMWFactory.produceBMW("730");
const bmw840 = BMWFactory.produceBMW("840");

bmw730.run();
bmw840.run();
2.1.3 应用场景
  • 工厂类负责创建的对象比较少:由于创建的对象比较少,不会造成工厂方法中业务逻辑过于复杂。

  • 客户端只需知道传入工厂类静态方法的参数,而不需要关心创建对象的细节。

2.2 工厂方法

工厂方法模式(Factory Method Pattern)又称为工厂模式,也叫多态工厂(Polymorphic Factory)模式,它属于类创建型模式。

在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象, 这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。

在上图中,模拟了用户购车的流程,小王和小秦分别向 BMW 730 和 BMW 840 工厂订购了 BMW730 和 BMW840 型号的车子,接着工厂按照对应的模型进行生产并在生产完成后交付给用户。

同样,我们来看一下如何使用工厂方法来描述 BMW 工厂生产指定型号车子的过程。

2.2.1 实现代码
abstract class BMWFactory {
    abstract produceBMW(): BMW;
}

class BMW730Factory extends BMWFactory {
    produceBMW(): BMW {
        return new BMW730();
    }
}

class BMW840Factory extends BMWFactory {
    produceBMW(): BMW {
        return new BMW840();
    }
}

在以上代码中,我们分别创建了 BMW730FactoryBMW840Factory 两个工厂类,然后使用这两个类的实例来生产不同型号的车子。

2.2.2 使用示例
const bmw730Factory = new BMW730Factory();
const bmw840Factory = new BMW840Factory();

const bmw730 = bmw730Factory.produceBMW();
const bmw840 = bmw840Factory.produceBMW();

bmw730.run();
bmw840.run();
2.2.3 应用场景
  • 一个类不知道它所需要的对象的类:在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建;客户端需要知道创建具体产品的工厂类。

  • 一个类通过其子类来指定创建哪个对象:在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。

2.3 抽象工厂

抽象工厂模式(Abstract Factory Pattern),提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。

在工厂方法模式中具体工厂负责生产具体的产品,每一个具体工厂对应一种具体产品,工厂方法也具有唯一性,一般情况下,一个具体工厂中只有一个工厂方法或者一组重载的工厂方法。 但是有时候我们需要一个工厂可以提供多个产品对象,而不是单一的产品对象。

在上图中,模拟了用户购车的流程,小王向 BMW 工厂订购了 BMW730,工厂按照 730 对应的模型进行生产并在生产完成后交付给小王。而小秦向同一个 BMW 工厂订购了 BMW840,工厂按照 840 对应的模型进行生产并在生产完成后交付给小秦。

下面我们来看一下如何使用抽象工厂来描述上述的购车过程。

2.3.1 实现代码
abstract class BMWFactory {
    abstract produce730BMW(): BMW730;
    abstract produce840BMW(): BMW840;
}

class ConcreteBMWFactory extends BMWFactory {
    produce730BMW(): BMW730 {
        return new BMW730();
    }

    produce840BMW(): BMW840 {
        return new BMW840();
    }
}
2.3.2 使用示例
const bmwFactory = new ConcreteBMWFactory();

const bmw730 = bmwFactory.produce730BMW();
const bmw840 = bmwFactory.produce840BMW();

bmw730.run();
bmw840.run();
2.3.3 应用场景
  • 一个系统不应当依赖于产品类实例如何被创建、组合和表达的细节,这对于所有类型的工厂模式都是重要的。

  • 系统中有多于一个的产品族,而每次只使用其中某一产品族。

  • 系统提供一个产品类的库,所有的产品以同样的接口出现,从而使客户端不依赖于具体实现。

三、单例模式

单例模式(Singleton Pattern)是一种常用的模式,有一些对象我们往往只需要一个,比如全局缓存、浏览器中的 window 对象等。单例模式用于保证一个类仅有一个实例,并提供一个访问它的全局访问点。

在上图中,模拟了借车的流程,小王临时有急事找曼波哥借车子,曼波哥家的车子刚好没用,就借给小王了。当天,小秦也需要用车子,也找曼波哥借车,因为曼波哥家里只有一辆车子,所以就没有车可借了。

对于车子来说,它虽然给生活带来了很大的便利,但养车也需要一笔不小的费用(车位费、油费和保养费等),所以曼波哥家里只有一辆车子。在开发软件系统时,如果遇到创建对象时耗时过多或耗资源过多,但又经常用到的对象,我们就可以考虑使用单例模式。

下面我们来看一下如何使用 TypeScript 来实现单例模式。

3.1 实现代码
class Singleton {
    // 定义私有的静态属性,来保存对象实例
    private static singleton: Singleton;
    private constructor() {}

    // 提供一个静态的方法来获取对象实例
    public static getInstance(): Singleton {
        if (!Singleton.singleton) {
            Singleton.singleton = new Singleton();
        }
        return Singleton.singleton;
    }
}
3.2 使用示例
let instance1 = Singleton.getInstance();
let instance2 = Singleton.getInstance();

console.log(instance1 === instance2); // true
3.3 应用场景
  • 需要频繁实例化然后销毁的对象。

  • 创建对象时耗时过多或耗资源过多,但又经常用到的对象。

  • 系统只需要一个实例对象,如系统要求提供一个唯一的序列号生成器或资源管理器,或者需要考虑资源消耗太大而只允许创建一个对象。

四、适配器模式

在实际生活中,也存在适配器的使用场景,比如:港式插头转换器、电源适配器和 USB 转接口。而在软件工程中,适配器模式的作用是解决两个软件实体间的接口不兼容的问题。 使用适配器模式之后,原本由于接口不兼容而不能工作的两个软件实体就可以一起工作。

4.1 实现代码
interface Logger {
    info(message: string): Promise<void>;
}

interface CloudLogger {
    sendToServer(message: string, type: string): Promise<void>;
}

class AliLogger implements CloudLogger {
    public async sendToServer(message: string, type: string): Promise<void> {
        console.info(message);
        console.info('This Message was saved with AliLogger');
    }
}

class CloudLoggerAdapter implements Logger {
    protected cloudLogger: CloudLogger;

    constructor (cloudLogger: CloudLogger) {
        this.cloudLogger = cloudLogger;
    }

    public async info(message: string): Promise<void> {
        await this.cloudLogger.sendToServer(message, 'info');
    }
}

class NotificationService {
    protected logger: Logger;

    constructor (logger: Logger) {    
        this.logger = logger;
    }

    public async send(message: string): Promise<void> {
        await this.logger.info(`Notification sended: ${message}`);
}
}

在以上代码中,因为 LoggerCloudLogger 这两个接口不匹配,所以我们引入了 CloudLoggerAdapter 适配器来解决兼容性问题。

4.2 使用示例
(async () => {
 const aliLogger = new AliLogger();
const cloudLoggerAdapter = new CloudLoggerAdapter(aliLogger);
const notificationService = new NotificationService(cloudLoggerAdapter);
await notificationService.send('Hello semlinker, To Cloud');
})();
4.3 应用场景及案例
  • 以前开发的系统存在满足新系统功能需求的类,但其接口同新系统的接口不一致。

  • 使用第三方提供的组件,但组件接口定义和自己要求的接口定义不同。