[Pattern] 设计模式
设计模式
- 设计模式基本介绍
- 单例模式
- 工厂模式
基本介绍
“模式”是由模式之父 Christopher Alexander(克里斯托弗·亚历山大)提出。

1991 年-1992 年之间,由 GoF:
- Erich Gamma
- Richard Helm
- Ralph Johnson
- John Vlissides
他们四个出版了一本书《设计模式:可复用面向对象软件的基础》,在书里面一共提出了 23 种设计模式:
- 创建型模式:提供一种创建对象的机制
- 结构型模式:对象和对象之间,以及对象和类之间如何进行组装
- 行为型模式:对象之间如何进行沟通和职责分配。
23 种设计模式如下表:

民间有一种偏见:像 JS 这种非标准的面向对象语言,使用设计模式有一点画蛇添足,这其实是不太准确的,无论什么范式的语言,要解决的问题都是相似,只是不同的语言,因为特性不一样,会使用到的设计模式有所不同。
单例模式
定义:保证一个类,仅有一个实例。
- 后端:线程池、缓冲池、全局缓存
- 前端:登录窗口,无论用户点击多少次,只有有一个窗口、浏览器的 window 对象
使用 JS 实现单例模式
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
sayHello() {
console.log("Hello");
}
}
// 书写一个辅助函数
function getSingle(fn) {
let instance = null; // 用于存储第一个实例对象
// 这里使用到了闭包来保存
return function (...args) {
if (instance !== null) {
// 如果实例对象已经存在,则直接返回
return instance;
}
// 代码走到这里,说明是第一次实例化
instance = new fn(...args);
return instance;
};
}
const SinglePerson = getSingle(Person);
const p1 = new SinglePerson("张三", 18);
const p2 = new SinglePerson("李四", 20);
console.log(p1 === p2); // true
使用 TS 来实现单例模式:
class Person {
private static instance: Person;
private name: string;
private age: number;
// 要让该类是一个单例类,一定要将构造函数私有化
private constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
public static getInstance(name: string, age: number): Person {
if (!Person.instance) {
Person.instance = new Person(name, age);
}
return Person.instance;
}
}
const p1 = Person.getInstance("zhangsan", 18);
const p2 = Person.getInstance("lisi", 20);
console.log(p1 === p2); // true
利用Typescript实现的时候,注意constructor必须是私有的,这样才能防止调用
工厂模式
工厂模式属于创建型模式:
在创建对象的时候,不会对客户端暴露创建的逻辑,客户端通过工厂所提供的统一接口来创建对象。
工厂模式里面有两个概念:
- 产品:具体创建出来的对象。
- 工厂:负责生产产品的。(创建对象的)
工厂模式如果要细分:
- 简单工厂
- 标准的工厂模式
- 抽象工厂
简单工厂
在简单工厂模式中,存在一个单独的工厂类,统一通过工厂类来创建对象。客户端通过传递不同的参数来指定对象的类型。
首先是不使用工厂模式:
// 不用工厂模式
interface IProduct {
use(): void;
}
// 产品A
class ProductA implements IProduct {
use() {
console.log("ProductA");
}
}
// 产品B
export class ProductB implements IProduct {
use() {
console.log("ProductB");
}
}
// 对应的客户端代码,实际的去和具体的产品打交道
// 创建具体的产品
const p1 = new ProductA();
const p2 = new ProductB();
p1.use();
p2.use();
在上面的代码模式中,具体到客户端,创建产品的逻辑和使用产品的逻辑是耦合到一起的,并且客户端能够清楚的知道创建产品的逻辑。
简单工厂模式如下:
interface IProduct {
use(): void;
}
// 产品A
class ProductA implements IProduct {
use() {
console.log("ProductA");
}
}
// 产品B
export class ProductB implements IProduct {
use() {
console.log("ProductB");
}
}
export class SimpleFactory {
static createProduct(type: string): IProduct {
switch (type) {
case "A": {
return new ProductA();
break;
}
case "B": {
return new ProductB();
break;
}
default: {
throw new Error("type error");
}
}
}
}
// 对应的客户端代码,实际的去和具体的产品打交道
// 创建具体的产品
const p1 = SimpleFactory.createProduct("A");
const p2 = SimpleFactory.createProduct("B");
p1.use();
p2.use();
这样的好处在于,创建产品的逻辑和使用产品的逻辑分离开了。
- 创建产品:由工厂来创建,具体产品是如何创建出来的,对客户端也讲也是一个黑盒。
- 客户端通过工厂提供的方法来得到产品,然后使用产品
简单工厂模式会存在的问题 ?
目前我们将创建产品的逻辑全部放入到了一个工厂,这里会导致如果存在要新增产品,那么需要动工厂相关的代码。
工厂方法模式
工厂方法模式和前面的简单工厂模式的区别在于:
一个产品就对应一个工厂。假设有产品 A、B、C,那就就会存在对应的工厂 A、B、C,现在假设要新增一个产品 D,那么前面的工厂是不会受到任何影响的,只需要新增工厂 D 即可。
来看一下工厂方法的类图:
- 产品接口:规范各种产品的。
- 具体的产品:无论产品是什么,反正需要实现 Product 接口。
- 工厂的接口:因为现在工厂有多个,所以需要对这些工厂进行统一的规范。
- 具体的工厂:需要实现工厂接口。
假设有一个日志系统,需要根据不同的环境来记录日志,如果使用前面介绍的简单工厂模式:
// 产品的接口
interface Logger {
log(message: string): void;
}
// 具体的产品,实现了 Logger 接口
// 以文件的信息记录日志
class FileLogger implements Logger {
log(message: string) {
console.log(`File log: ${message}`);
}
}
// 也是一个具体的产品,实现了 Logger 接口
// 以数据库的形式来记录日志
class DatabaseLogger implements Logger {
log(message: string) {
console.log(`Database log: ${message}`);
}
}
// 简单工厂
class LoggerFactory {
static createLogger(type: string): Logger {
switch (type) {
case "file":
return new FileLogger();
case "database":
return new DatabaseLogger();
default:
throw new Error("Unknown logger type");
}
}
}
const logger = LoggerFactory.createLogger("file");
logger.log("This is a message.");
types.ts
export interface ILogger {
log(message: string): void;
}
export interface ILoggerFactory {
createLogger(): ILogger;
}
FileLogger.ts
import { ILogger } from "./ILogger";
export class FileLogger implements ILogger {
log(message: string) {
console.log("log to file: ", message);
}
}
DatabaseLogger.ts
import { ILogger } from "./ILogger";
export class DatabaseLogger implements ILogger {
log(message: string) {
console.log("DatabaseLogger to file: ", message);
}
}
FileFactory.ts
import { ILogger, ILoggerFactory } from "./ILogger";
import { FileLogger } from "./FileLogger";
// FileFactory 这种工厂专门负责生产 FileLogger
export class FileFactory implements ILoggerFactory {
createLogger(): ILogger {
return new FileLogger();
}
}
DatabaseFactory.ts
import { ILogger, ILoggerFactory } from "./ILogger";
import { DatabaseLogger } from "./DatabaseLogger";
// DatabaseFactory 这种工厂专门负责生产 DatabaseLogger
export class DatabaseFactory implements ILoggerFactory {
createLogger(): ILogger {
return new DatabaseLogger();
}
}
Client.ts
import { FileFactory } from './FileFactory';
const fileFactory = new FileFactory();
const fileLogger = fileFactory.createLogger();
const dbFactory = new DatabaseFactory();
const dbLogger = dbFactory.createLogger();
观察者模式
观察者模式的定义:
允许你定义一种订阅机制,之后在事件发生的时候,通知多个观察该对象的观察者,多个观察者自发的进行一些行为。
例如生活中的一个例子:
假如你有两种类型的对象: 顾客和商店 。顾客对某个特定品牌的产品非常感兴趣(例如最新型号的手机),而该产品很快将会在商店里出售。
顾客可以每天来商店看看产品是否到货。但如果商品尚未到货时,绝大多数来到商店的顾客都会空手而归。另一方面,每次新产品到货时,商店可以向所有顾客发送邮件(可能会被视为垃圾邮件)。这样,部分顾客就无需反复前往商店了,但也可能会惹恼对新产品没有兴趣的其他顾客。

