Javascript設計模式之裝飾者模式詳解篇

来源:http://www.cnblogs.com/yuanbo88/archive/2017/01/16/6290477.html
-Advertisement-
Play Games

一、前言: 裝飾者模式(Decorator Pattern):在不改變原類和繼承的情況下動態擴展對象功能,通過包裝一個對象來實現一個新的具有原對象相同介面的新的對象。 裝飾者模式的特點: 1. 在不改變原對象的原本結構的情況下進行功能添加。 2. 裝飾對象和原對象具有相同的介面,可以使客戶以與原對象 ...


一、前言:

裝飾者模式(Decorator Pattern):在不改變原類和繼承的情況下動態擴展對象功能,通過包裝一個對象來實現一個新的具有原對象相同介面的新的對象。

裝飾者模式的特點:
1. 在不改變原對象的原本結構的情況下進行功能添加。
2. 裝飾對象和原對象具有相同的介面,可以使客戶以與原對象相同的方式使用裝飾對象。
3. 裝飾對象中包含原對象的引用,即裝飾對象是真正的原對象經過包裝後的對象。

 

二、Javascript裝飾者模式詳解:

描述:
裝飾者模式中,可以在運行時動態添加附加功能到對象中。當處理靜態類時,這可能是一個挑戰。在Javascript中,由於對象是可變的,因此,添加功能到對象中的過程本身並不是問題。
裝飾者模式的一個比較方便的特征在於其預期行為的可定製和可配置特性。可以從僅具有一些基本功能的普通對象開始,然後從可用裝飾資源池中選擇需要用於增強普通對象的哪些功能,並且按照順序進行裝飾,尤其是當裝飾順序很重要的時候。
實現裝飾者模式的其中一個方法是使得每個裝飾者成為一個對象,並且該對象包含了應該被重載的方法。每個裝飾者實際上繼承了目前已經被前一個裝飾者進行增強後的對象。每個裝飾方法在“繼承的對象”上調用了同樣的方法並獲取其值,此外它還繼續執行了一些操作。

先上實例1:

		//需要裝飾的類(函數)
		function Macbook() {
		    this.cost = function () {
		        return 1000;
		    };
		}
		
		//計算商品的包裝費
		function PackagingFee(macbook) {
		    this.cost = function () {
		        return macbook.cost() + 75;
		    };
		}
		
		//計算商品的運費
		function Freight(macbook) {
		    this.cost = function () {
		        return macbook.cost() + 300;
		    };
		}
		
		//計算商品的保險費用
		function Insurance(macbook) {
		    this.cost = function () {
		        return macbook.cost() + 250;
		    };
		}

		// 用法
		var myMacbook = new Insurance(new Freight(new PackagingFee(new Macbook())));
		console.log(myMacbook.cost());//1625
	

我們簡單的分析下上面的代碼,上面的代碼中,一共定義了四個函數(其中一個需要修飾的函數,三個用於修飾的函數)。
然後,聲明一個變數myMacbook指向new出來的Insurance對象,Insurance對象的形參指向new出來的Freight對象,Freight對象的形參指向new出來的PackagingFee對象,PackagingFee對象的形參指向new出來的Macbook對象。
接下來,調用myMacbook的cost方法。從上面的分析,我們可以得出 myMacbook.cost()的值等於(Freight對象的cost方法+250),Freight對象的cost方法等於(PackagingFee對象的cost方法+300),PackagingFee對象的cost方法等於(Macbook對象的cost方法+75)。
所以最終的結果是:myMacbook.cost()的值 = 250 + (300 + (75 + 1000)) = 1625。

		// 用法
		var myMacbook = new Insurance(new Freight(new PackagingFee(new Macbook())));
		console.log(myMacbook.cost());//1625

		//上面的代碼等價於下麵拆分後的代碼,或許拆分後代碼你更能看出前後的邏輯性
		var macbook = new Macbook();
		var package = new PackagingFee(macbook);
		var freight = new Freight(package);
		var myMacbook = new Insurance(freight);

		//當然,如果你不想聲明這麼多變數(macbook、package、freight),只用一個變數也是可以的
		var macbook = new Macbook();
		macbook = new PackagingFee(macbook);
		macbook = new Freight(macbook);
		var myMacbook = new Insurance(macbook);
	

