紅寶書第十二講:詳解JavaScript中的工廠模式與原型模式等各種設(shè)計(jì)模式

紅寶書第十二講:詳解JavaScript中的工廠模式與原型模式等各種設(shè)計(jì)模式

資料取自《JavaScript高級(jí)程序設(shè)計(jì)(第5版)》
查看總目錄:紅寶書學(xué)習(xí)大綱


工廠模式和原型模式解析

一、工廠模式:像訂外賣一樣創(chuàng)建對(duì)象

工廠模式就像一個(gè)“對(duì)象生成器”,只需要告訴它“我要什么”,它就會(huì)自動(dòng)生成并返回對(duì)應(yīng)的對(duì)象。適合需要批量創(chuàng)建復(fù)雜對(duì)象的場(chǎng)景 [1]

案例:點(diǎn)餐系統(tǒng)

假設(shè)你經(jīng)營(yíng)奶茶店,需要根據(jù)訂單類型生成不同配置的飲品:

// 工廠函數(shù)決定飲品類型和配料
function createDrink(type, name) {
  const drink = { name };
  if (type === '奶茶') {
    drink.base = '紅茶';
    drink.toppings = ['珍珠', '奶蓋'];
  } else if (type === '果茶') {
    drink.base = '四季春茶';
    drink.toppings = ['椰果', '寒天'];
  }
  return drink;
}

// 下單操作
const order1 = createDrink('奶茶', '招牌奶茶'); 
const order2 = createDrink('果茶', '滿杯鮮橙');

[1]: 參考資料6明確將工廠模式列為JavaScript對(duì)象創(chuàng)建的核心設(shè)計(jì)模式


二、原型模式:共享技能的忍者軍團(tuán)

原型模式的精髓是“多人共用同一套技能”。將公共方法存放在原型(prototype)中,所有實(shí)例無(wú)需重復(fù)存儲(chǔ)這些方法 [2]

示例:游戲中的敵人構(gòu)造
// 定義敵人原型
function Enemy(type) {
  this.type = type;
}

// 共享攻擊方法(所有敵人都會(huì))
Enemy.prototype.attack = function() {
  console.log(`${this.type}發(fā)起攻擊!`);
};

// 創(chuàng)建實(shí)例
const dragon = new Enemy('火龍');
const goblin = new Enemy('哥布林');

dragon.attack();  // 火龍發(fā)起攻擊!
goblin.attack();  // 哥布林發(fā)起攻擊!

// 驗(yàn)證方法共享
console.log(dragon.attack === goblin.attack); // true ?

[2]: 參考資料4通過Person.prototype案例證明原型共享方法的有效性

flowchart LR
    Enemy.prototype --> 共享方法attack
    dragon實(shí)例 -->|.__proto__| Enemy.prototype
    goblin實(shí)例 -->|.__proto__| Enemy.prototype

為什么用原型?

  1. 內(nèi)存節(jié)省:1000個(gè)實(shí)例共享1個(gè)方法,而非存儲(chǔ)1000份 [3]
  2. 動(dòng)態(tài)更新:在原型添加新方法,現(xiàn)有實(shí)例即時(shí)生效

例如新增Enemy.prototype.run = function() { ... },所有敵人都能調(diào)用.run()


三、對(duì)比總結(jié)

模式 適用場(chǎng)景 優(yōu)點(diǎn) 缺點(diǎn)
工廠模式 需要靈活創(chuàng)建多類型對(duì)象 隱藏創(chuàng)建細(xì)節(jié),代碼簡(jiǎn)潔 類型識(shí)別困難(如無(wú)法用instanceof
原型模式 大量對(duì)象需共享方法或?qū)傩?/td> 節(jié)省內(nèi)存,動(dòng)態(tài)擴(kuò)展性強(qiáng) 復(fù)雜屬性需獨(dú)立初始化(如對(duì)象引用)

[3]: 參考資料3強(qiáng)調(diào)代碼維護(hù)性需通過共享結(jié)構(gòu)實(shí)現(xiàn),這正是原型模式的核心優(yōu)勢(shì)

好的!以下是根據(jù)你的要求修改后的版本,增加了對(duì)比表格、詳細(xì)說明文字以及代碼注釋。


JavaScript 常用設(shè)計(jì)模式解析

一、設(shè)計(jì)模式特點(diǎn)對(duì)比表