这种问题在生活中也非常常见。在生活中一般采取的做法是由用户主动留下联系方式,当用户留下联系方式之后,该用户就变成了一个观察者,当商店开始出售新的产品的时候,就通知所有的观察者,所有的观察者开始采取之前定义好的行为。
观察者模式的类图结构:

- 发布者:像所有的观察者发布信息,一般发布者内部会有一个类似于 subscribers 的数组,该数组用于存储所有的观察者。
- 当新的事件发生的时候,发布者会去遍历 subscribers 数组,通知所有的观察者做相应的事情
- 观察者基本的一个接口,定义观察者统一的行为
- 具体的观察者
- 所有的观察者根据 Subscriber 接口的定义,都会有一个 update 方法
- 客户端会分别创建发布者和订阅者,执行相应的逻辑
types.ts
// 观察者的统一接口
export interface IObserver {
update(message: string): void;
}
Publisher.ts
import { IObserver } from "./IObserver";
// 发布者
export class Publisher {
// 该数组用于存储观察者
private observers: IObserver[] = [];
// 添加观察者
public addObserver(observer: IObserver): void {
this.observers.push(observer);
}
// 删除观察者
public removeObserver(observer: IObserver): void {
const index = this.observers.indexOf(observer);
if (index !== -1) {
this.observers.splice(index, 1);
}
}
// 通知所有的观察者
public notifyObservers(message: string): void {
this.observers.forEach((observer) => {
observer.update(message);
});
}
}
log.ts
import { IObserver } from "./IObserver";
export class LogNotificationListener implements IObserver {
public update(message: string): void {
console.log(`Logging notification: ${message}`);
}
}
email.ts
import { IObserver } from "./IObserver";
export class EmailNotificationListener implements IObserver {
public update(message: string): void {
console.log(`Sending email notification: ${message}`);
}
}
client.ts
import { Publisher } from "./Publisher";
import { EmailNotificationListener } from "./Email";
import { LogNotificationListener } from "./Log";
// 创建发布中心
const newPublisher = new Publisher();
// 创建两个观察者
const logObserver = new LogNotificationListener();
const emailObserver = new EmailNotificationListener();
// 接下来,我们将观察者添加到发布中心,相当于留下你的联系方式给商家
newPublisher.addObserver(logObserver);
newPublisher.addObserver(emailObserver);
// 最后,我们发布一个消息,相当于商家发布了一条消息
newPublisher.notifyObservers("Hello World!");
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
2024-02-23 [Rust] Instantiating Classic, Tuple, and Unit structs
2024-02-23 [Rust] Accessing specific values in a tuple by index
2024-02-23 [Rust] Create an array of numbers by using Range
2024-02-23 [Rust Unit testing] test should_panic
2024-02-23 [Rust Unit testing] assert & assert_eq
2024-02-23 [Rust] Implicitly returning values from functions
2024-02-23 [Rust] Constants