<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          【總結(jié)】1059- 圖解常見的九種設(shè)計(jì)模式

          共 22812字,需瀏覽 46分鐘

           ·

          2021-08-25 10:15

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

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

          2、結(jié)構(gòu)型模式:用來(lái)描述如何將類或?qū)ο蟀凑漳撤N布局組成更大的結(jié)構(gòu)。包括代理、適配器、橋接、裝飾、外觀、享元和組合 7 種模式。

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

          接下來(lái)阿寶哥將結(jié)合一些生活中的場(chǎng)景并通過精美的配圖,來(lái)向大家介紹 9 種常用的設(shè)計(jì)模式。

          一、建造者模式

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

          一輛小汽車 ?? 通常由 發(fā)動(dòng)機(jī)、底盤、車身和電氣設(shè)備 四大部分組成。汽車電氣設(shè)備的內(nèi)部構(gòu)造很復(fù)雜,簡(jiǎn)單起見,我們只考慮三個(gè)部分:引擎、底盤和車身。

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

          1.1 實(shí)現(xiàn)代碼
          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);
            }
          }

          在以上代碼中,我們定義一個(gè) CarBuilder 類,并提供了 addChassisaddEngineaddBody 3 個(gè)方法用于組裝車子的不同部位,當(dāng)車子的 3 個(gè)部分都組裝完成后,調(diào)用 build 方法就可以開始造車。

          1.2 使用示例
          const car = new CarBuilder()
            .addEngine('v12')
            .addBody('鎂合金')
            .addChassis('復(fù)合材料')
            .build();
          1.3 應(yīng)用場(chǎng)景及案例
          • 需要生成的產(chǎn)品對(duì)象有復(fù)雜的內(nèi)部結(jié)構(gòu),這些產(chǎn)品對(duì)象通常包含多個(gè)成員屬性。
          • 需要生成的產(chǎn)品對(duì)象的屬性相互依賴,需要指定其生成順序。
          • 隔離復(fù)雜對(duì)象的創(chuàng)建和使用,并使得相同的創(chuàng)建過程可以創(chuàng)建不同的產(chǎn)品。
          • Github - node-sql-query:https://github.com/dresende/node-sql-query

          二、工廠模式

          在現(xiàn)實(shí)生活中,工廠是負(fù)責(zé)生產(chǎn)產(chǎn)品的,比如牛奶、面包或禮物等,這些產(chǎn)品滿足了我們?nèi)粘5纳硇枨蟆?/p>

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

          2.1 簡(jiǎn)單工廠

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

          在上圖中,阿寶哥模擬了用戶購(gòu)車的流程,小王和小秦分別向 BMW 工廠訂購(gòu)了 BMW730 和 BMW840 型號(hào)的車型,接著工廠會(huì)先判斷用戶選擇的車型,然后按照對(duì)應(yīng)的模型進(jìn)行生產(chǎn)并在生產(chǎn)完成后交付給用戶。

          下面我們來(lái)看一下如何使用簡(jiǎn)單工廠來(lái)描述 BMW 工廠生產(chǎn)指定型號(hào)車子的過程。

          2.1.1 實(shí)現(xiàn)代碼
          abstract class BMW {
            abstract run(): void;
          }

          class BMW730 extends BMW {
            run(): void {
              console.log("BMW730 發(fā)動(dòng)咯");
            }
          }

          class BMW840 extends BMW {
            run(): void {
              console.log("BMW840 發(fā)動(dòng)咯");
            }
          }

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

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

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

          bmw730.run();
          bmw840.run();
          2.1.3 應(yīng)用場(chǎng)景
          • 工廠類負(fù)責(zé)創(chuàng)建的對(duì)象比較少:由于創(chuàng)建的對(duì)象比較少,不會(huì)造成工廠方法中業(yè)務(wù)邏輯過于復(fù)雜。
          • 客戶端只需知道傳入工廠類靜態(tài)方法的參數(shù),而不需要關(guān)心創(chuàng)建對(duì)象的細(xì)節(jié)。

          2.2 工廠方法

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

          在工廠方法模式中,工廠父類負(fù)責(zé)定義創(chuàng)建產(chǎn)品對(duì)象的公共接口,而工廠子類則負(fù)責(zé)生成具體的產(chǎn)品對(duì)象, 這樣做的目的是將產(chǎn)品類的實(shí)例化操作延遲到工廠子類中完成,即通過工廠子類來(lái)確定究竟應(yīng)該實(shí)例化哪一個(gè)具體產(chǎn)品類。

          在上圖中,阿寶哥模擬了用戶購(gòu)車的流程,小王和小秦分別向 BMW 730 和 BMW 840 工廠訂購(gòu)了 BMW730 和 BMW840 型號(hào)的車子,接著工廠按照對(duì)應(yīng)的模型進(jìn)行生產(chǎn)并在生產(chǎn)完成后交付給用戶。

          同樣,我們來(lái)看一下如何使用工廠方法來(lái)描述 BMW 工廠生產(chǎn)指定型號(hào)車子的過程。

          2.2.1 實(shí)現(xiàn)代碼
          abstract class BMWFactory {
            abstract produceBMW(): BMW;
          }

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

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

          在以上代碼中,我們分別創(chuàng)建了 BMW730FactoryBMW840Factory 兩個(gè)工廠類,然后使用這兩個(gè)類的實(shí)例來(lái)生產(chǎn)不同型號(hào)的車子。

          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 應(yīng)用場(chǎng)景
          • 一個(gè)類不知道它所需要的對(duì)象的類:在工廠方法模式中,客戶端不需要知道具體產(chǎn)品類的類名,只需要知道所對(duì)應(yīng)的工廠即可,具體的產(chǎn)品對(duì)象由具體工廠類創(chuàng)建;客戶端需要知道創(chuàng)建具體產(chǎn)品的工廠類。
          • 一個(gè)類通過其子類來(lái)指定創(chuàng)建哪個(gè)對(duì)象:在工廠方法模式中,對(duì)于抽象工廠類只需要提供一個(gè)創(chuàng)建產(chǎn)品的接口,而由其子類來(lái)確定具體要?jiǎng)?chuàng)建的對(duì)象,利用面向?qū)ο蟮亩鄳B(tài)性和里氏代換原則,在程序運(yùn)行時(shí),子類對(duì)象將覆蓋父類對(duì)象,從而使得系統(tǒng)更容易擴(kuò)展。

          2.3 抽象工廠

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

          在工廠方法模式中具體工廠負(fù)責(zé)生產(chǎn)具體的產(chǎn)品,每一個(gè)具體工廠對(duì)應(yīng)一種具體產(chǎn)品,工廠方法也具有唯一性,一般情況下,一個(gè)具體工廠中只有一個(gè)工廠方法或者一組重載的工廠方法。但是有時(shí)候我們需要一個(gè)工廠可以提供多個(gè)產(chǎn)品對(duì)象,而不是單一的產(chǎn)品對(duì)象。

          在上圖中,阿寶哥模擬了用戶購(gòu)車的流程,小王向 BMW 工廠訂購(gòu)了 BMW730,工廠按照 730 對(duì)應(yīng)的模型進(jìn)行生產(chǎn)并在生產(chǎn)完成后交付給小王。而小秦向同一個(gè) BMW 工廠訂購(gòu)了 BMW840,工廠按照 840 對(duì)應(yīng)的模型進(jìn)行生產(chǎn)并在生產(chǎn)完成后交付給小秦。

          下面我們來(lái)看一下如何使用抽象工廠來(lái)描述上述的購(gòu)車過程。

          2.3.1 實(shí)現(xiàn)代碼
          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 應(yīng)用場(chǎng)景
          • 一個(gè)系統(tǒng)不應(yīng)當(dāng)依賴于產(chǎn)品類實(shí)例如何被創(chuàng)建、組合和表達(dá)的細(xì)節(jié),這對(duì)于所有類型的工廠模式都是重要的。
          • 系統(tǒng)中有多于一個(gè)的產(chǎn)品族,而每次只使用其中某一產(chǎn)品族。
          • 系統(tǒng)提供一個(gè)產(chǎn)品類的庫(kù),所有的產(chǎn)品以同樣的接口出現(xiàn),從而使客戶端不依賴于具體實(shí)現(xiàn)。

          三、單例模式

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

          在上圖中,阿寶哥模擬了借車的流程,小王臨時(shí)有急事找阿寶哥借車子,阿寶哥家的車子剛好沒用,就借給小王了。當(dāng)天,小秦也需要用車子,也找阿寶哥借車,因?yàn)榘毟缂依镏挥幸惠v車子,所以就沒有車可借了。

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

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

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

          3.1 實(shí)現(xiàn)代碼
          class Singleton {
            // 定義私有的靜態(tài)屬性,來(lái)保存對(duì)象實(shí)例
            private static singleton: Singleton;
            private constructor() {}

            // 提供一個(gè)靜態(tài)的方法來(lái)獲取對(duì)象實(shí)例
            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 應(yīng)用場(chǎng)景
          • 需要頻繁實(shí)例化然后銷毀的對(duì)象。
          • 創(chuàng)建對(duì)象時(shí)耗時(shí)過多或耗資源過多,但又經(jīng)常用到的對(duì)象。
          • 系統(tǒng)只需要一個(gè)實(shí)例對(duì)象,如系統(tǒng)要求提供一個(gè)唯一的序列號(hào)生成器或資源管理器,或者需要考慮資源消耗太大而只允許創(chuàng)建一個(gè)對(duì)象。

          四、適配器模式

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

          4.1 實(shí)現(xiàn)代碼
          interface Logger {
            info(message: string): Promise<void>;
          }

          interface CloudLogger {
            sendToServer(message: stringtypestring): Promise<void>;
          }

          class AliLogger implements CloudLogger {
            public async sendToServer(message: stringtypestring): 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}`);
            }
          }

          在以上代碼中,因?yàn)?LoggerCloudLogger 這兩個(gè)接口不匹配,所以我們引入了 CloudLoggerAdapter 適配器來(lái)解決兼容性問題。

          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 應(yīng)用場(chǎng)景及案例
          • 以前開發(fā)的系統(tǒng)存在滿足新系統(tǒng)功能需求的類,但其接口同新系統(tǒng)的接口不一致。
          • 使用第三方提供的組件,但組件接口定義和自己要求的接口定義不同。
          • Github - axios-mock-adapter:https://github.com/ctimmerm/axios-mock-adapter

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

          5.1 觀察者模式

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

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

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

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

          5.1.1 實(shí)現(xiàn)代碼
          interface Observer {
            notify: Function;
          }

          class ConcreteObserver implements Observer{
            constructor(private name: string) {}

            notify() {
              console.log(`${this.name} has been notified.`);
            }
          }

          class Subject { 
            private observers: Observer[] = [];

            public addObserver(observer: Observer): void {
              console.log(observer, "is pushed!");
              this.observers.push(observer);
            }

            public deleteObserver(observer: Observer): void {
              console.log("remove", observer);
              const n: number = this.observers.indexOf(observer);
              n != -1 && this.observers.splice(n, 1);
            }

            public notifyObservers(): void {
              console.log("notify all the observers"this.observers);
              this.observers.forEach(observer => observer.notify());
            }
          }
          5.1.2 使用示例
          const subject: Subject = new Subject();
          const xiaoQin = new ConcreteObserver("小秦");
          const xiaoWang = new ConcreteObserver("小王");
          subject.addObserver(xiaoQin);
          subject.addObserver(xiaoWang);
          subject.notifyObservers();

          subject.deleteObserver(xiaoQin);
          subject.notifyObservers();
          5.1.3 應(yīng)用場(chǎng)景及案例
          • 一個(gè)對(duì)象的行為依賴于另一個(gè)對(duì)象的狀態(tài)。或者換一種說(shuō)法,當(dāng)被觀察對(duì)象(目標(biāo)對(duì)象)的狀態(tài)發(fā)生改變時(shí) ,會(huì)直接影響到觀察對(duì)象的行為。
          • RxJS Subject:https://github.com/ReactiveX/rxjs/blob/master/src/internal/Subject.ts
          • RxJS Subject 文檔:https://rxjs.dev/guide/subject

          5.2 發(fā)布訂閱模式

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

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

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

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

          5.2.1 實(shí)現(xiàn)代碼
          type EventHandler = (...args: any[]) => any;

          class EventEmitter {
            private c = new Map<string, EventHandler[]>();

            // 訂閱指定的主題
            subscribe(topic: string, ...handlers: EventHandler[]) {
              let topics = this.c.get(topic);
              if (!topics) {
                this.c.set(topic, topics = []);
              }
              topics.push(...handlers);
            }

            // 取消訂閱指定的主題
            unsubscribe(topic: string, handler?: EventHandler): boolean {
              if (!handler) {
                return this.c.delete(topic);
              }

              const topics = this.c.get(topic);
              if (!topics) {
                return false;
              }
              
              const index = topics.indexOf(handler);

              if (index < 0) {
                return false;
              }
              topics.splice(index, 1);
              if (topics.length === 0) {
                this.c.delete(topic);
              }
              return true;
            }

            // 為指定的主題發(fā)布消息
            publish(topic: string, ...args: any[]): any[] | null {
              const topics = this.c.get(topic);
              if (!topics) {
                return null;
              }
              return topics.map(handler => {
                try {
                  return handler(...args);
                } catch (e) {
                  console.error(e);
                  return null;
                }
              });
            }
          }
          5.2.2 使用示例
          const eventEmitter = new EventEmitter();
          eventEmitter.subscribe("ts"(msg) => console.log(`收到訂閱的消息:${msg}`) );

          eventEmitter.publish("ts""TypeScript發(fā)布訂閱模式");
          eventEmitter.unsubscribe("ts");
          eventEmitter.publish("ts""TypeScript發(fā)布訂閱模式");
          5.2.3 應(yīng)用場(chǎng)景
          • 對(duì)象間存在一對(duì)多關(guān)系,一個(gè)對(duì)象的狀態(tài)發(fā)生改變會(huì)影響其他對(duì)象。
          • 作為事件總線,來(lái)實(shí)現(xiàn)不同組件間或模塊間的通信。
          • BetterScroll - EventEmitter:https://github.com/ustbhuangyi/better-scroll/blob/dev/packages/shared-utils/src/events.ts
          • EventEmitter 在插件化架構(gòu)的應(yīng)用:https://mp.weixin.qq.com/s/N4iw3bi0bxJ57J8EAp5ctQ

          六、策略模式

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

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

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

          6.1 實(shí)現(xiàn)代碼

          為了更好地理解以下代碼,我們先來(lái)看一下對(duì)應(yīng)的 UML 類圖:

          interface Strategy {
            authenticate(...args: any): any;
          }

          class Authenticator {
            strategy: any;
            constructor() {
              this.strategy = null;
            }

            setStrategy(strategy: any) {
              this.strategy = strategy;
            }

            authenticate(...args: any) {
              if (!this.strategy) {
                console.log('尚未設(shè)置認(rèn)證策略');
                return;
              }
              return this.strategy.authenticate(...args);
            }
          }

          class WechatStrategy implements Strategy {
            authenticate(wechatToken: string) {
              if (wechatToken !== '123') {
                console.log('無(wú)效的微信用戶');
                return;
              }
              console.log('微信認(rèn)證成功');
            }
          }

          class LocalStrategy implements Strategy {
            authenticate(username: string, password: string) {
              if (username !== 'abao' && password !== '123') {
                console.log('賬號(hào)或密碼錯(cuò)誤');
                return;
              }
              console.log('賬號(hào)和密碼認(rèn)證成功');
            }
          }
          6.2 使用示例
          const auth = new Authenticator();

          auth.setStrategy(new WechatStrategy());
          auth.authenticate('123456');

          auth.setStrategy(new LocalStrategy());
          auth.authenticate('abao''123');
          6.3 應(yīng)用場(chǎng)景及案例
          • 一個(gè)系統(tǒng)需要?jiǎng)討B(tài)地在幾種算法中選擇一種時(shí),可將每個(gè)算法封裝到策略類中。
          • 多個(gè)類只區(qū)別在表現(xiàn)行為不同,可以使用策略模式,在運(yùn)行時(shí)動(dòng)態(tài)選擇具體要執(zhí)行的行為。
          • 一個(gè)類定義了多種行為,并且這些行為在這個(gè)類的操作中以多個(gè)條件語(yǔ)句的形式出現(xiàn),可將每個(gè)條件分支移入它們各自的策略類中以代替這些條件語(yǔ)句。
          • 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

          七、職責(zé)鏈模式

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

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

          在日常的軟件開發(fā)過程中,對(duì)于職責(zé)鏈來(lái)說(shuō),一種常見的應(yīng)用場(chǎng)景是中間件,下面我們來(lái)看一下如何利用職責(zé)鏈來(lái)處理請(qǐng)求。

          7.1 實(shí)現(xiàn)代碼

          為了更好地理解以下代碼,我們先來(lái)看一下對(duì)應(yīng)的 UML 類圖:

          interface IHandler {
            addMiddleware(h: IHandler): IHandler;
            get(url: string, callback: (data: any) => void): void;
          }

          abstract class AbstractHandler implements IHandler {
            next!: IHandler;
            addMiddleware(h: IHandler) {
              this.next = h;
              return this.next;
            }

            get(url: string, callback: (data: any) => void) {
              if (this.next) {
                return this.next.get(url, callback);
              }
            }
          }

          // 定義Auth中間件
          class Auth extends AbstractHandler {
            isAuthenticated: boolean;
            constructor(username: string, password: string) {
              super();

              this.isAuthenticated = false;
              if (username === 'abao' && password === '123') {
                this.isAuthenticated = true;
              }
            }

            get(url: string, callback: (data: any) => void) {
              if (this.isAuthenticated) {
                return super.get(url, callback);
              } else {
                throw new Error('Not Authorized');
              }
            }
          }

          // 定義Logger中間件
          class Logger extends AbstractHandler {
            get(url: string, callback: (data: any) => void) {
              console.log('/GET Request to: ', url);
              return super.get(url, callback);
            }
          }

          class Route extends AbstractHandler {
            URLMaps: {[key: string]: any};
            constructor() {
              super();
              this.URLMaps = {
                '/api/todos': [{ title: 'learn ts' }, { title: 'learn react' }],
                '/api/random'Math.random(),
              };
            }

            get(url: string, callback: (data: any) => void) {
              super.get(url, callback);

              if (this.URLMaps.hasOwnProperty(url)) {
                callback(this.URLMaps[url]);
              }
            }
          }
          7.2 使用示例
          const route = new Route();
          route.addMiddleware(new Auth('abao''123')).addMiddleware(new Logger());

          route.get('/api/todos'data => {
            console.log(JSON.stringify({ data }, null2));
          });

          route.get('/api/random'data => {
            console.log(data);
          });
          7.3 應(yīng)用場(chǎng)景
          • 可處理一個(gè)請(qǐng)求的對(duì)象集合應(yīng)被動(dòng)態(tài)指定。
          • 想在不明確指定接收者的情況下,向多個(gè)對(duì)象中的一個(gè)提交一個(gè)請(qǐng)求。
          • 有多個(gè)對(duì)象可以處理一個(gè)請(qǐng)求,哪個(gè)對(duì)象處理該請(qǐng)求運(yùn)行時(shí)自動(dòng)確定,客戶端只需要把請(qǐng)求提交到鏈上即可。

          八、模板方法模式

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

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

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

          8.1 實(shí)現(xiàn)代碼

          為了更好地理解以下代碼,我們先來(lái)看一下對(duì)應(yīng)的 UML 類圖:

          import fs from 'fs';

          abstract class DataParser {
            data: string = '';
            out: any = null;

            // 這就是所謂的模板方法
            parse(pathUrl: string) {
              this.readFile(pathUrl);
              this.doParsing();
              this.printData();
            }

            readFile(pathUrl: string) {
              this.data = fs.readFileSync(pathUrl, 'utf8');
            }

            abstract doParsing(): void;
            
            printData() {
              console.log(this.out);
            }
          }

          class CSVParser extends DataParser {
            doParsing() {
              this.out = this.data.split(',');
            }
          }

          class MarkupParser extends DataParser {
            doParsing() {
              this.out = this.data.match(/<\w+>.*<\/\w+>/gim);
            }
          }
          8.2 使用示例
          const csvPath = './data.csv';
          const mdPath = './design-pattern.md';

          new CSVParser().parse(csvPath);
          new MarkupParser().parse(mdPath);
          8.3 應(yīng)用場(chǎng)景
          • 算法的整體步驟很固定,但其中個(gè)別部分易變時(shí),這時(shí)候可以使用模板方法模式,將容易變的部分抽象出來(lái),供子類實(shí)現(xiàn)。
          • 當(dāng)需要控制子類的擴(kuò)展時(shí),模板方法只在特定點(diǎn)調(diào)用鉤子操作,這樣就只允許在這些點(diǎn)進(jìn)行擴(kuò)展。

          九、參考資源

          • 維基百科 - 設(shè)計(jì)模式
          • Java設(shè)計(jì)模式:23種設(shè)計(jì)模式全面解析
          • Design Patterns Everyday

          1. JavaScript 重溫系列(22篇全)
          2. ECMAScript 重溫系列(10篇全)
          3. JavaScript設(shè)計(jì)模式 重溫系列(9篇全)
          4. 正則 / 框架 / 算法等 重溫系列(16篇全)
          5. Webpack4 入門(上)|| Webpack4 入門(下)
          6. MobX 入門(上) ||  MobX 入門(下)
          7. 120+篇原創(chuàng)系列匯總

          回復(fù)“加群”與大佬們一起交流學(xué)習(xí)~

          點(diǎn)擊“閱讀原文”查看 120+ 篇原創(chuàng)文章




          瀏覽 54
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日韩伦人妻无码 | 99热最新 | 无码高清视频, | 中国一级特黄大片学生 | 国产又粗又猛又爽又黄91 |