設(shè)計(jì)模式 主要用途 優(yōu)點(diǎn) 缺點(diǎn)
工廠模式 創(chuàng)建多種類型的對(duì)象,隱藏創(chuàng)建邏輯,提供統(tǒng)一的創(chuàng)建接口 簡(jiǎn)化對(duì)象創(chuàng)建邏輯,易于擴(kuò)展和維護(hù) 增加了系統(tǒng)的復(fù)雜性,可能需要維護(hù)多個(gè)工廠類
單例模式 確保一個(gè)類只有一個(gè)實(shí)例,并提供全局訪問點(diǎn) 避免重復(fù)實(shí)例化,節(jié)省資源,便于管理全局狀態(tài) 可能導(dǎo)致代碼難以測(cè)試,過度使用會(huì)限制系統(tǒng)的靈活性
原型模式 通過復(fù)制現(xiàn)有對(duì)象創(chuàng)建新對(duì)象,避免復(fù)雜的初始化過程 性能優(yōu)化,減少重復(fù)代碼,易于創(chuàng)建多個(gè)類似對(duì)象 可能導(dǎo)致對(duì)象之間的關(guān)系復(fù)雜,難以管理
代理模式 提供一個(gè)代理對(duì)象來(lái)控制對(duì)實(shí)際對(duì)象的訪問,添加額外邏輯 可以在不修改原始對(duì)象的情況下添加功能,如權(quán)限控制、緩存等 增加了系統(tǒng)的復(fù)雜性,可能影響性能
觀察者模式 定義對(duì)象間的一對(duì)多依賴關(guān)系,當(dāng)一個(gè)對(duì)象改變時(shí),所有依賴它的對(duì)象都會(huì)得到通知 實(shí)現(xiàn)了對(duì)象間的松耦合,便于擴(kuò)展和維護(hù) 可能導(dǎo)致通知風(fēng)暴,影響性能,且依賴關(guān)系復(fù)雜
策略模式 定義一系列算法,封裝起來(lái)并使它們可互換 提供了算法的可切換性,易于擴(kuò)展和維護(hù) 可能導(dǎo)致策略類過多,增加系統(tǒng)的復(fù)雜性
裝飾者模式 動(dòng)態(tài)地給對(duì)象添加額外的功能,而不改變其結(jié)構(gòu) 提供了比繼承更靈活的擴(kuò)展方式,易于添加新功能 可能導(dǎo)致裝飾器過多,影響性能和可讀性
適配器模式 將一個(gè)類的接口轉(zhuǎn)換成客戶希望的另一個(gè)接口 提高了類的兼容性,避免了修改現(xiàn)有代碼 增加了系統(tǒng)的復(fù)雜性,可能隱藏了接口的不一致性
模板方法模式 定義一個(gè)操作中的算法框架,將某些步驟延遲到子類中 提供了代碼復(fù)用性,便于維護(hù)和擴(kuò)展 子類可能過度依賴父類,限制了靈活性
建造者模式 逐步構(gòu)建一個(gè)復(fù)雜的對(duì)象,隱藏構(gòu)建過程 提供了構(gòu)建過程的靈活性,易于擴(kuò)展和維護(hù) 可能增加系統(tǒng)的復(fù)雜性,需要維護(hù)多個(gè)建造者類

二、設(shè)計(jì)模式詳細(xì)解析

1. 工廠模式(Factory Pattern)

詳細(xì)說明

工廠模式就像一個(gè)“制造機(jī)器”,它的主要任務(wù)是根據(jù)不同的需求創(chuàng)建不同類型的對(duì)象,而不需要直接使用 new 關(guān)鍵字去實(shí)例化對(duì)象。想象一下,你去玩具店買玩具,你只需要告訴店員你想要什么類型的玩具(比如小汽車、洋娃娃),店員就會(huì)給你制造出對(duì)應(yīng)類型的玩具,而你不需要自己去組裝玩具。工廠模式的核心在于隱藏了對(duì)象的創(chuàng)建細(xì)節(jié),讓使用者只需要關(guān)心對(duì)象的使用,而不需要關(guān)心對(duì)象是如何被創(chuàng)建的。

代碼示例

// 定義一個(gè)基類 Animal
class Animal {
  speak() {
    return this.makeSound();
  }
}

// 定義具體的 Dog 類
class Dog extends Animal {
  makeSound() {
    return "Woof"; // 狗的叫聲
  }
}

// 定義具體的 Cat 類
class Cat extends Animal {
  makeSound() {
    return "Meow"; // 貓的叫聲
  }
}

// 工廠類,負(fù)責(zé)創(chuàng)建不同類型的動(dòng)物對(duì)象
class AnimalFactory {
  createAnimal(type) {
    switch (type) {
      case "dog":
        return new Dog(); // 創(chuàng)建 Dog 實(shí)例
      case "cat":
        return new Cat(); // 創(chuàng)建 Cat 實(shí)例
      default:
        throw new Error("Unknown animal type"); // 如果類型未知,拋出錯(cuò)誤
    }
  }
}

// 使用工廠模式創(chuàng)建對(duì)象
const factory = new AnimalFactory();
const dog = factory.createAnimal("dog"); // 創(chuàng)建 Dog 實(shí)例
console.log(dog.speak()); // 輸出:Woof
const cat = factory.createAnimal("cat"); // 創(chuàng)建 Cat 實(shí)例
console.log(cat.speak()); // 輸出:Meow