再看看實例2:

		function ConcreteClass() {
		    this.performTask = function () {
		        this.preTask();
		        console.log('doing something');
		        this.postTask();
		    };
		}

		function AbstractDecorator(decorated) {
		    this.performTask = function () {
		        decorated.performTask();
		    };
		}

		function ConcreteDecoratorClass(decorated) {
		    this.base = AbstractDecorator;
		    this.base(decorated);// add performTask method

		    decorated.preTask = function () {
		        console.log('pre-calling..');
		    };

		    decorated.postTask = function () {
		        console.log('post-calling..');
		    };
		}

		var concrete = new ConcreteClass();
		var decorator1 = new ConcreteDecoratorClass(concrete);
		decorator1.performTask();
		//pre-calling..
		//doing something
		//post-calling..
	
實例2實際上和實例1是非常類似的,我們來簡單分析下吧。首先,實例2中定義了三個函數,然後聲明瞭兩個變數concrete和decorator1,最後調用了decorator1的performTask方法。
粗看一眼,ConcreteDecoratorClass裡面好像並沒有performTask方法。我們先來分析下麵的兩行代碼:
			var concrete = new ConcreteClass(); //聲明一個變數concrete指向new出來的ConcreteClass對象
			var decorator1 = new ConcreteDecoratorClass(concrete); //聲明一個變數decorator1指向new出來的ConcreteDecoratorClass對象,並傳入變數concrete作為形參
		
然後,我們再來逐行分析下ConcreteDecoratorClass函數裡面的代碼:
			this.base = AbstractDecorator; //定義一個當前對象(decorator1)的base屬性,並指向函數AbstractDecorator
			this.base(decorated); //調用base屬性指向的函數,也就是調用AbstractDecorator函數,同時傳入形參decorated,形參decorated指向new出來的ConcreteClass對象
		
說到這裡,好像還是沒有分析出ConcreteDecoratorClass函數裡面有performTask方法,重點是看 "this"
ConcreteDecoratorClass函數中的this指向new出來的ConcreteDecoratorClass對象(也就是和decorator1指向同一個對象);
AbstractDecorator函數裡面的this關鍵是看哪個對象來調用這個函數,this就指向哪個對象(從代碼 “this.base = AbstractDecorator; this.base(decorated);” 中我們可以看出是new出來的ConcreteDecoratorClass對象在調用AbstractDecorator函數),所以AbstractDecorator函數裡面的this指向new出來的ConcreteDecoratorClass對象(也和decorator1指向同一個對象)。
總結下來,我們會發現,在上面的代碼中,不管是ConcreteDecoratorClass函數裡面的this,還是AbstractDecorator函數裡面的this,都指向new出來的ConcreteDecoratorClass對象

所以,當我們執行decorator1.performTask()時,它會繼續執行匿名函數中的代碼(decorated.performTask();),匿名函數中的decorated形參指向new出來的ConcreteClass對象,並執行該對象的performTask方法。

最後看看實例3:

		var tree = {};
		tree.decorate = function () {
		    console.log('Make sure the tree won\'t fall');
		};

		tree.getDecorator = function (deco) {
		    tree[deco].prototype = this;
		    return new tree[deco];
		};

		tree.RedApples = function () {
		    this.decorate = function () {
		        this.RedApples.prototype.decorate(); // 第7步:先執行原型(這時候是Angel了)的decorate方法
		        console.log('Add some red apples'); // 第8步 再輸出 red
		        // 將這2步作為RedApples的decorate方法
		    }
		};

		tree.BlueApples = function () {
		    this.decorate = function () {
		        this.BlueApples.prototype.decorate(); // 第1步:先執行原型的decorate方法,也就是tree.decorate()
		        console.log('Put on some blue apples'); // 第2步 再輸出blue
		        // 將這2步作為BlueApples的decorate方法
		    }
		};

		tree.Angel = function () {
		    this.decorate = function () {
		        this.Angel.prototype.decorate(); // 第4步:先執行原型(這時候是BlueApples了)的decorate方法
		        console.log('An angel on the top'); // 第5步 再輸出angel
		        // 將這2步作為Angel的decorate方法
		    }
		};

		tree = tree.getDecorator('BlueApples'); // 第3步:將BlueApples對象賦給tree,這時候父原型里的getDecorator依然可用
		tree = tree.getDecorator('Angel'); // 第6步:將Angel對象賦給tree,這時候父原型的父原型里的getDecorator依然可用
		tree = tree.getDecorator('RedApples'); // 第9步:將RedApples對象賦給tree

		tree.decorate(); // 第10步:執行RedApples對象的decorate方法
		//Make sure the tree won't fall
		//Add blue apples
		//An angel on the top
		//Put on some red apples
	
