自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

圖解常見的九種設計模式

開發(fā) 前端
在軟件工程中,設計模式(Design Pattern)是對軟件設計中普遍存在(反復出現(xiàn))的各種問題,所提出的解決方案。

 [[345020]]

本文轉載自微信公眾號「全棧修仙之路」,作者阿寶哥。轉載本文請聯(lián)系全棧修仙之路公眾號。

在軟件工程中,設計模式(Design Pattern)是對軟件設計中普遍存在(反復出現(xiàn))的各種問題,所提出的解決方案。根據(jù)模式的目的來劃分的話,GoF(Gang of Four)設計模式可以分為以下 3 種類型:

 

1、創(chuàng)建型模式:用來描述 “如何創(chuàng)建對象”,它的主要特點是 “將對象的創(chuàng)建和使用分離”。包括單例、原型、工廠方法、抽象工廠和建造者 5 種模式。

2、結構型模式:用來描述如何將類或對象按照某種布局組成更大的結構。包括代理、適配器、橋接、裝飾、外觀、享元和組合 7 種模式。

3、行為型模式:用來識別對象之間的常用交流模式以及如何分配職責。包括模板方法、策略、命令、職責鏈、狀態(tài)、觀察者、中介者、迭代器、訪問者、備忘錄和解釋器 11 種模式。

接下來阿寶哥將結合一些生活中的場景并通過精美的配圖,來向大家介紹 9 種常用的設計模式。

一、建造者模式

建造者模式(Builder Pattern)將一個復雜對象分解成多個相對簡單的部分,然后根據(jù)不同需要分別創(chuàng)建它們,最后構建成該復雜對象。

一輛小汽車 🚗 通常由 發(fā)動機、底盤、車身和電氣設備 四大部分組成。汽車電氣設備的內部構造很復雜,簡單起見,我們只考慮三個部分:引擎、底盤和車身。

 

在現(xiàn)實生活中,小汽車也是由不同的零部件組裝而成,比如上圖中我們把小汽車分成引擎、底盤和車身三大部分。下面我們來看一下如何使用建造者模式來造車子。