代碼注釋

  • Animal 是一個(gè)基類,定義了一個(gè)通用的 speak 方法。
  • DogCat 是具體的動(dòng)物類,分別實(shí)現(xiàn)了 makeSound 方法。
  • AnimalFactory 是工廠類,通過 createAnimal 方法根據(jù)傳入的類型參數(shù)創(chuàng)建對(duì)應(yīng)的動(dòng)物對(duì)象。
  • 使用工廠模式時(shí),我們只需要通過工廠類的 createAnimal 方法來(lái)獲取對(duì)象,而不需要直接使用 new 關(guān)鍵字。

流程圖

graph TD
    A[開始] --> B[創(chuàng)建工廠實(shí)例]
    B --> C[調(diào)用工廠方法]
    C --> D{根據(jù)類型判斷}
    D -- dog --> E[創(chuàng)建Dog實(shí)例]
    D -- cat --> F[創(chuàng)建Cat實(shí)例]
    E --> G[返回Dog實(shí)例]
    F --> H[返回Cat實(shí)例]
    G --> I[使用實(shí)例]
    H --> I
    I --> J[結(jié)束]

2. 單例模式(Singleton Pattern)

詳細(xì)說明

單例模式確保一個(gè)類只有一個(gè)實(shí)例,并提供一個(gè)全局訪問點(diǎn)。想象一下,一個(gè)國(guó)家的總統(tǒng),全國(guó)只能有一個(gè)總統(tǒng),不管你從哪里獲取總統(tǒng)的信息,都是同一個(gè)總統(tǒng)對(duì)象。如果已經(jīng)有一個(gè)總統(tǒng)實(shí)例了,再獲取總統(tǒng)對(duì)象時(shí),就直接返回這個(gè)已經(jīng)存在的實(shí)例,而不是再創(chuàng)建一個(gè)新的。單例模式的核心在于保證全局唯一性,避免重復(fù)實(shí)例化,節(jié)省資源。

代碼示例

// 定義一個(gè) Logger 類
class Logger {
  constructor() {
    if (Logger.instance) {
      return Logger.instance; // 如果已經(jīng)存在實(shí)例,直接返回
    }
    this.messages = []; // 用于存儲(chǔ)日志消息
    Logger.instance = this; // 保存當(dāng)前實(shí)例
  }

  log(message) {
    this.messages.push(message); // 添加日志消息
  }

  getLogs() {
    return this.messages; // 獲取所有日志消息
  }
}

// 使用單例模式
const logger1 = new Logger();
logger1.log("Hello"); // 添加日志

const logger2 = new Logger();
logger2.log("World"); // 添加日志

console.log(logger1.getLogs()); // 輸出:["Hello", "World"]
console.log(logger1 === logger2); // 輸出:true(logger1 和 logger2 是同一個(gè)實(shí)例)

代碼注釋

  • Logger 類中有一個(gè)靜態(tài)屬性 instance,用于保存唯一的實(shí)例。
  • 在構(gòu)造函數(shù)中,如果 Logger.instance 已經(jīng)存在,則直接返回已存在的實(shí)例,否則創(chuàng)建一個(gè)新的實(shí)例并保存到 Logger.instance 中。
  • 通過 log 方法添加日志消息,通過 getLogs 方法獲取所有日志消息。
  • 無(wú)論創(chuàng)建多少次 Logger 實(shí)例,始終返回同一個(gè)實(shí)例,確保全局唯一性。

流程圖

graph TD
    A[開始] --> B[創(chuàng)建Logger實(shí)例]
    B --> C{是否已存在實(shí)例}
    C -- 是 --> D[返回已存在的實(shí)例]
    C -- 否 --> E[創(chuàng)建新實(shí)例]
    E --> F[保存實(shí)例]
    F --> G[使用實(shí)例]
    D --> G
    G --> H[結(jié)束]

3. 原型模式(Prototype Pattern)

詳細(xì)說明

原型模式允許通過復(fù)制現(xiàn)有的對(duì)象來(lái)創(chuàng)建新的對(duì)象,而不需要通過構(gòu)造函數(shù)來(lái)創(chuàng)建。想象一下,你有一個(gè)已經(jīng)配置好的電腦模板,你可以直接復(fù)制這個(gè)模板來(lái)創(chuàng)建新的電腦,而不需要重新配置。原型模式的核心在于通過對(duì)象的復(fù)制來(lái)創(chuàng)建新對(duì)象,避免了復(fù)雜的初始化過程,提高了性能。

代碼示例

// 定義一個(gè)原型對(duì)象
const animalPrototype = {
  speak() {
    return this.makeSound(); // 調(diào)用 makeSound 方法
  },
};

// 通過 Object.create 復(fù)制原型對(duì)象創(chuàng)建 Dog 實(shí)例
const dog = Object.create(animalPrototype);
dog.makeSound = function () {
  return "Woof"; // 狗的叫聲
};