實例3看起來很複雜,實際上分析邏輯還是和前面兩個實例一樣,我們可以看出實例3中一共聲明瞭5個函數表達式。我們重點分析下下麵的代碼:
			//tree.getDecorator('BlueApples')返回new出來的tree.BlueApples的實例對象,並將該對象賦值給空的tree對象
			tree = tree.getDecorator('BlueApples'); //new出來的tree.BlueApples的實例對象的原型指向 --> 空對象tree

			//tree.getDecorator('Angel')返回new出來的tree.Angel的實例對象(這行代碼中的第二個tree已經是上面一行代碼運行結果後的tree.BlueApples的實例對象)
			tree = tree.getDecorator('Angel'); //new出來的tree.Angel的實例對象的原型指向 --> tree.BlueApples的實例對象

			//tree.getDecorator('RedApples')返回new出來的tree.RedApples的實例對象(這行代碼中的第二個tree已經是上面一行代碼運行結果後的tree.Angel的實例對象)
			tree = tree.getDecorator('RedApples'); //new出來的tree.RedApples的實例對象的原型指向 --> tree.Angel的實例對象
			
			//調用tree.decorate(),這裡的tree已經是new出來的tree.RedApples的實例對象了。
			//tree.RedApples的實例對象的decorate屬性方法裡面的第一行代碼是 “this.RedApples.prototype.decorate()”
			//結合上面的分析可以得出以下的原型鏈結構:
			//this.RedApples.prototype --> tree.Angel;
			//tree.Angel.prototype --> tree.BlueApples;
			//tree.BlueApples.prototype --> 空對象tree
			tree.decorate();
		

分析到這裡,就不難知道最後的輸出結果了。

 

三、其他:

我們可以看出本文章中的裝飾者模式案例中用了很多this,對this不太瞭解的朋友可以移步到 《你真的懂javascript中的 “this” 嗎?》
本文案例建議複製下來逐行分析,趕緊行動起來吧!

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 今兒與一群友討論vue相關問題讓我思量極深,1.我們是否在爭對性解決問題或者說是幫助別人;2.我們是否在炫耀自己的技能。以下是被戲劇化的對白: "群友":最近按照vue官網示例學習了一周,leader要我回報下成果"sam(本人)":給他擼一個帶vue的界面就行了撒"群友":擼一個界面? 能給一個示 ...
  • 首先先介紹Node Node是js的運行環境, 所謂“運行環境(平臺)”有兩層意思: + 首先,JavaScript 語言通過 Node 在伺服器運行,在這個意義上,Node 有點像 JavaScript 虛擬機; + 其次,Node 提供大量工具庫,使得 JavaScript 語言與操作系統互動( ...
  • 做前端已經一年了,開發中換過很多開發工具,遇到bug到處求解,以及自學時到處找相關文章及教程,所以經過這麼多的風波,我總結了一些對大家有幫助的網站,主題也將長期更新。 資源網站篇 "CSDN" :全球最大中文IT社區,為IT專業技術人員提供最全面的信息傳播和服務平臺 "伯樂線上" :專業的IT互聯網 ...
  • 咳咳咳咳,感冒了感冒了,鼻塞,藍瘦啊!嘴巴也開裂,哎,心疼自己。想到這是第三隻唇膏了!只怪,放蕩不倔愛自由, 行駛在冷風路上麽,北風那個吹啊吹啊吹啊,好了,發神經發完了,接下來進入正題,嚴肅臉。(字數150應該湊夠了。) js鏈式運動 代碼如下 : 今天為什麼把css的代碼也放上來呢? 只是手誤而已 ...
  • 事件與事件流 用戶在點擊網頁上某一按鈕時,瀏覽器會想辦法接收這一操作,如何接收呢? 瀏覽器會為點擊操作劃定若幹個範圍,從小到大依次是按鈕本身,包含這個按鈕的元素, 最後就是整個頁面。這一操作稱為事件,而接收事件的次序稱為事件流。 事件流總共有兩種形式:事件冒泡,事件捕獲。 事件冒泡 事件冒泡就是從小 ...
  • <meta content="width=device-width,initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport"/> 1. width=device-width width:可視區域的寬度,值可為數字或關 ...
  • 一、前言: 我們知道 “this” 是javascript語言的一個關鍵字,在編寫javascript代碼的時候,經常會見到或者用到它。 但是,有一部分開發朋友,對 “this” 一知半解,下麵我們就一起來探討學習下javascript中 “this” 的具體含義吧! 二、This總結: This指 ...
  • jQuery(function(){});jQuery().ready(function(){}); 綁定點擊事件: jQuery('#temp').click(function() {}); $(document).ready(function(){ $("p").click(function()... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...