1.1 實現(xiàn)代碼

  1. class Car { 
  2.   constructor( 
  3.     public engine: string, 
  4.     public chassis: string,  
  5.     public body: string 
  6.   ) {} 
  7.  
  8. class CarBuilder { 
  9.   engine!: string; // 引擎 
  10.   chassis!: string; // 底盤 
  11.   body!: string; // 車身 
  12.  
  13.   addChassis(chassis: string) { 
  14.     this.chassis = chassis; 
  15.     return this; 
  16.   } 
  17.  
  18.   addEngine(engine: string) { 
  19.     this.engine = engine; 
  20.     return this; 
  21.   } 
  22.  
  23.   addBody(body: string) { 
  24.     this.body = body; 
  25.     return this; 
  26.   } 
  27.  
  28.   build() { 
  29.     return new Car(this.engine, this.chassis, this.body); 
  30.   } 

在以上代碼中,我們定義一個 CarBuilder 類,并提供了 addChassis、addEngine和 addBody 3 個方法用于組裝車子的不同部位,當車子的 3 個部分都組裝完成后,調用build 方法就可以開始造車。

1.2 使用示例

  1. const car = new CarBuilder() 
  2.   .addEngine('v12'
  3.   .addBody('鎂合金'
  4.   .addChassis('復合材料'
  5.   .build(); 

1.3 應用場景及案例

需要生成的產品對象有復雜的內部結構,這些產品對象通常包含多個成員屬性。

需要生成的產品對象的屬性相互依賴,需要指定其生成順序。

隔離復雜對象的創(chuàng)建和使用,并使得相同的創(chuàng)建過程可以創(chuàng)建不同的產品。

Github - node-sql-query:https://github.com/dresende/node-sql-query

二、工廠模式

在現(xiàn)實生活中,工廠是負責生產產品的,比如牛奶、面包或禮物等,這些產品滿足了我們日常的生理需求。

 

在眾多設計模式當中,有一種被稱為工廠模式的設計模式,它提供了創(chuàng)建對象的最佳方式。工廠模式可以分為:簡單工廠模式、工廠方法模式和抽象工廠模式。

2.1 簡單工廠

簡單工廠模式又叫 靜態(tài)方法模式,因為工廠類中定義了一個靜態(tài)方法用于創(chuàng)建對象。簡單工廠讓使用者不用知道具體的參數(shù)就可以創(chuàng)建出所需的 ”產品“ 類,即使用者可以直接消費產品而不需要知道產品的具體生產細節(jié)。

 

在上圖中,阿寶哥模擬了用戶購車的流程,小王和小秦分別向 BMW 工廠訂購了 BMW730 和 BMW840 型號的車型,接著工廠會先判斷用戶選擇的車型,然后按照對應的模型進行生產并在生產完成后交付給用戶。

下面我們來看一下如何使用簡單工廠來描述 BMW 工廠生產指定型號車子的過程。

2.1.1 實現(xiàn)代碼

  1. abstract class BMW { 
  2.   abstract run(): void; 
  3.  
  4. class BMW730 extends BMW { 
  5.   run(): void { 
  6.     console.log("BMW730 發(fā)動咯"); 
  7.   } 
  8.  
  9. class BMW840 extends BMW { 
  10.   run(): void { 
  11.     console.log("BMW840 發(fā)動咯"); 
  12.   } 
  13.  
  14. class BMWFactory { 
  15.   public static produceBMW(model: "730" | "840"): BMW { 
  16.     if (model === "730") { 
  17.       return new BMW730(); 
  18.     } else { 
  19.       return new BMW840(); 
  20.     } 
  21.   } 

在以上代碼中,我們定義一個 BMWFactory 類,該類提供了一個靜態(tài)的 produceBMW()方法,用于根據(jù)不同的模型參數(shù)來創(chuàng)建不同型號的車子。

2.1.2 使用示例

  1. const bmw730 = BMWFactory.produceBMW("730"); 
  2. const bmw840 = BMWFactory.produceBMW("840"); 
  3.  
  4. bmw730.run(); 
  5. bmw840.run(); 

2.1.3 應用場景

  • 工廠類負責創(chuàng)建的對象比較少:由于創(chuàng)建的對象比較少,不會造成工廠方法中業(yè)務邏輯過于復雜。
  • 客戶端只需知道傳入工廠類靜態(tài)方法的參數(shù),而不需要關心創(chuàng)建對象的細節(jié)。

2.2 工廠方法

工廠方法模式(Factory Method Pattern)又稱為工廠模式,也叫多態(tài)工廠(Polymorphic Factory)模式,它屬于類創(chuàng)建型模式。

在工廠方法模式中,工廠父類負責定義創(chuàng)建產品對象的公共接口,而工廠子類則負責生成具體的產品對象, 這樣做的目的是將產品類的實例化操作延遲到工廠子類中完成,即通過工廠子類來確定究竟應該實例化哪一個具體產品類。

 

在上圖中,阿寶哥模擬了用戶購車的流程,小王和小秦分別向 BMW 730 和 BMW 840 工廠訂購了 BMW730 和 BMW840 型號的車子,接著工廠按照對應的模型進行生產并在生產完成后交付給用戶。

同樣,我們來看一下如何使用工廠方法來描述 BMW 工廠生產指定型號車子的過程。

2.2.1 實現(xiàn)代碼

  1. abstract class BMWFactory { 
  2.   abstract produceBMW(): BMW; 
  3.  
  4. class BMW730Factory extends BMWFactory { 
  5.   produceBMW(): BMW { 
  6.     return new BMW730(); 
  7.   } 
  8.  
  9. class BMW840Factory extends BMWFactory { 
  10.   produceBMW(): BMW { 
  11.     return new BMW840(); 
  12.   } 

在以上代碼中,我們分別創(chuàng)建了 BMW730Factory 和 BMW840Factory 兩個工廠類,然后使用這兩個類的實例來生產不同型號的車子。

2.2.2 使用示例

  1. const bmw730Factory = new BMW730Factory(); 
  2. const bmw840Factory = new BMW840Factory(); 
  3.  
  4. const bmw730 = bmw730Factory.produceBMW(); 
  5. const bmw840 = bmw840Factory.produceBMW(); 
  6.  
  7. bmw730.run(); 
  8. bmw840.run(); 

2.2.3 應用場景

  • 一個類不知道它所需要的對象的類:在工廠方法模式中,客戶端不需要知道具體產品類的類名,只需要知道所對應的工廠即可,具體的產品對象由具體工廠類創(chuàng)建;客戶端需要知道創(chuàng)建具體產品的工廠類。
  • 一個類通過其子類來指定創(chuàng)建哪個對象:在工廠方法模式中,對于抽象工廠類只需要提供一個創(chuàng)建產品的接口,而由其子類來確定具體要創(chuàng)建的對象,利用面向對象的多態(tài)性和里氏代換原則,在程序運行時,子類對象將覆蓋父類對象,從而使得系統(tǒng)更容易擴展。

2.3 抽象工廠

抽象工廠模式(Abstract Factory Pattern),提供一個創(chuàng)建一系列相關或相互依賴對象的接口,而無須指定它們具體的類。

在工廠方法模式中具體工廠負責生產具體的產品,每一個具體工廠對應一種具體產品,工廠方法也具有唯一性,一般情況下,一個具體工廠中只有一個工廠方法或者一組重載的工廠方法。但是有時候我們需要一個工廠可以提供多個產品對象,而不是單一的產品對象。

 

在上圖中,阿寶哥模擬了用戶購車的流程,小王向 BMW 工廠訂購了 BMW730,工廠按照 730 對應的模型進行生產并在生產完成后交付給小王。而小秦向同一個 BMW 工廠訂購了 BMW840,工廠按照 840 對應的模型進行生產并在生產完成后交付給小秦。

下面我們來看一下如何使用抽象工廠來描述上述的購車過程。

2.3.1 實現(xiàn)代碼

  1. abstract class BMWFactory { 
  2.   abstract produce730BMW(): BMW730; 
  3.   abstract produce840BMW(): BMW840; 
  4.  
  5. class ConcreteBMWFactory extends BMWFactory { 
  6.   produce730BMW(): BMW730 { 
  7.     return new BMW730(); 
  8.   } 
  9.  
  10.   produce840BMW(): BMW840 { 
  11.     return new BMW840(); 
  12.   } 

2.3.2 使用示例

  1. const bmwFactory = new ConcreteBMWFactory(); 
  2.  
  3. const bmw730 = bmwFactory.produce730BMW(); 
  4. const bmw840 = bmwFactory.produce840BMW(); 
  5.  
  6. bmw730.run(); 
  7. bmw840.run(); 

2.3.3 應用場景

  • 一個系統(tǒng)不應當依賴于產品類實例如何被創(chuàng)建、組合和表達的細節(jié),這對于所有類型的工廠模式都是重要的。
  • 系統(tǒng)中有多于一個的產品族,而每次只使用其中某一產品族。
  • 系統(tǒng)提供一個產品類的庫,所有的產品以同樣的接口出現(xiàn),從而使客戶端不依賴于具體實現(xiàn)。

三、單例模式

單例模式(Singleton Pattern)是一種常用的模式,有一些對象我們往往只需要一個,比如全局緩存、瀏覽器中的 window 對象等。單例模式用于保證一個類僅有一個實例,并提供一個訪問它的全局訪問點。

 

在上圖中,阿寶哥模擬了借車的流程,小王臨時有急事找阿寶哥借車子,阿寶哥家的車子剛好沒用,就借給小王了。當天,小秦也需要用車子,也找阿寶哥借車,因為阿寶哥家里只有一輛車子,所以就沒有車可借了。

對于車子來說,它雖然給生活帶來了很大的便利,但養(yǎng)車也需要一筆不小的費用(車位費、油費和保養(yǎng)費等),所以阿寶哥家里只有一輛車子。

在開發(fā)軟件系統(tǒng)時,如果遇到創(chuàng)建對象時耗時過多或耗資源過多,但又經常用到的對象,我們就可以考慮使用單例模式。

下面我們來看一下如何使用 TypeScript 來實現(xiàn)單例模式。

3.1 實現(xiàn)代碼

  1. class Singleton { 
  2.   // 定義私有的靜態(tài)屬性,來保存對象實例 
  3.   private static singleton: Singleton; 
  4.   private constructor() {} 
  5.  
  6.   // 提供一個靜態(tài)的方法來獲取對象實例 
  7.   public static getInstance(): Singleton { 
  8.     if (!Singleton.singleton) { 
  9.       Singleton.singleton = new Singleton(); 
  10.     } 
  11.     return Singleton.singleton; 
  12.   } 

3.2 使用示例

  1. let instance1 = Singleton.getInstance(); 
  2. let instance2 = Singleton.getInstance(); 
  3.  
  4. console.log(instance1 === instance2); // true 

3.3 應用場景

  • 需要頻繁實例化然后銷毀的對象。
  • 創(chuàng)建對象時耗時過多或耗資源過多,但又經常用到的對象。
  • 系統(tǒng)只需要一個實例對象,如系統(tǒng)要求提供一個唯一的序列號生成器或資源管理器,或者需要考慮資源消耗太大而只允許創(chuàng)建一個對象。

四、適配器模式

在實際生活中,也存在適配器的使用場景,比如:港式插頭轉換器、電源適配器和 USB 轉接口。而在軟件工程中,適配器模式的作用是解決兩個軟件實體間的接口不兼容的問題。使用適配器模式之后,原本由于接口不兼容而不能工作的兩個軟件實體就可以一起工作。

 

4.1 實現(xiàn)代碼

  1. interface Logger { 
  2.   info(message: string): Promise<void>; 
  3.  
  4. interface CloudLogger { 
  5.   sendToServer(message: string, type: string): Promise<void>; 
  6.  
  7. class AliLogger implements CloudLogger { 
  8.   public async sendToServer(message: string, type: string): Promise<void> { 
  9.     console.info(message); 
  10.     console.info('This Message was saved with AliLogger'); 
  11.   } 
  12.  
  13. class CloudLoggerAdapter implements Logger { 
  14.   protected cloudLogger: CloudLogger; 
  15.  
  16.   constructor (cloudLogger: CloudLogger) { 
  17.     this.cloudLogger = cloudLogger; 
  18.   } 
  19.  
  20.   public async info(message: string): Promise<void> { 
  21.     await this.cloudLogger.sendToServer(message, 'info'); 
  22.   } 
  23.  
  24. class NotificationService { 
  25.   protected logger: Logger; 
  26.    
  27.   constructor (logger: Logger) {     
  28.     this.logger = logger; 
  29.   } 
  30.  
  31.   public async send(message: string): Promise<void> { 
  32.     await this.logger.info(`Notification sended: ${message}`); 
  33.   } 

在以上代碼中,因為 Logger 和 CloudLogger 這兩個接口不匹配,所以我們引入了 CloudLoggerAdapter 適配器來解決兼容性問題。

4.2 使用示例

  1. (async () => { 
  2.   const aliLogger = new AliLogger(); 
  3.   const cloudLoggerAdapter = new CloudLoggerAdapter(aliLogger); 
  4.   const notificationService = new NotificationService(cloudLoggerAdapter); 
  5.   await notificationService.send('Hello semlinker, To Cloud'); 
  6. })(); 

4.3 應用場景及案例

  • 以前開發(fā)的系統(tǒng)存在滿足新系統(tǒng)功能需求的類,但其接口同新系統(tǒng)的接口不一致。
  • 使用第三方提供的組件,但組件接口定義和自己要求的接口定義不同。
  • Github - axios-mock-adapter:https://github.com/ctimmerm/axios-mock-adapter

五、觀察者模式 & 發(fā)布訂閱模式

5.1 觀察者模式

觀察者模式,它定義了一種一對多的關系,讓多個觀察者對象同時監(jiān)聽某一個主題對象,這個主題對象的狀態(tài)發(fā)生變化時就會通知所有的觀察者對象,使得它們能夠自動更新自己。

在觀察者模式中有兩個主要角色:Subject(主題)和 Observer(觀察者)。

 

在上圖中,Subject(主題)就是阿寶哥的 TS 專題文章,而觀察者就是小秦和小王。由于觀察者模式支持簡單的廣播通信,當消息更新時,會自動通知所有的觀察者。

下面我們來看一下如何使用 TypeScript 來實現(xiàn)觀察者模式。

5.1.1 實現(xiàn)代碼

  1. interface Observer { 
  2.   notify: Function
  3.  
  4. class ConcreteObserver implements Observer{ 
  5.   constructor(private name: string) {} 
  6.  
  7.   notify() { 
  8.     console.log(`${this.name} has been notified.`); 
  9.   } 
  10.  
  11. class Subject {  
  12.   private observers: Observer[] = []; 
  13.  
  14.   public addObserver(observer: Observer): void { 
  15.     console.log(observer, "is pushed!"); 
  16.     this.observers.push(observer); 
  17.   } 
  18.  
  19.   public deleteObserver(observer: Observer): void { 
  20.     console.log("remove", observer); 
  21.     const n: number = this.observers.indexOf(observer); 
  22.     n != -1 && this.observers.splice(n, 1); 
  23.   } 
  24.  
  25.   public notifyObservers(): void { 
  26.     console.log("notify all the observers", this.observers); 
  27.     this.observers.forEach(observer => observer.notify()); 
  28.   } 

5.1.2 使用示例

  1. const subject: Subject = new Subject(); 
  2. const xiaoQin = new ConcreteObserver("小秦"); 
  3. const xiaoWang = new ConcreteObserver("小王"); 
  4. subject.addObserver(xiaoQin); 
  5. subject.addObserver(xiaoWang); 
  6. subject.notifyObservers(); 
  7.  
  8. subject.deleteObserver(xiaoQin); 
  9. subject.notifyObservers(); 

5.1.3 應用場景及案例

  • 一個對象的行為依賴于另一個對象的狀態(tài)。或者換一種說法,當被觀察對象(目標對象)的狀態(tài)發(fā)生改變時 ,會直接影響到觀察對象的行為。
  • RxJS Subject:https://github.com/ReactiveX/rxjs/blob/master/src/internal/Subject.ts
  • RxJS Subject 文檔:https://rxjs.dev/guide/subject

5.2 發(fā)布訂閱模式

在軟件架構中,發(fā)布/訂閱是一種消息范式,消息的發(fā)送者(稱為發(fā)布者)不會將消息直接發(fā)送給特定的接收者(稱為訂閱者)。而是將發(fā)布的消息分為不同的類別,然后分別發(fā)送給不同的訂閱者。 同樣的,訂閱者可以表達對一個或多個類別的興趣,只接收感興趣的消息,無需了解哪些發(fā)布者存在。

在發(fā)布訂閱模式中有三個主要角色:Publisher(發(fā)布者)、 Channels(通道)和 Subscriber(訂閱者)。

 

在上圖中,Publisher(發(fā)布者)是阿寶哥,Channels(通道)中 Topic A 和 Topic B 分別對應于 TS 專題和 Deno 專題,而 Subscriber(訂閱者)就是小秦、小王和小池。

下面我們來看一下如何使用 TypeScript 來實現(xiàn)發(fā)布訂閱模式。

5.2.1 實現(xiàn)代碼

  1. type EventHandler = (...args: any[]) => any
  2.  
  3. class EventEmitter { 
  4.   private c = new Map<string, EventHandler[]>(); 
  5.  
  6.   // 訂閱指定的主題 
  7.   subscribe(topic: string, ...handlers: EventHandler[]) { 
  8.     let topics = this.c.get(topic); 
  9.     if (!topics) { 
  10.       this.c.set(topic, topics = []); 
  11.     } 
  12.     topics.push(...handlers); 
  13.   } 
  14.  
  15.   // 取消訂閱指定的主題 
  16.   unsubscribe(topic: string, handler?: EventHandler): boolean { 
  17.     if (!handler) { 
  18.       return this.c.delete(topic); 
  19.     } 
  20.  
  21.     const topics = this.c.get(topic); 
  22.     if (!topics) { 
  23.       return false
  24.     } 
  25.      
  26.     const index = topics.indexOf(handler); 
  27.  
  28.     if (index < 0) { 
  29.       return false
  30.     } 
  31.     topics.splice(index, 1); 
  32.     if (topics.length === 0) { 
  33.       this.c.delete(topic); 
  34.     } 
  35.     return true
  36.   } 
  37.  
  38.   // 為指定的主題發(fā)布消息 
  39.   publish(topic: string, ...args: any[]): any[] | null { 
  40.     const topics = this.c.get(topic); 
  41.     if (!topics) { 
  42.       return null
  43.     } 
  44.     return topics.map(handler => { 
  45.       try { 
  46.         return handler(...args); 
  47.       } catch (e) { 
  48.         console.error(e); 
  49.         return null
  50.       } 
  51.     }); 
  52.   } 

5.2.2 使用示例

  1. const eventEmitter = new EventEmitter(); 
  2. eventEmitter.subscribe("ts", (msg) => console.log(`收到訂閱的消息:${msg}`) ); 
  3.  
  4. eventEmitter.publish("ts""TypeScript發(fā)布訂閱模式"); 
  5. eventEmitter.unsubscribe("ts"); 
  6. eventEmitter.publish("ts""TypeScript發(fā)布訂閱模式"); 

5.2.3 應用場景

  • 對象間存在一對多關系,一個對象的狀態(tài)發(fā)生改變會影響其他對象。
  • 作為事件總線,來實現(xiàn)不同組件間或模塊間的通信。
  • BetterScroll - EventEmitter:https://github.com/ustbhuangyi/better-scroll/blob/dev/packages/shared-utils/src/events.ts
  • EventEmitter 在插件化架構的應用:https://mp.weixin.qq.com/s/N4iw3bi0bxJ57J8EAp5ctQ

六、策略模式

策略模式(Strategy Pattern)定義了一系列的算法,把它們一個個封裝起來,并且使它們可以互相替換。策略模式的重心不是如何實現(xiàn)算法,而是如何組織、調用這些算法,從而讓程序結構更靈活、可維護、可擴展。

 

目前在一些主流的 Web 站點中,都提供了多種不同的登錄方式。比如賬號密碼登錄、手機驗證碼登錄和第三方登錄。為了方便維護不同的登錄方式,我們可以把不同的登錄方式封裝成不同的登錄策略。

下面我們來看一下如何使用策略模式來封裝不同的登錄方式。

6.1 實現(xiàn)代碼

為了更好地理解以下代碼,我們先來看一下對應的 UML 類圖:

  1. interface Strategy { 
  2.   authenticate(...args: any): any
  3.  
  4. class Authenticator { 
  5.   strategy: any
  6.   constructor() { 
  7.     this.strategy = null
  8.   } 
  9.  
  10.   setStrategy(strategy: any) { 
  11.     this.strategy = strategy; 
  12.   } 
  13.  
  14.   authenticate(...args: any) { 
  15.     if (!this.strategy) { 
  16.       console.log('尚未設置認證策略'); 
  17.       return
  18.     } 
  19.     return this.strategy.authenticate(...args); 
  20.   } 
  21.  
  22. class WechatStrategy implements Strategy { 
  23.   authenticate(wechatToken: string) { 
  24.     if (wechatToken !== '123') { 
  25.       console.log('無效的微信用戶'); 
  26.       return
  27.     } 
  28.     console.log('微信認證成功'); 
  29.   } 
  30.  
  31. class LocalStrategy implements Strategy { 
  32.   authenticate(username: string, password: string) { 
  33.     if (username !== 'abao' && password !== '123') { 
  34.       console.log('賬號或密碼錯誤'); 
  35.       return
  36.     } 
  37.     console.log('賬號和密碼認證成功'); 
  38.   } 

6.2 使用示例

  1. const auth = new Authenticator(); 
  2.  
  3. auth.setStrategy(new WechatStrategy()); 
  4. auth.authenticate('123456'); 
  5.  
  6. auth.setStrategy(new LocalStrategy()); 
  7. auth.authenticate('abao''123'); 

6.3 應用場景及案例

  • 一個系統(tǒng)需要動態(tài)地在幾種算法中選擇一種時,可將每個算法封裝到策略類中。
  • 多個類只區(qū)別在表現(xiàn)行為不同,可以使用策略模式,在運行時動態(tài)選擇具體要執(zhí)行的行為。
  • 一個類定義了多種行為,并且這些行為在這個類的操作中以多個條件語句的形式出現(xiàn),可將每個條件分支移入它們各自的策略類中以代替這些條件語句。
  • Github - passport-local:https://github.com/jaredhanson/passport-local
  • Github - passport-oauth2:https://github.com/jaredhanson/passport-oauth2
  • Github - zod:https://github.com/vriad/zod/blob/master/src/types/string.ts

七、職責鏈模式

職責鏈模式是使多個對象都有機會處理請求,從而避免請求的發(fā)送者和接受者之間的耦合關系。在職責鏈模式里,很多對象由每一個對象對其下家的引用而連接起來形成一條鏈。請求在這個鏈上傳遞,直到鏈上的某一個對象決定處理此請求。

 

在公司中不同的崗位擁有不同的職責與權限。以上述的請假流程為例,當阿寶哥請 1 天假時,只要組長審批就可以了,不需要流轉到主管和總監(jiān)。如果職責鏈上的某個環(huán)節(jié)無法處理當前的請求,若含有下個環(huán)節(jié),則會把請求轉交給下個環(huán)節(jié)來處理。

在日常的軟件開發(fā)過程中,對于職責鏈來說,一種常見的應用場景是中間件,下面我們來看一下如何利用職責鏈來處理請求。

7.1 實現(xiàn)代碼

為了更好地理解以下代碼,我們先來看一下對應的 UML 類圖:

  1. interface IHandler { 
  2.   addMiddleware(h: IHandler): IHandler; 
  3.   get(url: string, callback: (data: any) => void): void; 
  4.  
  5. abstract class AbstractHandler implements IHandler { 
  6.   next!: IHandler; 
  7.   addMiddleware(h: IHandler) { 
  8.     this.next = h; 
  9.     return this.next
  10.   } 
  11.  
  12.   get(url: string, callback: (data: any) => void) { 
  13.     if (this.next) { 
  14.       return this.next.get(url, callback); 
  15.     } 
  16.   } 
  17.  
  18. // 定義Auth中間件 
  19. class Auth extends AbstractHandler { 
  20.   isAuthenticated: boolean; 
  21.   constructor(username: string, password: string) { 
  22.     super(); 
  23.  
  24.     this.isAuthenticated = false
  25.     if (username === 'abao' && password === '123') { 
  26.       this.isAuthenticated = true
  27.     } 
  28.   } 
  29.  
  30.   get(url: string, callback: (data: any) => void) { 
  31.     if (this.isAuthenticated) { 
  32.       return super.get(url, callback); 
  33.     } else { 
  34.       throw new Error('Not Authorized'); 
  35.     } 
  36.   } 
  37.  
  38. // 定義Logger中間件 
  39. class Logger extends AbstractHandler { 
  40.   get(url: string, callback: (data: any) => void) { 
  41.     console.log('/GET Request to: ', url); 
  42.     return super.get(url, callback); 
  43.   } 
  44.  
  45. class Route extends AbstractHandler { 
  46.   URLMaps: {[key: string]: any}; 
  47.   constructor() { 
  48.     super(); 
  49.     this.URLMaps = { 
  50.       '/api/todos': [{ title: 'learn ts' }, { title: 'learn react' }], 
  51.       '/api/random': Math.random(), 
  52.     }; 
  53.   } 
  54.  
  55.   get(url: string, callback: (data: any) => void) { 
  56.     super.get(url, callback); 
  57.  
  58.     if (this.URLMaps.hasOwnProperty(url)) { 
  59.       callback(this.URLMaps[url]); 
  60.     } 
  61.   } 

7.2 使用示例

  1. const route = new Route(); 
  2. route.addMiddleware(new Auth('abao''123')).addMiddleware(new Logger()); 
  3.  
  4. route.get('/api/todos', data => { 
  5.   console.log(JSON.stringify({ data }, null, 2)); 
  6. }); 
  7.  
  8. route.get('/api/random', data => { 
  9.   console.log(data); 
  10. }); 

7.3 應用場景

可處理一個請求的對象集合應被動態(tài)指定。

想在不明確指定接收者的情況下,向多個對象中的一個提交一個請求。

有多個對象可以處理一個請求,哪個對象處理該請求運行時自動確定,客戶端只需要把請求提交到鏈上即可。

八、模板方法模式

模板方法模式由兩部分結構組成:抽象父類和具體的實現(xiàn)子類。通常在抽象父類中封裝了子類的算法框架,也包括實現(xiàn)一些公共方法以及封裝子類中所有方法的執(zhí)行順序。子類通過繼承這個抽象類,也繼承了整個算法結構,并且可以選擇重寫父類的方法。

 

在上圖中,阿寶哥通過使用不同的解析器來分別解析 CSV 和 Markup 文件。雖然解析的是不同的類型的文件,但文件的處理流程是一樣的。這里主要包含讀取文件、解析文件和打印數(shù)據(jù)三個步驟。針對這個場景,我們就可以引入模板方法來封裝以上三個步驟的處理順序。

下面我們來看一下如何使用模板方法來實現(xiàn)上述的解析流程。

8.1 實現(xiàn)代碼

為了更好地理解以下代碼,我們先來看一下對應的 UML 類圖:

  1. import fs from 'fs'
  2.  
  3. abstract class DataParser { 
  4.   data: string = ''
  5.   outany = null
  6.  
  7.   // 這就是所謂的模板方法 
  8.   parse(pathUrl: string) { 
  9.     this.readFile(pathUrl); 
  10.     this.doParsing(); 
  11.     this.printData(); 
  12.   } 
  13.  
  14.   readFile(pathUrl: string) { 
  15.     this.data = fs.readFileSync(pathUrl, 'utf8'); 
  16.   } 
  17.  
  18.   abstract doParsing(): void; 
  19.    
  20.   printData() { 
  21.     console.log(this.out); 
  22.   } 
  23.  
  24. class CSVParser extends DataParser { 
  25.   doParsing() { 
  26.     this.out = this.data.split(','); 
  27.   } 
  28.  
  29. class MarkupParser extends DataParser { 
  30.   doParsing() { 
  31.     this.out = this.data.match(/<\w+>.*<\/\w+>/gim); 
  32.   } 

8.2 使用示例

  1. const csvPath = './data.csv'
  2. const mdPath = './design-pattern.md'
  3.  
  4. new CSVParser().parse(csvPath); 
  5. new MarkupParser().parse(mdPath); 

8.3 應用場景

算法的整體步驟很固定,但其中個別部分易變時,這時候可以使用模板方法模式,將容易變的部分抽象出來,供子類實現(xiàn)。

 

當需要控制子類的擴展時,模板方法只在特定點調用鉤子操作,這樣就只允許在這些點進行擴展。

 

責任編輯:武曉燕 來源: 全棧修仙之路
相關推薦

2024-11-08 16:08:28

2009-06-29 18:11:40

JSP設計模式

2020-09-11 10:36:24

設計模式代碼

2020-04-10 20:32:44

網絡安全黑客隱私

2022-02-13 22:42:52

設計模式策略

2021-10-27 10:03:16

風險管理企業(yè)ERM

2010-06-12 16:16:47

UML設計

2022-03-25 11:01:28

Golang裝飾模式Go 語言

2021-04-20 22:09:13

Python編程語言

2020-06-28 10:15:39

架構模式軟件

2017-09-14 09:30:38

軟件架構模式

2021-02-19 14:07:03

JavaScript編程開發(fā)

2022-07-27 20:37:45

主流企業(yè)架構

2021-08-02 07:57:03

設計系統(tǒng)客戶端

2022-06-07 08:55:04

Golang單例模式語言

2020-10-14 13:58:14

23種設計模式速記

2015-09-14 09:31:44

結對設計

2021-10-29 09:40:21

設計模式軟件

2021-08-26 08:00:00

Django開發(fā)框架

2019-01-30 15:23:02

數(shù)據(jù)庫MySQLMongoDB
點贊
收藏

51CTO技術棧公眾號