// 通過 Object.create 復(fù)制原型對(duì)象創(chuàng)建 Cat 實(shí)例
const cat = Object.create(animalPrototype);
cat.makeSound = function () {
  return "Meow"; // 貓的叫聲
};

console.log(dog.speak()); // 輸出:Woof
console.log(cat.speak()); // 輸出:Meow

代碼注釋

  • animalPrototype 是一個(gè)原型對(duì)象,定義了一個(gè)通用的 speak 方法。
  • 使用 Object.create 方法,通過復(fù)制 animalPrototype 來(lái)創(chuàng)建 dogcat 實(shí)例。
  • 每個(gè)實(shí)例都可以通過覆蓋 makeSound 方法來(lái)定義自己的行為。
  • 這種方式避免了通過構(gòu)造函數(shù)創(chuàng)建對(duì)象的復(fù)雜性,同時(shí)保持了對(duì)象的繼承關(guān)系。

流程圖

graph TD
    A[開始] --> B[創(chuàng)建原型對(duì)象]
    B --> C[復(fù)制原型對(duì)象]
    C --> D[修改復(fù)制對(duì)象的屬性]
    D --> E[使用復(fù)制對(duì)象]
    E --> F[結(jié)束]

4. 代理模式(Proxy Pattern)

詳細(xì)說明

代理模式通過創(chuàng)建一個(gè)代理對(duì)象來(lái)控制對(duì)實(shí)際對(duì)象的訪問,代理對(duì)象可以添加額外的邏輯,比如權(quán)限檢查、緩存、遠(yuǎn)程調(diào)用等。想象一下,你去銀行取錢,銀行柜員就是一個(gè)代理,他可以檢查你的身份,確認(rèn)你有權(quán)限取錢后,才會(huì)讓你取錢。代理模式的核心在于在不修改原始對(duì)象的情況下,添加額外的控制邏輯。

代碼示例

// 定義一個(gè)真實(shí)對(duì)象
class RealSubject {
  request() {
    return "RealSubject: Handling request."; // 真實(shí)對(duì)象的請(qǐng)求方法
  }
}

// 定義一個(gè)代理對(duì)象
class Proxy {
  constructor(realSubject) {
    this.realSubject = realSubject; // 保存真實(shí)對(duì)象的引用
  }

  request() {
    if (this.checkAccess()) { // 檢查權(quán)限
      this.realSubject.request(); // 調(diào)用真實(shí)對(duì)象的請(qǐng)求方法
      this.logAccess(); // 記錄日志
    }
  }

  checkAccess() {
    console.log("Proxy: Logging the time of request."); // 記錄請(qǐng)求時(shí)間
    return true; // 假設(shè)權(quán)限檢查通過
  }

  logAccess() {
    console.log("Proxy: Logging the request."); // 記錄請(qǐng)求
  }
}

// 使用代理模式
const realSubject = new RealSubject(); // 創(chuàng)建真實(shí)對(duì)象
const proxy = new Proxy(realSubject); // 創(chuàng)建代理對(duì)象
proxy.request(); // 調(diào)用代理的請(qǐng)求方法

代碼注釋

  • RealSubject 是真實(shí)對(duì)象,定義了一個(gè) request 方法。
  • Proxy 是代理對(duì)象,通過 request 方法控制對(duì)真實(shí)對(duì)象的訪問。
  • 在調(diào)用真實(shí)對(duì)象的 request 方法之前,代理對(duì)象會(huì)執(zhí)行額外的邏輯,如權(quán)限檢查和日志記錄。
  • 通過代理模式,可以在不修改真實(shí)對(duì)象的情況下,添加額外的功能。

流程圖

graph TD
    A[開始] --> B[創(chuàng)建真實(shí)對(duì)象]
    B --> C[創(chuàng)建代理對(duì)象]
    C --> D[調(diào)用代理的請(qǐng)求方法]
    D --> E[檢查權(quán)限]
    E -- 有權(quán)限 --> F[調(diào)用真實(shí)對(duì)象的方法]
    F --> G[記錄日志]
    G --> H[結(jié)束]
    E -- 無(wú)權(quán)限 --> H

5. 觀察者模式(Observer Pattern)

詳細(xì)說明

觀察者模式定義了一種一對(duì)多的依賴關(guān)系,讓多個(gè)觀察者對(duì)象同時(shí)監(jiān)聽某一個(gè)主題對(duì)象。當(dāng)主題對(duì)象發(fā)生變化時(shí),所有依賴它的觀察者都會(huì)自動(dòng)收到通知并進(jìn)行更新。想象一下,你關(guān)注了一個(gè)博主,當(dāng)博主發(fā)布新的文章時(shí),你會(huì)收到通知。觀察者模式的核心在于實(shí)現(xiàn)對(duì)象間的松耦合,當(dāng)一個(gè)對(duì)象改變時(shí),其他對(duì)象可以自動(dòng)響應(yīng)。

