Web開發應了解的5種設計模式
本文轉載自微信公眾號「前端進階之路」,作者三余。轉載本文請聯系前端進階之路公眾號。
什么是設計模式?
設計模式是對軟件設計開發過程中反復出現的某類問題的通用解決方案。設計模式更多的是指導思想和方法論,而不是現成的代碼,當然每種設計模式都有每種語言中的具體實現方式。學習設計模式更多的是理解各種模式的內在思想和解決的問題,畢竟這是前人無數經驗總結成的最佳實踐,而代碼實現則是對加深理解的輔助。使用設計模式是為了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。在本文中,我將介紹其中常見的五種設計模式在JavaScript中實際使用場景:
單例設計模式
定義
單例模式僅允許類或對象具有單個實例,并且它使用全局變量來存儲該實例。
實現方法是判斷是否存在該對象的實例,如果已存在則不再創建
使用場景適用于業務場景中只能存在一個的實例,比如彈窗,購物車
實現
單例模式分為懶漢式和餓漢式:
- 懶漢式
- let ShopCar = (function () {
- let instance;
- function init() {
- /*這里定義單例代碼*/
- return {
- buy(good) {
- this.goods.push(good);
- },
- goods: [],
- };
- }
- return {
- getInstance: function () {
- if (!instance) {
- instance = init();
- }
- return instance;
- },
- };
- })();
- let car1 = ShopCar.getInstance();
- let car2 = ShopCar.getInstance();
- car1.buy('橘子');
- car2.buy('蘋果');
- console.log(car1.goods); //[ '橘子', '蘋果' ]
- console.log(car1 === car2); // true
- 餓漢式
- var ShopCar = (function () {
- var instance = init();
- function init() {
- /*這里定義單例代碼*/ return {
- buy(good) {
- this.goods.push(good);
- },
- goods: [],
- };
- }
- return {
- getInstance: function () {
- return instance;
- },
- };
- })();
- let car1 = ShopCar.getInstance();
- let car2 = ShopCar.getInstance();
- car1.buy('橘子');
- car2.buy('蘋果'); //[ '橘子', '蘋果' ]
- console.log(car1.goods);
- console.log(car1 === car2); // true
懶漢式在類加載時,不創建實例,因此類加載速度快,但運行時獲取對象的速度慢;
餓漢式在類加載時就完成了初始化,所以類加載較慢,但獲取對象的速度快
策略模式
定義
策略模式定義一系列的算法,將每一個算法封裝起來,并讓他們可以相互替換。
實現方法定義一組可變的策略類封裝具體算法,定義一組不變的環境類將請求委托給某一個策略類
使用場景適用于業務場景中需要判斷多種條件,甚至包含復雜條件嵌套的,可以使用策略模式來提升代碼的可維護性和可讀性。比如支付,博客權限校驗
實現
實例:
- // 定義幾個策略類
- var PaymentMethodStrategy = {
- BankAccount: function (money) {
- return money > 50 ? money * 0.7 : money;
- },
- CreditCard: function (money) {
- return money * 0.8;
- },
- Alipay: function (money) {
- return money;
- },
- };
- /*環境類*/
- var userPay = function (selectedStrategy, money) {
- return PaymentMethodStrategy[selectedStrategy](money);
- };
- console.log('銀行卡支付價格為:' + userPay('BankAccount', 100)); //70
- console.log('支付寶支付價格為:' + userPay('Alipay', 100)); //100
- console.log('信用卡支付價格為:' + userPay('CreditCard', 100)); //80
觀察者模式
定義
觀察者模式是對象的行為模式,在對象之間定義了一對多的依賴關系,就是多個觀察者和一個被觀察者之間的關系,當被觀察者發生變化的時候,會通知所有的觀察者對象,他們做出相對應的操作。
實現方法定義一組可變的策略類封裝具體算法,定義一組不變的環境類將請求委托給某一個策略類
使用場景適用于業務場景中當一個對象的狀態發生變化時,需要自動通知其他關聯對象,自動刷新對象狀態,或者說執行對應對象的方法,比如你是一個老師,需要通知班里家長的時候,你可以建一個群(列表)。每次通知事件的時候只要循環執行這個列表就好了(群發),而不用關心這個列表里有誰。
實現
實例:
- // 創建一個群,保存通知,通知變化之后通知每個家長(觸發所有觀察者對象)
- class Group {
- constructor() {
- this.message = '暫無通知';
- this.parents = [];
- }
- getMessage() {
- return this.message;
- }
- setMassage(message) {
- this.message = message;
- this.notifyAllObservers();
- }
- notifyAllObservers() {
- this.parents.forEach((parent) => {
- parent.update();
- });
- }
- attach(parent) {
- this.parents.push(parent);
- }
- }
- // 觀察者,每個家長
- class Parent {
- constructor(name, group) {
- this.name = name;
- this.group = group;
- this.group.attach(this);
- }
- update() {
- console.log(`${this.name} 收到通知: ${this.group.getMessage()}`);
- }
- }
- let group = new Group();
- let t1 = new Parent('李媽媽', group);
- let t2 = new Parent('王爸爸', group);
- let t3 = new Parent('張爺爺', group);
- group.setMassage('開家長會');
- group.setMassage('開運動會');
- /*
- 李媽媽 收到通知: 開家長會
- 王爸爸 收到通知: 開家長會
- 張爺爺 收到通知: 開家長會
- 李媽媽 收到通知: 開運動會
- 王爸爸 收到通知: 開運動會
- 張爺爺 收到通知: 開運動會
- */
發布訂閱模式
定義
發布訂閱模式指的是希望接收通知的對象(Subscriber)基于一個主題通過自定義事件訂閱主題,發布事件的對象(Publisher)通過發布主題事件的方式通知各個訂閱該主題的 Subscriber 對象。
實現
- const pubSub = {
- list:{},
- subscribe(key,fn){ // 訂閱
- if (!this.list[key]) {
- this.list[key] = [];
- }
- this.list[key].push(fn);
- },
- publish(){ // 發布
- const arg = arguments;
- const key = Array.prototype.shift.call(arg);
- const fns = this.list[key];
- if(!fns || fns.length<=0) return false;
- for(var i=0,len=fns.length;i<len;i++){
- fns[i].apply(this, arg);
- }
- },
- unSubscribe(key) { // 取消訂閱
- delete this.list[key];
- }
- };
- // 進行訂閱
- pubSub.subscribe('name', (name) => {
- console.log('your name is ' + name);
- });
- pubSub.subscribe('sex', (sex) => {
- console.log('your sex is ' + sex);
- });
- // 進行發布
- pubSub.publish('name', 'ttsy1'); // your name is ttsy1
- pubSub.publish('sex', 'male'); // your sex is male
上述代碼的訂閱是基于 name 和 sex 主題來自定義事件,發布是通過 name 和 sex 主題并傳入自定義事件的參數,最終觸發了特定主題的自定義事件。
可以通過 unSubscribe 方法取消特定主題的訂閱。
- pubSub.subscribe('name', (name) => {
- console.log('your name is ' + name);
- });
- pubSub.subscribe('sex', (sex) => {
- console.log('your sex is ' + sex);
- });
- pubSub.unSubscribe('name');
- pubSub.publish('name', 'ttsy1'); // 這個主題被取消訂閱了
- pubSub.publish('sex', 'male'); // your sex is male
觀察者模式 VS 發布訂閱模式:
觀察者模式與發布訂閱模式都是定義了一個一對多的依賴關系,當有關狀態發生變更時則執行相應的更新。
不同的是,在觀察者模式中依賴于 Subject 對象的一系列 Observer 對象在被通知之后只能執行同一個特定的更新方法,而在發布訂閱模式中則可以基于不同的主題去執行不同的自定義事件。
相對而言,發布訂閱模式比觀察者模式要更加靈活多變。
裝飾器模式
定義
在不改變原來的結構和功能基礎上,動態裝飾一些針對特別場景所適用的方法或屬性,即添加一些新功能以增強它的某種能力
實現方法定義一組可變的策略類封裝具體算法,定義一組不變的環境類將請求委托給某一個策略類
使用場景原有方法維持不變,在原有方法上再掛載其他方法來滿足現有需求;函數的解耦,將函數拆分成多個可復用的函數,再將拆分出來的函數掛載到某個函數上,實現相同的效果但增強了復用性。比如多孔插座,機車改裝
實現
實例:
- const Man = function () {
- this.run = function () {
- console.log('跑步');
- };
- };
- const Decorator = function (old) {
- this.oldAbility = old.run;
- this.fly = function () {
- console.log('具備飛行能力');
- };
- this.newAbility = function () {
- this.oldAbility();
- this.fly();
- };
- };
- const man = new Man();
- const superMan = new Decorator(man);
- superMan.fly(); // 具備飛行能力
代理模式
定義
代理模式給某一個對象提供一個代理對象,并由代理對象控制對原對象的引用。通俗的來講代理模式就是我們生活中常見的中介。
實現方法定義一個委托者和一個代理,需要委托的事情在代理中完成
使用場景在某些情況下,一個客戶類不想或者不能直接引用一個委托對象,而代理類對象可以在客戶類和委托對象之間起到中介的作用。代理可以幫客戶過濾掉一些請求并且把一些開銷大的對象,延遲到真正需要它時才創建。中介購車、代購、課代表替老師收作業
實現
實例:
- class Letter {
- constructor(name) {
- this.name = name;
- }
- }
- // 暗戀人小明
- let XiaoMing = {
- name: '小明',
- sendLetter(target) {
- target.receiveLetter(this.name);
- },
- };
- // 代理小華
- let xiaoHua = {
- receiveLetter(customer) {
- // 當然要等小紅好心情時才送情書,也在送情書也才創建情書
- XiaoHong.listenGoodMood(() => {
- XiaoHong.receiveLetter(new Letter(customer + '的情書'));
- });
- },
- };
- // 心儀對象小紅
- let XiaoHong = {
- name: '小紅',
- receiveLetter(letter) {
- console.log(this.name + '收到:' + letter.name);
- },
- listenGoodMood(fn) {
- setTimeout(() => {
- fn();
- }, 1000);
- },
- };
- XiaoMing.sendLetter(xiaoHua); //小紅收到:小明的情書
Proxy 是 ES6 提供的專門以代理角色出現的代理器,Vue 3.0 的響應式數據部分棄用了 Object.defineProperty,使用 Proxy 來代替它。
- var proxy = new Proxy(target, handler);
現在用Proxy模擬一下另一種場景:為了保護不及格的同學,課代表拿到全班成績單后只會公示及格人的成績。對考分有疑問的考生,復議后新分數比以前大10分才有權利去更新成績
- const scoreList = { wang: 90, li: 60, wu: 100 };
- const yyProxy = new Proxy(scoreList, {
- get: function (scoreList, name) {
- if (scoreList[name] > 69) {
- console.log('輸出成績');
- return scoreList[name];
- } else {
- console.log('不及格的成績無法公示');
- }
- },
- set: function (scoreList, name, val) {
- if (val - scoreList[name] > 10) {
- console.log('修改成績');
- scoreList[name] = val;
- } else {
- console.log('無法修改成績');
- }
- },
- });
- yyProxy['wang'] = 98; //無法修改成績
- yyProxy['li'] = 80; //修改成績
總結
我曾經以為設計模式是瘋狂的,遙遠的軟件開發指南。然后我發現我一直在使用它們!我介紹的一些模式已在許多應用程序中使用。但歸根結底,它們只是理論而已。作為開發人員,是否使用取決于使用后是否使代碼邏輯更易于實現和維護。