From 88dae533e2e42c85fb66f150f248375de4d56d93 Mon Sep 17 00:00:00 2001 From: mrsingsing Date: Tue, 19 Dec 2023 01:22:50 +0800 Subject: [PATCH] docs: update design patterns --- .../architectural/dependency-injection.md | 0 .../architectural/model-view-controller.md | 0 .../architectural/model-view-viewmodel.md | 0 .../architectural/service-locator.md | 0 .../creational/abstract-factory.md | 12 + docs/design-patterns/creational/builder.md | 12 + .../creational/factory-method.md | 14 + docs/design-patterns/creational/prototype.md | 12 + docs/design-patterns/creational/singleton.md | 96 ++++++- .../design-principles-and-ideas.md | 269 ++++++++++++++++-- docs/design-patterns/index.md | 48 ++-- 11 files changed, 398 insertions(+), 65 deletions(-) create mode 100644 docs/design-patterns/architectural/dependency-injection.md create mode 100644 docs/design-patterns/architectural/model-view-controller.md create mode 100644 docs/design-patterns/architectural/model-view-viewmodel.md create mode 100644 docs/design-patterns/architectural/service-locator.md create mode 100644 docs/design-patterns/creational/abstract-factory.md create mode 100644 docs/design-patterns/creational/builder.md create mode 100644 docs/design-patterns/creational/factory-method.md create mode 100644 docs/design-patterns/creational/prototype.md diff --git a/docs/design-patterns/architectural/dependency-injection.md b/docs/design-patterns/architectural/dependency-injection.md new file mode 100644 index 000000000..e69de29bb diff --git a/docs/design-patterns/architectural/model-view-controller.md b/docs/design-patterns/architectural/model-view-controller.md new file mode 100644 index 000000000..e69de29bb diff --git a/docs/design-patterns/architectural/model-view-viewmodel.md b/docs/design-patterns/architectural/model-view-viewmodel.md new file mode 100644 index 000000000..e69de29bb diff --git a/docs/design-patterns/architectural/service-locator.md b/docs/design-patterns/architectural/service-locator.md new file mode 100644 index 000000000..e69de29bb diff --git a/docs/design-patterns/creational/abstract-factory.md b/docs/design-patterns/creational/abstract-factory.md new file mode 100644 index 000000000..252603d65 --- /dev/null +++ b/docs/design-patterns/creational/abstract-factory.md @@ -0,0 +1,12 @@ +--- +nav: + title: 设计模式 + order: 10 +group: + title: 创建型 + order: 2 +title: 抽象工厂模式 +order: 5 +--- + +# 抽象工厂模式 diff --git a/docs/design-patterns/creational/builder.md b/docs/design-patterns/creational/builder.md new file mode 100644 index 000000000..9b477725a --- /dev/null +++ b/docs/design-patterns/creational/builder.md @@ -0,0 +1,12 @@ +--- +nav: + title: 设计模式 + order: 10 +group: + title: 创建型 + order: 2 +title: 创造者模式 +order: 6 +--- + +# 创造者模式 diff --git a/docs/design-patterns/creational/factory-method.md b/docs/design-patterns/creational/factory-method.md new file mode 100644 index 000000000..e79bc09c7 --- /dev/null +++ b/docs/design-patterns/creational/factory-method.md @@ -0,0 +1,14 @@ +--- +nav: + title: 设计模式 + order: 10 +group: + title: 创建型 + order: 2 +title: 工厂方法模式 +order: 4 +--- + +# 工厂方法模式 + +工厂方法模式是一种创建型设计模式, 其在父类中提供一个创建对象的方法, 允许子类决定实例化对象的类型。 diff --git a/docs/design-patterns/creational/prototype.md b/docs/design-patterns/creational/prototype.md new file mode 100644 index 000000000..38db9a826 --- /dev/null +++ b/docs/design-patterns/creational/prototype.md @@ -0,0 +1,12 @@ +--- +nav: + title: 设计模式 + order: 10 +group: + title: 创建型 + order: 2 +title: 原型模式 +order: 3 +--- + +# 原型模式 diff --git a/docs/design-patterns/creational/singleton.md b/docs/design-patterns/creational/singleton.md index 582bda7dc..1a2a527bf 100644 --- a/docs/design-patterns/creational/singleton.md +++ b/docs/design-patterns/creational/singleton.md @@ -6,29 +6,37 @@ group: title: 创建型 order: 2 title: 单例模式 -order: 1 +order: 2 --- # 单例模式 -**单例模式(Singleton)** 指保证一个类仅有一个实例,并提供一个访问它的全局访问点。 +**单例模式(Singleton)** 指保证一个类仅有一个实例,且该类能自行创建这个实例的一种模式。例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误。 -## 功能 +在计算机系统中,还有 Windows 的回收站、操作系统中的文件系统、多线程中的线程池、显卡的驱动程序对象、打印机的后台处理服务、应用程序的日志对象、数据库的连接池、网站的计数器、Web 应用的配置对象、应用程序中的对话框、系统中的缓存等常常被设计成单例。 -- 单例模式能保证全局的唯一性,可以减少命名变量 -- 单例模式在一定情况下可以节约内存,减少过多的类生成需要的内存和运行时间 -- 把代码都放在一个类里面维护,实现了高内聚 +单例模式在现实生活中的应用也非常广泛,例如公司 CEO、部门经理等都属于单例模型。 + +## 特点 + +单例模式有三个特点: + +1. 单例类只有一个实例对象 +2. 该单例对象必须由单例类自行创建 +3. 单例类对外提供一个访问该单例的全局访问点 优点: -1. 提供了对唯一实例的受控访问 -2. 避免对共享资源的多重占用 -3. 节约系统资源 +1. 可避免对共享资源的多重占用 +2. 可保证内存中只有一个实例,减少内存的开销 +3. 可设置全局访问点,优化和共享资源的访问 缺点: -1. 扩展性差 -2. 职责过重 +1. 扩展性差:单例模式一般没有接口,扩展困难。如果要扩展,则修改原来的代码,违背开闭原则 +2. 职责过重:单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则 + +## 模式结构 ## 代码示例 @@ -36,6 +44,66 @@ order: 1 所以要减少全局变量使用,即使用全局变量也要将污染降到最低。 +单例模式通常有两种实现形式:懒汉式单例和饿汉式单例子。 + +### 饿汉式单例 + +该模式的特点是类一旦加载就创建一个单例,保证在调用 `getInstance` 方法之前单例已经存在了。 + +```ts +class HungrySingleton { + public name: string; + public type: number; + + private static instance: HungrySingleton = new HungrySingleton(); + + // 将 constructor 设为私有属性,防止 new 调用 + private constructor() {} + + public static getInstance(): HungrySingleton { + return HungrySingleton.instance; + } +} + +const personA = HungrySingleton.getInstance(); +const personB = HugnrySingleton.getInstance(); + +console.log(personA === personB); +// true +``` + +### 懒汉模式 + +1. 实例被需要时才去创建,如果单例已经创建,将无法创建新的单例,返回原先的单例。 +2. 如果某个单例使用的次数少,并且创建单例消耗的资源较多,那么就需要实现单例的按需创建 + +```ts +class LazySingleton { + public name: string; + public age: number; + private static instance: LazySingleton; + + public construcotr(name: string, age: number) { + this.name = name; + this.age = age; + } + + public static getInstance(name: string, age: number): LazySingleton { + if (LazySingleton.instance === null) { + LazySingleton.instance = new LazySingleton(name, age); + } + + return this.instance; + } +} + +const personA = LazySingleton.getInstance('Dave', 20); +const personB = LazySingleton.getInstance('Mary', 24); + +console.log(personA, personB); +// LazySingleton{ name: "Dave", age: 20 } LazySingleton{ name: "Dave", age: 20 } +``` + ### 命名空间 命名空间可以减少全局变量的数量,可以使用对象字面量将这一类的变量作为它的属性进行访问。 @@ -56,7 +124,7 @@ var Singleton = { 使用 IIFI 立即执行函数表达式,让 JavaScript 编译器不在认为这是一个函数声明,而是 **立即执行函数**,将私有变量保存在它的闭包环境中,暴露可以访问私有变量的接口。类似创建一个块级作用域,和其他作用域的变量隔离。 ```js -var Singleton = (function () { +const Singleton = (function () { let instance; function createInstance() { @@ -78,10 +146,8 @@ function run() { const instance1 = Singleton.getInstance(); const instance2 = Singleton.getInstance(); - alert('Same instance? ' + (instance1 === instance2)); + console.log('Same instance? ' + (instance1 === instance2)); } run(); ``` - -### 惰性单例 diff --git a/docs/design-patterns/design-principles-and-ideas.md b/docs/design-patterns/design-principles-and-ideas.md index 42c48101e..72812cd79 100644 --- a/docs/design-patterns/design-principles-and-ideas.md +++ b/docs/design-patterns/design-principles-and-ideas.md @@ -11,40 +11,269 @@ order: 1 # 设计思想与原则 -## SOLID 原则 +SOLID 是一个面向对象设计和编程中的五个基本原则的缩写,它们旨在帮助开发者设计更加灵活、可维护和可扩展的软件系统。这些原则由 Robert C. Martin 等人提出,它们包括以下五个原则: -SOLID 原则并非单纯的 1 个原则,而是由 5 个设计原则组成的,它们分别是:单一职责原则、开闭原则、里式替换原则、接口隔离原则和依赖反转原则,依次对应 SOLID 中的 S、O、L、I、D 这 5 个英文字母。 +1. 单一职责原则(Single Responsibility Principle,SRP) +2. 开放/封闭原则(Open/Closed Principle,OCP) +3. 里氏替换原则(Liskov Substitution Principle,LSP) +4. 接口隔离原则(Interface Segregation Principle,ISP) +5. 依赖反转原则(Dependency Inversion Principle,DIP) -### SRP 单一职责原则 +这些原则共同促使开发者创建具有高内聚、低耦合、易扩展和易维护性的软件系统。遵循这些原则有助于构建更健壮的面向对象系统,提高代码质量和可维护性。 -单一职责原则(Single Responsibility Principle)指一个类或者模块只负责完成一个职责(或者功能)。 +### 单一职责原则 -每一个类,应该要有明确的定义,不要设计大而全的类,要设计粒度小、功能单一的类。 +单一职责原则(Single Responsibility Principle,SRP)要求一个类或者模块只负责完成一个职责(或者功能)。 +假设我们有一个简单的厨师类,它负责烹饪和洗碗两个职责: -作用:避免将不相关的代码耦合在一起,提高了类或者模块的内聚性。 +```typescript +class Chef { + cookDish(dish) { + // 烹饪菜品的具体实现 + } -### OCP 开闭原则 + washDishes() { + // 洗碗的具体实现 + } +} +``` -开闭原则(Open Closed Principle)指软件实体(模块、类、方法等)应该 **对扩展开放、对修改关闭**。 +这个类违反了单一职责原则,因为它有两个职责:烹饪和洗碗。这样的设计可能导致以下问题: -添加一个新的功能应该是,在已有代码基础上扩展代码(新增模块、类、方法等),而非修改已有代码(修改模块、类、方法等)。 +1. 如果厨师的烹饪逻辑变化,需要修改 `cookDish` 方法,这可能会影响洗碗的部分。 +2. 如果洗碗的逻辑变化,需要修改 `washDishes` 方法,这可能会影响烹饪的部分。 -作用:增加了类的可扩展性。 +按照单一职责原则,我们应该将这两个职责分开,分别由不同的类负责: -### LSP 里式替换原则 +```typescript +class Chef { + cookDish(dish) { + // 烹饪菜品的具体实现 + } +} -里式替换原则(Liskov Substitution Principle)指子类对象能够替换程序中父类对象出现的任何地方,并且保证原来程序的逻辑行为不变及正确性不被破坏。 +class Dishwasher { + washDishes() { + // 洗碗的具体实现 + } +} +``` -### LSP 接口隔离原则 +这样,`Chef` 类专注于烹饪,而 `Dishwasher` 类专注于洗碗。每个类都有一个单一的职责,使得代码更清晰、易于理解,并且在未来的变更中更具弹性。 -接口隔离原则(Interface Segregation Principle)指接口的调用者不应该被强迫依赖它不需要的接口。 +### 开放封闭原则 -这一原则和单一职责原则有点类似,只不过它更侧重于接口。 +开关封闭原则(Open/Closed Principle,OCP)要求软件实体(例如类、模块、函数等)应该对扩展开放,对修改关闭。简而言之,一个模块在扩展新功能时不应该修改原有的代码,而是通过添加新的代码来实现扩展。 -- 如果把 **接口** 理解为一组接口集合,可以是某个类库的接口等。如果部分接口只被部分调用者使用,我们就需要将这部分接口隔离出来,单独给这部分调用者使用,而不强迫其他调用者也依赖这部分不会被用到的接口。 -- 如果把 **接口** 理解为单个 API 接口或函数,部分调用者只需要函数中的部分功能,那我们就需要把函数拆分成粒度更细的多个函数,让调用者只依赖它需要的那个细粒度函数。 -- 如果把 **接口** 理解为 OOP 中的接口,也可以理解为面向对象编程语言中的接口语法。那接口的设计要尽量单一,不要让接口的实现类和调用者,依赖不需要的接口函数。 +考虑一个动物园的场景。我们有一些动物,每个动物都会发出叫声。初始设计如下: -### DIP 依赖反转原则 +```typescript +class Animal { + constructor(name) { + this.name = name; + } -依赖反转原则(Dependency Inversion Principle)指高层模块不要依赖低层模块。高层模块和低层模块应该通过抽象来互相依赖。除此之外,抽象不要依赖具体实现细节,具体实现细节依赖抽象。大白话就是面向接口编程,依赖于抽象而不依赖于具体。 + makeSound() { + // 默认的叫声 + console.log("Some generic animal sound"); + } +} + +class Lion extends Animal { + makeSound() { + console.log("Roar!"); + } +} + +class Elephant extends Animal { + makeSound() { + console.log("Trumpet!"); + } +} + +``` + +在这个设计中,`Animal` 类是一个基类,而 `Lion` 和 `Elephant` 是它的子类。每个动物都有自己的叫声,通过重写 `makeSound` 方法来实现。 + +现在,假设我们要添加一些新的动物,比如鹦鹉和狗,按照开放/封闭原则,我们应该扩展而不是修改现有的代码: + +```typescript +class Parrot extends Animal { + makeSound() { + console.log("Squawk!"); + } +} + +class Dog extends Animal { + makeSound() { + console.log("Bark!"); + } +} + +``` + +这样,我们通过扩展 Animal 类,而不是修改它,来添加新的功能(新的动物)。这符合开放/封闭原则,因为我们对于现有代码的修改是关闭的,我们只是通过扩展来引入新的功能。 + +使用开放/封闭原则可以使代码更加稳定,降低对现有代码的影响,同时也更容易应对变化和扩展。 + + +### 里式替换原则 + +里氏替换原则(Liskov Substitution Principle,LSP) 是 SOLID 原则之一,它强调子类型(派生类或子类)必须能够替换掉它们的基类型(基类或父类)并出现在基类能够工作的任何地方,而不破坏程序的正确性。 + +通俗地说,如果一个类是基类的子类,那么在任何需要基类的地方,都可以使用这个子类而不产生错误。子类应该保持基类的行为,并且可以扩展或修改基类的行为,但不应该破坏基类原有的约定。 + +假设我们有一个表示矩形的基类 `Rectangle`: + +```typescript +class Rectangle { + constructor(width, height) { + this.width = width; + this.height = height; + } + + setWidth(width) { + this.width = width; + } + + setHeight(height) { + this.height = height; + } + + getArea() { + return this.width * this.height; + } +} +``` + +现在,我们创建了一个子类 `Square` 继承自 `Rectangle`,表示正方形。在正方形中,宽和高应该始终相等。 + +```typescript +class Square extends Rectangle { + setWidth(width) { + super.setWidth(width); + super.setHeight(width); + } + + setHeight(height) { + super.setWidth(height); + super.setHeight(height); + } +} +``` + +这里的问题是,`Square` 子类在修改宽度或高度时,通过覆写 `setWidth` 和 `setHeight` 方法,强制宽和高相等,这与基类的行为不一致。如果在需要 `Rectangle` 的地方使用了 `Square`,可能会导致程序逻辑错误。 + +这违反了里氏替换原则,因为子类修改了父类的预期行为。为了符合里氏替换原则,可能需要重新设计类的继承结构,或者使用更精确的命名来表达实际意图。 + +### 接口隔离原则 + +接口隔离原则(Interface Segregation Principle,ISP) 是 SOLID 原则之一,它强调一个类不应该被强迫实现它不需要的接口。简而言之,一个类对另一个类的依赖应该建立在最小的接口上。 + +在通俗的语言中,接口隔离原则告诉我们不应该让一个类依赖它不需要的接口,否则会导致类需要实现一些它根本不需要的方法。 + +举例说明,假设我们有一个动物园的系统,其中有两种动物,一种会飞,一种会游泳: + +```typescript +// 不遵循接口隔离原则的设计 +class Bird { + fly() { + // 实现飞行逻辑 + } + + swim() { + // 这是一个鸟类不需要的方法,违反接口隔离原则 + } +} + +class Fish { + swim() { + // 实现游泳逻辑 + } + + fly() { + // 这是一个鱼类不需要的方法,违反接口隔离原则 + } +} + +``` + +在这个例子中,`Bird` 类实现了 `fly` 和 `swim` 两个方法,而 `Fish` 类也实现了 `swim` 和 `fly` 两个方法。这违反了接口隔离原则,因为每个类都被迫实现了它们不需要的方法。 + +为了符合接口隔离原则,我们可以将接口拆分成更小的部分,让每个类只实现它们需要的方法: + +```typescript +// 遵循接口隔离原则的设计 +class Bird { + fly() { + // 实现飞行逻辑 + } +} + +class Fish { + swim() { + // 实现游泳逻辑 + } +} +``` + +这样,每个类都只依赖于它们需要的接口,不再强迫实现不必要的方法。接口隔离原则的目标是使接口更具体,更贴近类的实际需求,从而提高系统的灵活性和可维护性。 + +### 依赖反转原则 + +依赖反转原则(Dependency Inversion Principle,DIP) 是 SOLID 原则之一,它强调高层模块不应该依赖于低层模块,而两者都应该依赖于抽象。抽象不应该依赖于细节,细节应该依赖于抽象。简而言之,依赖反转原则倡导通过抽象来解耦高层和低层模块之间的依赖关系。 + +举例说明,考虑一个简单的报告生成系统,有一个高层模块 `ReportGenerator` 负责生成报告: + +```typescript +// 不遵循依赖反转原则的设计 +class ReportGenerator { + constructor() { + this.pdfGenerator = new PDFGenerator(); // 依赖于具体的 PDF 生成器 + } + + generateReport() { + // 生成报告的逻辑 + this.pdfGenerator.generatePDF(); + } +} + +class PDFGenerator { + generatePDF() { + // 具体的 PDF 生成逻辑 + } +} +``` + +在这个设计中,`ReportGenerator` 直接依赖于具体的 `PDFGenerator` 类,这违反了依赖反转原则。如果我们想使用其他类型的报告生成器,例如 `HTMLGenerator`,就需要修改 `ReportGenerator` 类。 + +为了符合依赖反转原则,我们应该通过抽象来解耦高层和低层模块: + +```typescript +// 遵循依赖反转原则的设计 +class ReportGenerator { + constructor(generator) { + this.generator = generator; // 依赖于抽象的报告生成器接口 + } + + generateReport() { + // 生成报告的逻辑 + this.generator.generate(); + } +} + +class PDFGenerator { + generate() { + // 具体的 PDF 生成逻辑 + } +} + +class HTMLGenerator { + generate() { + // 具体的 HTML 生成逻辑 + } +} + +``` + +现在,`ReportGenerator` 不再直接依赖于具体的实现,而是依赖于抽象的报告生成器接口。这使得我们可以轻松地扩展系统,例如添加新的报告生成器,而不需要修改 `ReportGenerator` 类。这样的设计更加灵活,符合依赖反转原则。 \ No newline at end of file diff --git a/docs/design-patterns/index.md b/docs/design-patterns/index.md index 95ad3aacf..8cec2e66e 100644 --- a/docs/design-patterns/index.md +++ b/docs/design-patterns/index.md @@ -15,45 +15,33 @@ order: 1 - 设计模式 - **创建型** - - 简单工厂模式 - - 工厂方法模式 - - 抽象工厂模式 - - 创造者模式 - - 原型模式 + - [工厂方法模式](/design-patterns/creational/factory-method) + - [抽象工厂模式](/design-patterns/creational/abstract-factory) + - [创造者模式](/design-patterns/creational/builder) + - [原型模式](/design-patterns/creational/prototype) - [单例模式](/design-patterns/creational/singleton) - **结构型** - - [外观模式](/design-patterns/structual/facade) - [适配器模式](/design-patterns/structual/adapter) - - [代理模式](/design-patterns/structual/proxy) - - [装饰者模式](/design-patterns/structual/decorator) - [桥接模式](/design-patterns/structual/bridge) - [组合模式](/design-patterns/structual/composite) + - [装饰者模式](/design-patterns/structual/decorator) + - [外观模式](/design-patterns/structual/facade) - [享元模式](/design-patterns/structual/flyweight) + - [代理模式](/design-patterns/structual/proxy) - **行为型** - - [模板方法模式](/design-patterns/behavioral/template-method) - - [观察者模式](/design-patterns/behavioral/observer) - - [状态模式](/design-patterns/behavioral/state) - - [策略模式](/design-patterns/behavioral/strategy) - [职责链模式](/design-patterns/behavioral/chain-of-responsibility) - [命令模式](/design-patterns/behavioral/command) - - [访问者模式](/design-patterns/behavioral/visitor) + - [解释器模式](/design-patterns/behavioral/interpreter) + - [迭代器模式](/design-patterns/behavioral/iterator) - [中介者模式](/design-patterns/behavioral/mediator) - [备忘录模式](/design-patterns/behavioral/memento) - - [迭代器模式](/design-patterns/behavioral/iterator) - - [解释器模式](/design-patterns/behavioral/interpreter) -- **技巧型** - - 链模式 - - 委托模式 - - 数据访问对象模式 - - 节流模式 - - 简单模板模式 - - 惰性模式 - - 参与者模式 - - 等待者模式 + - [观察者模式](/design-patterns/behavioral/observer) + - [状态模式](/design-patterns/behavioral/state) + - [策略模式](/design-patterns/behavioral/strategy) + - [模板方法模式](/design-patterns/behavioral/template-method) + - [访问者模式](/design-patterns/behavioral/visitor) - **架构型** - - 同步模块模式 - - 异步模块模式 - - Widgt 模式 - - MVC 模式 - - MVP 模式 - - MVVM 模式 + - [MVC 模式](/design-patterns/architectural/model-view-controller) + - [MVVM 模式](/design-patterns/architectural/model-view-viewmodel) + - [服务定位器模式](/design-patterns/architectural/service-locator) + - [依赖注入模式](/design-patterns/architectural/dependency-injection) \ No newline at end of file