代碼示例

// 定義一個(gè)主題對(duì)象
class Subject {
  constructor() {
    this.observers = []; // 保存觀察者列表
  }

  addObserver(observer) {
    this.observers.push(observer); // 添加觀察者
  }

  removeObserver(observer) {
    this.observers = this.observers.filter((obs) => obs !== observer); // 移除觀察者
  }

  notify() {
    this.observers.forEach((observer) => observer.update()); // 通知所有觀察者
  }
}

// 定義一個(gè)觀察者對(duì)象
class Observer {
  update() {
    console.log("Observer: I've been notified!"); // 觀察者收到通知后的響應(yīng)
  }
}

// 使用觀察者模式
const subject = new Subject(); // 創(chuàng)建主題對(duì)象
const observer1 = new Observer(); // 創(chuàng)建觀察者1
const observer2 = new Observer(); // 創(chuàng)建觀察者2

subject.addObserver(observer1); // 添加觀察者1
subject.addObserver(observer2); // 添加觀察者2

subject.notify(); // 通知所有觀察者

代碼注釋

  • Subject 是主題對(duì)象,維護(hù)一個(gè)觀察者列表,并提供添加、移除和通知觀察者的方法。
  • Observer 是觀察者對(duì)象,定義了一個(gè) update 方法,用于響應(yīng)通知。
  • 當(dāng)主題對(duì)象的狀態(tài)發(fā)生變化時(shí),通過調(diào)用 notify 方法通知所有觀察者。
  • 觀察者模式實(shí)現(xiàn)了對(duì)象間的松耦合,便于擴(kuò)展和維護(hù)。

流程圖

graph TD
    A[開始] --> B[創(chuàng)建主題對(duì)象]
    B --> C[創(chuàng)建觀察者對(duì)象]
    C --> D[將觀察者添加到主題]
    D --> E[主題狀態(tài)改變]
    E --> F[通知所有觀察者]
    F --> G[觀察者執(zhí)行更新]
    G --> H[結(jié)束]

6. 策略模式(Strategy Pattern)

詳細(xì)說明

策略模式定義了一系列的算法,把它們封裝起來(lái),并使它們可以互換。策略模式讓算法的變化獨(dú)立于使用算法的客戶。想象一下,你有一個(gè)計(jì)算器,可以選擇不同的運(yùn)算方式(如加法、減法)。策略模式的核心在于通過封裝不同的算法,讓客戶端可以在運(yùn)行時(shí)動(dòng)態(tài)選擇算法,而不需要修改代碼。

代碼示例

// 定義一個(gè)上下文對(duì)象
class Context {
  constructor(strategy) {
    this.strategy = strategy; // 保存策略對(duì)象
  }

  executeStrategy(a, b) {
    return this.strategy(a, b); // 執(zhí)行策略
  }
}

// 定義加法策略
function addStrategy(a, b) {
  return a + b; // 加法運(yùn)算
}

// 定義減法策略
function subtractStrategy(a, b) {
  return a - b; // 減法運(yùn)算
}

// 使用策略模式
const context = new Context(addStrategy); // 使用加法策略
console.log(context.executeStrategy(10, 5)); // 輸出:15

context.strategy = subtractStrategy; // 切換到減法策略
console.log(context.executeStrategy(10, 5)); // 輸出:5

代碼注釋

  • Context 是上下文對(duì)象,保存一個(gè)策略對(duì)象,并通過 executeStrategy 方法執(zhí)行策略。
  • addStrategysubtractStrategy 是具體的策略,分別實(shí)現(xiàn)加法和減法運(yùn)算。
  • 客戶端可以通過動(dòng)態(tài)更換策略對(duì)象來(lái)改變行為,而不需要修改上下文對(duì)象的代碼。

流程圖

graph TD
    A[開始] --> B[創(chuàng)建上下文對(duì)象]
    B --> C[設(shè)置策略]
    C --> D[執(zhí)行策略]
    D --> E[輸出結(jié)果]
    E --> F[結(jié)束]

7. 裝飾者模式(Decorator Pattern)

詳細(xì)說明

裝飾者模式允許動(dòng)態(tài)地給一個(gè)對(duì)象添加額外的功能,而不需要修改其結(jié)構(gòu)。想象一下,你有一件衣服,你可以通過添加裝飾(如徽章、刺繡)來(lái)改變它的外觀,而不需要改變衣服本身的結(jié)構(gòu)。裝飾者模式的核心在于通過組合的方式,動(dòng)態(tài)地?cái)U(kuò)展對(duì)象的功能。

代碼示例

// 定義一個(gè)組件基類
class Component {
  operation() {
    return "Component"; // 基類的通用方法
  }
}

// 定義具體的組件類
class ConcreteComponent extends Component {
  operation() {
    return "ConcreteComponent"; // 具體組件的實(shí)現(xiàn)
  }
}

