<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>

          圖解常見的九種設(shè)計模式

          共 14911字,需瀏覽 30分鐘

           ·

          2020-10-28 22:22

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

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

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

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

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

          一、建造者模式

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

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

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

          1.1 實現(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);
          ??}
          }

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

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

          二、工廠模式

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

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

          2.1 簡單工廠

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

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

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

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

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

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

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

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

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

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

          2.2 工廠方法

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

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

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

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

          2.2.1 實現(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 兩個工廠類,然后使用這兩個類的實例來生產(chǎn)不同型號的車子。

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

          2.3 抽象工廠

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

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

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

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

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

          三、單例模式

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

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

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

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

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

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

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

          四、適配器模式

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

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

          interface?CloudLogger?{
          ??sendToServer(message:?string,?type:?string):?Promise<void>;
          }

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

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

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

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

          5.1 觀察者模式

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

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

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

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

          5.1.1 實現(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 應用場景及案例
          • 一個對象的行為依賴于另一個對象的狀態(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ā)布訂閱模式

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

          六、策略模式

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

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

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

          6.1 實現(xiàn)代碼

          為了更好地理解以下代碼,我們先來看一下對應的 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è)置認證策略');
          ??????return;
          ????}
          ????return?this.strategy.authenticate(...args);
          ??}
          }

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

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

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

          auth.setStrategy(new?LocalStrategy());
          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ā)送者和接受者之間的耦合關(guān)系。在職責鏈模式里,很多對象由每一個對象對其下家的引用而連接起來形成一條鏈。請求在這個鏈上傳遞,直到鏈上的某一個對象決定處理此請求。

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

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

          7.1 實現(xiàn)代碼

          為了更好地理解以下代碼,我們先來看一下對應的 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?},?null,?2));
          });

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

          八、模板方法模式

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

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

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

          8.1 實現(xiàn)代碼

          為了更好地理解以下代碼,我們先來看一下對應的 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 應用場景
          • 算法的整體步驟很固定,但其中個別部分易變時,這時候可以使用模板方法模式,將容易變的部分抽象出來,供子類實現(xiàn)。
          • 當需要控制子類的擴展時,模板方法只在特定點調(diào)用鉤子操作,這樣就只允許在這些點進行擴展。

          九、參考資源

          • 維基百科 - 設(shè)計模式
          • Java設(shè)計模式:23種設(shè)計模式全面解析
          • Design Patterns Everyday
          推薦閱讀
          從 12.9K 的前端開源項目我學到了啥?

          從 12.9K 的前端開源項目我學到了啥?

          「1.8W字」2020不可多得的 TS 學習指南

          「1.8W字」2020不可多得的 TS 學習指南

          細數(shù) TS 中那些奇怪的符號

          細數(shù) TS 中那些奇怪的符號

          掃碼關(guān)注公眾號,訂閱更多精彩內(nèi)容。



          你點的每個贊,我都認真當成了喜歡
          瀏覽 22
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  国产高清无码在线视频 | 男女做爱视频网站 | 婷婷综合网 性 | 国产午夜一区二区 | 天天草婷婷蕉蕉操 |