摘要:在類似于這樣的面向對象語言中,抽象類的使用在這個設計模式中非常重要。假設系統中存在大量類似的對象而導致內存消耗過高,享元模式就非常有用了。享元模式包含兩種狀態即屬性內部狀態存儲于對象內部。不過與享元模式不同的是它不會區分內部狀態和外部狀態。
模式8-模版方法模式
模版方法模式是一種基于繼承的設計模式。主要由兩部分構成:
抽象父類:包含子類的算法框架和一些通用的具體方法;
具體實現的子類: 包含對于父類中抽象方法的實現,繼承父類的整個算法實現方法,并且可以重寫父類中的方法。
在類似于java這樣的面向對象語言中,抽象類的使用在這個設計模式中非常重要。因為在編譯的時候會對繼承抽象類的子類進行檢測,要求必須要對抽象方法進行實現。然而在javascript中沒有類型檢查,所以要保證子類實現了所有抽象父類的抽象方法,可以在運行時進行檢測,即讓抽象方法拋出錯誤。
示例:
var Beverage = function() {} Beverage.prototype.boilWater = function(){ console.log("boil water"); } Beverage.prototype.brew = function (){ throw new Error("you must define function brew"); } Beverage.prototype.pourInCup = function (){ throw new Error("you must define function pourInCup"); } Beverage.prototype.addCondiments = function (){ throw new Error("you must define function addCondiments"); } Beverage.prototype.customerWantsCondiments = function(){ throw new Error("you must define function customerWantsCondiments"); } //泡飲料的順序和步驟是定的,算是一個子類通用的算法 Beverage.prototype.init = function(){ this.boilWater(); this.brew(); this.pourInCup(); if(this.customerWantsCondiments){ this.addCondiments(); } }; var Tea = function(){}; Tea.prototype = new Beverage(); Tea.prototype.brew = function(){ console.log("brew-up"); } Tea.prototype.pourInCup = function(){ console.log("pour tea"); } Tea.prototype.addCondiments = function(){ console.log("add sugar and milk"); } Tea.prototype.customerWantsCondiments = function() { return window.confirm("Do you need condiments?"); } var tea = new Tea(); tea.init(); var Coffee = function(){}; Coffee.prototype = new Beverage(); Coffee.prototype.brew = function(){ console.log("brew coffee"); } Coffee.prototype.pourInCup = function(){ console.log("pour coffee"); } Coffee.prototype.addCondiments = function(){ console.log("add lemon"); } Coffee.prototype.customerWantsCondiments = function() { return window.confirm("Do you need condiments?"); } var coffee = new Coffee(); coffee.init();
這個設計模式是有利于系統的拓展的,并且符合開放-封閉原則。另外,我們在js中不一定非要使用繼承的方式來完成這個設計模式,也可以通過傳入高階函數作為參數來實現,用以替代父類中的抽象函數。
模式9-享元模式
享元模式是為了優化性能而存在的。假設系統中存在大量類似的對象而導致內存消耗過高,享元模式就非常有用了。
享元模式包含兩種狀態(即屬性):
內部狀態:
-存儲于對象內部。 -可以被一些對象共享。 -獨立于具體的場景,通常不會改變。
外部狀態:
取決于具體的場景,并根據場景而變化,外部狀態不能被共享。
剝離了外部狀態的對象成為共享對象,外部狀態在必要時被傳入共享對象來組裝成一個完整的對象。系統中可能存在的最大享元對象個數等于不同內部狀態的組合數。
示例:
//這個享元只有一個內部狀態 var Model = function( sex ){ this.sex = sex; }; Model.prototype.takePhoto = function(){ console.log( "sex= " + this.sex + " underwear=" + this.underwear); }; //分別創建一個男模特對象和一個女模特對象: var maleModel = new Model( "male" ), femaleModel = new Model( "female" ); //給男模特依次穿上所有的男裝,并進行拍照: //注:這里我們是在需要的時候才傳入外部狀態 for ( var i = 1; i <= 50; i++ ){ maleModel.underwear = "underwear" + i; maleModel.takePhoto(); }; //同樣,給女模特依次穿上所有的女裝,并進行拍照: for ( var j = 1; j <= 50; j++ ){ femaleModel.underwear = "underwear" + j; femaleModel.takePhoto(); };
應用:
文件上傳,只根據上傳組件的不同來(使用工廠)新建uploader對象(只含有一個uploadType內部狀態,同樣uploadType的被共享),而文件信息等儲存在外部,只當需要(如刪除文件)時才(通過uploadManager)將外部狀態傳入內部。
對象池是另一種性能優化的方案,其思想是創建一個池子用來存放空閑對象,當需要使用該對象時從池子中取,如果沒有則創建,用完之后放回。不過與享元模式不同的是它不會區分內部狀態和外部狀態。
模式10-職責鏈模式
思想是將可能處理請求的對象連成一個鏈,請求者只需知道第一個對象,然后將請求沿著鏈依次傳遞直到遇到可以處理該請求的對象。這就使得請求發出著和請求接受者之間解耦,也就是說請求發出者不必知道哪個對象可以處理請求,避免了在一個函數中使用大量的if else判斷。
示例:
var handler1 = function(params){ if(params === true) { // check condition console.log("request solved by handler1"); } else { return false; // condition not valid } } var handler2 = function(params){ if(params === true) { // check condition console.log("request solved by handler2"); } else { return false; // condition not valid } } var handler3 = function(params){ if(params === true) { // check condition console.log("request solved by handler3"); } else { return false; // condition not valid } } var Chain = function( fn ){ this.fn = fn; this.successor = null; }; Chain.prototype.setNextSuccessor = function( successor ){ return this.successor = successor; }; Chain.prototype.passRequest = function(){ var ret = this.fn.apply( this, arguments ); if ( ret === false ){ return this.successor && this.successor.passRequest.apply( this.successor, arguments ); } return ret; }; //調用next函數可以手動傳遞請求,用于異步處理 Chain.prototype.next= function(){ return this.successor && this.successor.passRequest.apply( this.successor, arguments ); }; var chain1 = new Chain(handler1); var chain2 = new Chain(handler2); var chain3 = new Chain(handler3); //指定節點在職責鏈中的順序 chain1.setNextSuccessor(chain2); chain2.setNextSuccessor(chain3); //把請求傳遞給第一個節點: chain1.passRequest(someParams);
職責鏈模式使得各個鏈結點之間可以拆分重組,便于插入或刪除結點。并且請求不一定要從第一個結點開始。同時,要避免職責鏈過長帶來的性能問題。
另外,可以利用js的函數式特性將函數“鏈接”起來實現職責鏈模式,即為Function對象的原型添加after函數。
應用:
不同瀏覽器文件上傳控件的選擇,DOM事件冒泡等。
模式11-中介者模式
中介者模式是為了解除對象之間的強耦合關系,所有對象都通過中介者通信而不再相互引用。
示例:
假設一個網頁中幾個DOM元素的值共同決定一個按鈕的有效性,
例如input必須有效,select1和select2必須選擇
傳統的方法中,我們需要為這三個DOM元素添加值改變監聽函數,并且在每個監聽函數中都要堅持另外兩個DOM的值。這時,如果我們需要加入一個input2,就需要將所有其他DOM元素的監聽函數進行修改。
如果使用中介者模式,我們可以創建一個mediator對象,并且提供一個向其發送消息的借口。然后,我們為其他DOM創建的監聽函數中只需要向中介者發送一個消息并且把this作為參數傳遞告訴中介者是誰發的消息。而中介者的實現中,只需要對收到的不同消息進行處理即可,如果需要添加新的關聯DOM,也只需要稍稍修改mediator的代碼而不會需要修改其他DOM的監聽函數了。
總之,中介者模式使對象之間的網狀引用關系變成了一對多的關系,滿足一個對象盡可能少地了解其他對象的原則。但是該模式引入了中介者對象,也會造成一些內存消耗。
模式12-裝飾者模式
裝飾者模式用于動態地給對象增加職責。
為了保證在執行原函數和this指向發生改變,我們需要引入代理函數。代理函數的功能是在原函數執行之前或之后,執行另外的函數。
示例:
使用AOP,改變Function對象的原型
Function.prototype.before = function(beforefn){ var _self = this; return function(){ beforefn.apply(this, arguments); //確保this指向不變 this.apply(this, arguments); } } var func1 = function(){ alert("1"); } func1 = func1.before(function(){ alert("0"); }); func1(); //先輸出0再輸出1
不改變原型的做法:
var before = function(fn, beforefn){ return function() { beforefn.apply(this, arguments); fn.apply(this, arguments); } } func1 = before(func1, function(){alert("0")});
應用:
對于某些用戶操作(如單擊按鈕)數據統計上報,改變函數的arguments對象(如ajax傳數據時添加屬性),表單驗證和提交功能的分離等。
這個設計模式使得開發人員在開發框架時可以只考慮對象的穩定和基礎功能,其他需要添加的個性功能可以被動態添加。它不同于代理模式的是,代理模式的目的是為了控制對對象的訪問或添加一些功能,而裝飾者模式則是為了為對象動態添加功能,并且通常會形成一條長長的裝飾鏈。
值得注意的是,裝飾者模式返回的是一個新的函數,因此原函數上的屬性會消失。同時,裝飾鏈也疊加了函數的作用域,過長則會對性能產生影響。
模式13-狀態模式
狀態模式使得一個對象在其狀態改變時改變其行為。也就是說,在不同狀態下調用同一個名字的函數其函數功能是不同的。
在狀態模式中,一般有兩類對象:context和狀態類。context構造函數中應該實例化所有的狀態類并作為context的屬性,以便context調用狀態類中的方法。而狀態類的構造函數應該以context作為參數,以便可以調用context中改變其state值的接口來對其進行賦值。
例如一個擁有不同光強度的燈的控制代碼示例:
var Light = function(){ this.offLightState = new OffLightState( this ); // 持有狀態對象的引用 this.weakLightState = new WeakLightState( this ); this.strongLightState = new StrongLightState( this ); this.superStrongLightState = new SuperStrongLightState( this ); this.button = null; }; Light.prototype.init = function(){ var button = document.createElement( "button" ), self = this; this.button = document.body.appendChild( button ); this.button.innerHTML = "開關"; this.currState = this.offLightState; this.button.onclick = function(){ self.currState.buttonWasPressed(); } }; //接下來就要定義各種狀態類 var OffLightState = function( light ){ this.light = light; }; OffLightState.prototype.buttonWasPressed = function(){ console.log( "弱光" ); this.light.setState( this.light.weakLightState ); }; // 其他狀態類似,都需要定義一個buttonWasPressed方法,略
該設計模式優點是將對象的狀態跟對應的方法一起封裝在一個類里,使得狀態的管理如添加刪除等更easy。狀態模式與策略模式很相似,都是有context和一些策略/狀態類。不同點是策略類之間是平行的,用戶需要知道他們的不同來選擇調用,而狀態類之間的狀態轉換關系是早就規定好的,用戶也不必知道其內部不同點。
JavaScript版本的狀態機(在js中狀態類不一定需要通過類來創建,可以使用顯式的對象):
var Light = function(){ this.currState = FSM.off; // 設置當前狀態 this.button = null; }; Light.prototype.init = function(){ var button = document.createElement( "button" ), self = this; button.innerHTML = "已關燈"; this.button = document.body.appendChild( button ); this.button.onclick = function(){ self.currState.buttonWasPressed.call( self ); } }; var FSM = { off: { buttonWasPressed: function(){ console.log( "關燈" ); this.button.innerHTML = "下一次按我是開燈"; this.currState = FSM.on; } }, on: { buttonWasPressed: function(){ console.log( "開燈" ); this.button.innerHTML = "下一次按我是關燈"; this.currState = FSM.off; }; var light = new Light(); light.init();
模式14-適配器模式
適配器模式的作用就是轉換不兼容的接口。
示例:
var getStudents = function(){ //這個函數返回一個對象的數組 var arr = [ {"id": 0, "name": "LiLei" }, {"id": 1, "name": "HanMeimei" } ]; } var printStudents = function(fn){ var params = fn(); for(var i = 0; i < params.length; i++){ console.log(params[i].name + ":" + params[i].id); } } printStudents(getStudents); //倘若現在提供學生信息的函數變了,返回值類型也變了: var getStudentsObj = function () { var stu = { "0": "LiLei", "1": "HanMeimei" }; } //adaptor var stuAdapter = function(oldfn){ var newRes = {}; var stu = oldfn(); for(var i = 0; i < stu.length; i++) { newRes[stu[i].id] = stu[i].name; } return function(){ return newRes; } } printStudent(stuAdapter(oldfn));
裝飾者模式與代理模式的結構與適配器模式很像,都是用一個對象來包裝另一個對象,但不同點仍然是他們的意圖。適配者模式只是轉換接口,不需要知道對象的具體實現。
P.s. 本文總結自《JavaScript設計模式與開發實踐》,曾探著
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/81086.html
摘要:在中使用在中使用腳本有兩種方式一種是嵌入在中的腳本,另一種是引入外部的腳本。二者并行執行,不會造成阻塞。字符編碼,較少使用。放置的位置將腳本放在標簽前是最佳的。小結把插入到頁面中要使用元素。延遲腳本總是按照指定它們的順序執行。 在 HTML 中使用 JavaScript 在html中使用JavaScript腳本有兩種方式一種是嵌入在HTML中的腳本,另一種是引入外部的腳本。兩種方式都離...
摘要:創建對象中,創建對象的基本模式有三種。因此,在設計構造函數時,需要進行慎重考慮。因此在中,這種問題被稱作繼承破壞封裝。靜態成員每個只有一份,直接通過類對象進行訪問。 什么是封裝 找工作時一些公司給了offer后我就想知道真正拿到手的是多少,畢竟賦稅繁重。但各種稅也好,五險一金也好我實在是弄不清楚,于是我就會在網上的一些稅后收入計算器上進行計算,只需要填寫一些基本信息,比如稅前收入,所...
摘要:元素向頁面中插入的主要方法就是使用元素。這個屬性的用途是表明腳本在執行時不會影響頁面的構造。因此,在元素中設置屬性,相當于告訴瀏覽器立即下載,但延遲執行。混雜模式會讓的行為與包含非標準特性的相同,而標準模式則讓的行為更接近標準行為。 元素 向html頁面中插入js的主要方法就是使用元素。使用元素的方式有兩種:直接在頁面中嵌入js代碼和包含外部js文件。直接在頁面中嵌入js代碼如下: ...
摘要:構造函數對于被實例化的,我們稱之為構造函數,及使用關鍵字調用的,對于它們來說,會被改變,指向實例。上栗子全局賦上屬性通過關鍵字創建實例,改變函數內部指向注解通過這個栗子,我們可以看出,通過創建構造函數的實例,使得的指向改變,指向了實例本身。 用栗子說this Bug年年有,今年特別多 對于JavaScript這么靈活的語言來說,少了this怎么活! function ...
摘要:方法始終從前向后找參數接收兩個參數,第一個參數可以是一個對象或者一個字符串這個字符串不會轉換成正則表達式,第二個參數可以是一個字符串或者一個函數。要想替換所有子字符串,唯一的辦法就是提供一個正則表達式,而且要指定全局標志標志。 字符串的模式匹配方法 match() 參數:只接受一個參數,要么是一個正則表達式,要么是一個RegExp()對象。 返回:數組。數組中的第一項是與整個模式匹配的...
閱讀 2060·2021-11-22 13:52
閱讀 984·2021-11-17 09:33
閱讀 2716·2021-09-01 10:49
閱讀 2851·2019-08-30 15:53
閱讀 2663·2019-08-29 16:10
閱讀 2437·2019-08-29 11:31
閱讀 1356·2019-08-26 11:40
閱讀 1872·2019-08-26 10:59