// 定義裝飾者類
class Decorator extends Component {
  constructor(component) {
    super();
    this.component = component; // 保存組件對(duì)象
  }

  operation() {
    return `Decorator(${this.component.operation()})`; // 調(diào)用組件對(duì)象的方法,并添加額外邏輯
  }
}

// 使用裝飾者模式
const component = new ConcreteComponent(); // 創(chuàng)建具體組件
console.log(component.operation()); // 輸出:ConcreteComponent

const decoratedComponent = new Decorator(component); // 創(chuàng)建裝飾者
console.log(decoratedComponent.operation()); // 輸出:Decorator(ConcreteComponent)

代碼注釋

  • Component 是組件基類,定義了一個(gè)通用的 operation 方法。
  • ConcreteComponent 是具體的組件類,實(shí)現(xiàn)了 operation 方法。
  • Decorator 是裝飾者類,通過組合的方式保存一個(gè)組件對(duì)象,并在調(diào)用組件對(duì)象的方法時(shí)添加額外的邏輯。
  • 通過裝飾者模式,可以在不修改組件類的情況下,動(dòng)態(tài)地?cái)U(kuò)展功能。

流程圖

graph TD
    A[開始] --> B[創(chuàng)建組件對(duì)象]
    B --> C[創(chuàng)建裝飾者對(duì)象]
    C --> D[裝飾者調(diào)用組件方法]
    D --> E[輸出結(jié)果]
    E --> F[結(jié)束]

8. 適配器模式(Adapter Pattern)

詳細(xì)說明

適配器模式將一個(gè)類的接口轉(zhuǎn)換成客戶希望的另一個(gè)接口,使原本不兼容的接口能夠兼容。想象一下,你有一個(gè)充電器,但它只能給手機(jī)充電,而你有一個(gè)平板電腦需要充電。適配器模式的核心在于通過一個(gè)適配器對(duì)象,將充電器的接口轉(zhuǎn)換為平板電腦可以使用的接口,從而實(shí)現(xiàn)兼容。

代碼示例

// 定義一個(gè)媒體播放器接口
class MediaPlayer {
  play(audioType, fileName) {
    console.log(`Playing ${audioType} file. Name: ${fileName}`); // 播放音頻文件
  }
}

// 定義一個(gè)高級(jí)媒體播放器接口
class AdvancedMediaPlayer {
  playVlc(fileName) {
    console.log(`Playing VLC file. Name: ${fileName}`); // 播放 VLC 文件
  }

  playMp4(fileName) {
    console.log(`Playing MP4 file. Name: ${fileName}`); // 播放 MP4 文件
  }
}

// 定義一個(gè)適配器類
class MediaAdapter extends MediaPlayer {
  constructor(audioType, advancedMediaPlayer) {
    super();
    this.audioType = audioType; // 保存音頻類型
    this.advancedMediaPlayer = advancedMediaPlayer; // 保存高級(jí)媒體播放器對(duì)象
  }

  play(audioType, fileName) {
    if (audioType === "vlc") {
      this.advancedMediaPlayer.playVlc(fileName); // 調(diào)用高級(jí)媒體播放器的 playVlc 方法
    } else if (audioType === "mp4") {
      this.advancedMediaPlayer.playMp4(fileName); // 調(diào)用高級(jí)媒體播放器的 playMp4 方法
    }
  }
}

// 使用適配器模式
const mediaPlayer = new MediaPlayer(); // 創(chuàng)建媒體播放器對(duì)象
const advancedMediaPlayer = new AdvancedMediaPlayer(); // 創(chuàng)建高級(jí)媒體播放器對(duì)象
const mediaAdapter = new MediaAdapter("vlc", advancedMediaPlayer); // 創(chuàng)建適配器對(duì)象

mediaAdapter.play("vlc", "song.vlc"); // 通過適配器播放 VLC 文件

代碼注釋

  • MediaPlayer 是媒體播放器接口,定義了一個(gè) play 方法。
  • AdvancedMediaPlayer 是高級(jí)媒體播放器接口,定義了 playVlcplayMp4 方法。
  • MediaAdapter 是適配器類,通過繼承 MediaPlayer 并保存一個(gè) AdvancedMediaPlayer 對(duì)象,將高級(jí)媒體播放器的接口適配到媒體播放器的接口。
  • 通過適配器模式,可以讓客戶端代碼(MediaPlayer)調(diào)用高級(jí)媒體播放器的方法,實(shí)現(xiàn)接口的兼容。

流程圖

graph TD
    A[開始] --> B[創(chuàng)建MediaPlayer實(shí)例]
    B --> C[創(chuàng)建AdvancedMediaPlayer實(shí)例]
    C --> D[創(chuàng)建適配器實(shí)例]
    D --> E[調(diào)用適配器的play方法]
    E --> F{判斷音頻類型}
    F -- vlc --> G[調(diào)用playVlc方法]
    F -- mp4 --> H[調(diào)用playMp4方法]
    G --> I[輸出結(jié)果]
    H --> I
    I --> J[結(jié)束]

9. 模板方法模式(Template Method Pattern)

詳細(xì)說明

模板方法模式定義了一個(gè)操作中的算法的框架,而將一些步驟延遲到子類中。模板方法使得子類可以不改變算法的結(jié)構(gòu)即可重定義算法的某些特定步驟。想象一下,你有一個(gè)烤面包機(jī),它有一個(gè)固定的烤面包流程(預(yù)熱、烤面包、冷卻),但你可以通過更換不同的面包片來(lái)改變烤面包的結(jié)果。模板方法模式的核心在于通過定義一個(gè)算法框架,讓子類可以在不改變框架的情況下,實(shí)現(xiàn)具體的步驟。

代碼示例

// 定義一個(gè)抽象類
class AbstractClass {
  templateMethod() {
    this.baseOperation1(); // 執(zhí)行基礎(chǔ)操作1
    this.requiredOperations1(); // 執(zhí)行需要子類實(shí)現(xiàn)的操作1
    this.baseOperation2(); // 執(zhí)行基礎(chǔ)操作2
    this.hook1(); // 執(zhí)行鉤子方法1
    this.requiredOperations2(); // 執(zhí)行需要子類實(shí)現(xiàn)的操作2
    this.baseOperation3(); // 執(zhí)行基礎(chǔ)操作3
    this.hook2(); // 執(zhí)行鉤子方法2
  }

  baseOperation1() {
    console.log("AbstractClass says: I am doing the bulk of the work"); // 基礎(chǔ)操作1
  }

  baseOperation2() {
    console.log("AbstractClass says: But I let subclasses alter some specific steps"); // 基礎(chǔ)操作2
  }

  baseOperation3() {
    console.log("AbstractClass says: But I am doing the bulk of the work anyway"); // 基礎(chǔ)操作3
  }

  hook1() {} // 鉤子方法1
  hook2() {} // 鉤子方法2
}

// 定義一個(gè)具體的子類
class ConcreteClass1 extends AbstractClass {
  requiredOperations1() {
    console.log("ConcreteClass1 says: Implemented Operation1"); // 子類實(shí)現(xiàn)的操作1
  }

  requiredOperations2() {
    console.log("ConcreteClass1 says: Implemented Operation2"); // 子類實(shí)現(xiàn)的操作2
  }
}

// 定義另一個(gè)具體的子類
class ConcreteClass2 extends AbstractClass {
  requiredOperations1() {
    console.log("ConcreteClass2 says: Implemented Operation1"); // 子類實(shí)現(xiàn)的操作1
  }

  requiredOperations2() {
    console.log("ConcreteClass2 says: Implemented Operation2"); // 子類實(shí)現(xiàn)的操作2
  }

  hook1() {
    console.log("ConcreteClass2 says: Overridden Hook1"); // 子類重寫的鉤子方法1
  }
}

// 使用模板方法模式
const template1 = new ConcreteClass1(); // 創(chuàng)建具體子類1
template1.templateMethod(); // 調(diào)用模板方法

const template2 = new ConcreteClass2(); // 創(chuàng)建具體子類2
template2.templateMethod(); // 調(diào)用模板方法

代碼注釋

  • AbstractClass 是抽象類,定義了一個(gè)模板方法 templateMethod,它按順序調(diào)用一系列操作,包括基礎(chǔ)操作和需要子類實(shí)現(xiàn)的操作。
  • ConcreteClass1ConcreteClass2 是具體的子類,分別實(shí)現(xiàn)了 requiredOperations1requiredOperations2 方法。
  • 子類可以通過重寫鉤子方法(如 hook1hook2)來(lái)擴(kuò)展模板方法的行為。
  • 模板方法模式允許子類在不改變算法結(jié)構(gòu)的情況下,實(shí)現(xiàn)具體的步驟。

流程圖

graph TD
    A[開始] --> B[創(chuàng)建抽象類]
    B --> C[定義模板方法]
    C --> D[調(diào)用基礎(chǔ)操作]
    D --> E[調(diào)用子類實(shí)現(xiàn)的操作]
    E --> F[調(diào)用鉤子方法]
    F --> G[結(jié)束]

10. 建造者模式(Builder Pattern)

詳細(xì)說明

建造者模式將一個(gè)復(fù)雜對(duì)象的構(gòu)建與它的表示分離,使得同樣的構(gòu)建過程可以創(chuàng)建不同的表示。想象一下,你正在建造一座房子,你需要逐步完成不同的步驟(如打地基、建墻壁、安裝門窗)。建造者模式的核心在于通過一個(gè)建造者對(duì)象逐步構(gòu)建復(fù)雜對(duì)象,隱藏具體的構(gòu)建過程,讓客戶端只需要關(guān)心最終的結(jié)果。

代碼示例

// 定義一個(gè)復(fù)雜對(duì)象
class Car {
  constructor() {
    this.type = null; // 車型
    this.seats = null; // 座位數(shù)
    this.engine = null; // 發(fā)動(dòng)機(jī)類型
    this.tripComputer = null; // 是否有行程計(jì)算機(jī)
    this.gps = null; // 是否有 GPS
  }
}

// 定義一個(gè)建造者類
class CarBuilder {
  constructor() {
    this.car = new Car(); // 創(chuàng)建一個(gè) Car 實(shí)例
  }

  setType(type) {
    this.car.type = type; // 設(shè)置車型
    return this; // 返回當(dāng)前對(duì)象,支持鏈?zhǔn)秸{(diào)用
  }

  setSeats(seats) {
    this.car.seats = seats; // 設(shè)置座位數(shù)
    return this; // 返回當(dāng)前對(duì)象,支持鏈?zhǔn)秸{(diào)用
  }

  setEngine(engine) {
    this.car.engine = engine; // 設(shè)置發(fā)動(dòng)機(jī)類型
    return this; // 返回當(dāng)前對(duì)象,支持鏈?zhǔn)秸{(diào)用
  }

  setTripComputer(tripComputer) {
    this.car.tripComputer = tripComputer; // 設(shè)置行程計(jì)算機(jī)
    return this; // 返回當(dāng)前對(duì)象,支持鏈?zhǔn)秸{(diào)用
  }

  setGPS(gps) {
    this.car.gps = gps; // 設(shè)置 GPS
    return this; // 返回當(dāng)前對(duì)象,支持鏈?zhǔn)秸{(diào)用
  }

  build() {
    return this.car; // 返回構(gòu)建好的 Car 實(shí)例
  }
}

// 使用建造者模式
const carBuilder = new CarBuilder(); // 創(chuàng)建建造者對(duì)象
const car = carBuilder
  .setType("SUV") // 設(shè)置車型為 SUV
  .setSeats(4) // 設(shè)置座位數(shù)為 4
  .setEngine("V6") // 設(shè)置發(fā)動(dòng)機(jī)類型為 V6
  .setTripComputer(true) // 設(shè)置行程計(jì)算機(jī)為 true
  .setGPS(true) // 設(shè)置 GPS 為 true
  .build(); // 構(gòu)建汽車

console.log(car); // 輸出構(gòu)建好的汽車對(duì)象

代碼注釋

  • Car 是一個(gè)復(fù)雜對(duì)象,包含多個(gè)屬性(如車型、座位數(shù)、發(fā)動(dòng)機(jī)類型等)。
  • CarBuilder 是建造者類,通過一系列的設(shè)置方法(如 setTypesetSeats 等)逐步構(gòu)建 Car 對(duì)象。
  • 每個(gè)設(shè)置方法都返回當(dāng)前建造者對(duì)象,支持鏈?zhǔn)秸{(diào)用,最后通過 build 方法返回構(gòu)建好的 Car 對(duì)象。
  • 建造者模式隱藏了構(gòu)建過程的細(xì)節(jié),讓客戶端只需要關(guān)心最終的結(jié)果。

流程圖

graph TD
    A[開始] --> B[創(chuàng)建建造者實(shí)例]
    B --> C[設(shè)置類型]
    C --> D[設(shè)置座位數(shù)]
    D --> E[設(shè)置發(fā)動(dòng)機(jī)]
    E --> F[設(shè)置行程計(jì)算機(jī)]
    F --> G[設(shè)置GPS]
    G --> H[構(gòu)建汽車]
    H --> I[結(jié)束]

希望這些詳細(xì)的說明和代碼注釋能幫助你更好地理解這些設(shè)計(jì)模式!


目錄:總目錄
上篇文章:紅寶書第十一講:超易懂版「ES6類與繼承」零基礎(chǔ)教程:用現(xiàn)實(shí)例子+圖解實(shí)現(xiàn)


腳注


  1. 《JavaScript高級(jí)程序設(shè)計(jì)(第5版)》索引的"factory pattern"驗(yàn)證工廠模式的代碼組織形式。 ? ?

  2. 《JavaScript高級(jí)程序設(shè)計(jì)(第5版)》通過Person類與.prototype的關(guān)聯(lián)說明原型共享機(jī)制。 ? ?

  3. 《JavaScript高級(jí)程序設(shè)計(jì)(第5版)》指出代碼可維護(hù)性的核心在于避免冗余,與原型設(shè)計(jì)理念一致。 ? ?

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,488評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,034評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,327評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,554評(píng)論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,337評(píng)論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,883評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,975評(píng)論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,114評(píng)論 0 286
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,625評(píng)論 1 332
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,555評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,737評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,244評(píng)論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,973評(píng)論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,615評(píng)論 1 280
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,343評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,699評(píng)論 2 370

推薦閱讀更多精彩內(nèi)容