JavaShuo
欄目
標籤
[書籍精讀]《JavaScript設計模式與開發實踐》精讀筆記分享
時間 2020-05-15
標籤
書籍
精讀
javascript
設計
模式
開發
實踐
筆記
分享
欄目
JavaScript
简体版
原文
原文鏈接
寫在前面
書籍介紹:本書在尊重《設計模式》原意的同時,針對JavaScript語言特性全面介紹了更適合JavaScript程序員的了16個經常使用的設計模式,講解了JavaScript面向對象和函數式編程方面的基礎知識,介紹了面向對象的設計原則及其在設計模式中的體現,還分享了面向對象編程技巧和平常開發中的代碼重構。本書將教會你如何把經典的設計模式應用到JavaScript語言中,編寫出優美高效、結構化和可維護的代碼。
個人簡評:這本書主要圍繞JavaScript中的一些設計模式和設計原則,每種模式的講解都帶有生活實例,恰當貼切,比較容易懂。不過,在此特地建議一下,有必定的編程經驗和項目經歷後再讀設計模式方面的書。
!!文末有pdf書籍、筆記思惟導圖、隨書代碼打包下載地址,須要請自取!閱讀「書籍精讀系列」全部筆記,請移步:
推薦收藏-JavaScript書籍精讀筆記系列導航
第一章 面向對象的JavaScript
JavaScript沒有提供傳統面嚮對象語言中的類式繼承,而是經過原型委託的方式來實現對象與對象之間的繼承。JavaScript也沒有在語言層面提供對抽象類和接口的支持
1.1.動態類型語言和鴨子類型
靜態類型語言在編譯時便肯定變量的類型,而動態類型語言的變量類型要到程序運行的時候,待變量被賦予某個值以後,纔會具備某種類型
鴨子類型的通俗說法是:若是它走起路來像鴨子,叫起來也是鴨子,那麼它就是鴨子
鴨子類型指導咱們只關注對象的行爲,而不關注對象自己,也就是關注HAS-A,而不是IS-A
在動態類型語言的面向對象設計中,鴨子類型的概念相當重要。利用鴨子類型的思想,咱們沒必要藉助超類型的幫助,就能輕鬆地在動態類型語言中實現一個原則:面向接口編程,而不是面向實現編程
1.2.多態
多態的實際含義是:同一操做做用於不一樣的對象上面,能夠產生不一樣的解釋和不一樣的執行結果。換句話說,給不一樣的對象發送同一個消息的時候,這些對象會根據這個消息分別給出不一樣的反饋
一段」多態「的JavaScript代碼:多態背後的思想是將「作什麼」和「誰去作以及怎麼去作」分離開,也就是將「不變的事物」與「可能改變的事物」分離開來
類型檢查和多態:靜態類型的面嚮對象語言一般被設計爲能夠向上轉型:當給一個類變量賦值時,這個變量的類型既能夠使用這個類自己,也能夠使用這個類的超類
JavaScript的多態:多態的思想其實是把「作什麼」和「誰去作」分離開來,要實現這一點,歸根結底先要消除類型之間的耦合關係;在JavaScript中,並不須要諸如向上轉型之類的技術取得多態的結果;
多態在面向對象程序設計中的做用:多態最根本的做用就是經過把過程化的條件分支語句轉換爲對象的多態性,從而消除這些條件分支語句
設計模式與多態:GoF所著的《設計模式》,徹底是從面向對象設計的角度出發的,經過對封裝、繼承、多態組合等技術的反覆使用,提煉出一些可重複使用的面向對象設計技巧
1.3.封裝
封裝的目的是將信息隱藏
封裝數據:但JavaScript並無提供對private、protected、public這些關鍵字的支持,咱們只能依賴變量的做用域來實現封裝特性,並且只能模擬出public和private這兩種封裝性
封裝實現:封裝的目的是將信息隱藏。封裝應該被視爲「任何形式的封裝」,也就是說,封裝不只僅是隱藏數據,還包括隱藏實現細節、設計細節以及隱藏對象的類型等;封裝使得對象之間的耦合變鬆散,對象之間只經過暴露的API接口來通訊;
封裝類型:封裝類型是靜態類型語言中一種重要的封裝方式。通常而言,封裝類型是經過抽象類和接口類進行的;JavaScript自己也是一門類型模糊的語言。在封裝類型方面,JavaScript沒有能力,也沒有必要作得更多;
封裝變化:《設計模式》一書中共概括總結了23種設計模式。從意圖上區分,這23種設計模式分別被劃分爲建立型模式、結構型模式和行爲型模式
1.4.原型模式和基於原型繼承的JavaScript對象
原型模式不單是一種設計模式,也被稱爲一種編程範性
使用克隆的原型模式:原型模式的實現關鍵,是語言自己是否提供了clone方法。ECMAScript5提供了Object.create方法,能夠用來克隆對象
克隆是建立對象的手段:但原型模式的真正目的並不是在於須要獲得一個如出一轍的對象,而是提供了一種便捷的方式去建立某個類型的對象,克隆只是建立這個對象的過程和手段
體驗Io語言:在JavaScript語言中不存在類的概念,對象也並不是從類中建立出來的,全部的JavaScript對象都是從某個對象上克隆而來的;JavaScript基於原型的面向對象系統參考了Self語言和Smalltalk語言;
原型編程範性的一些規則:Io語言和JavaScript語言同樣,基於原型鏈的委託機制就是原型鏈繼承的本質;原型編程中的一個重要特性,即當對象沒法響應某個請求時,會把該請求委託給它本身的原型;原型編程範型至少包括如下基本規則(全部的數據都是對象;要獲得一個對象,不是經過實例化類,而是找到一個對象做爲原型並克隆它;對象會記住它的原型;若是對象沒法響應某個請求,它會把這個請求委託給它本身的原型)
JavaScript中的原型繼承:事實上,JavaScript中的根對象是Object.prototype對象。Object.prototype對象是一個空的對象;JavaScript的函數既能夠做爲普通函數被調用,也能夠做爲構造器被調用。當使用new運算符來調用函數時,此時的函數就是一個構造器。用new運算符來建立對象的過程,實際上也只是先克隆Object.prototype對象,再進行一些其餘額外操做的過程;就JavaScript的真正實現來講,其實並不能說對象有原型,而只能說對象的構造器有原型。對於「對象把請求委託給它本身的原型」這句話,更好的說法是對象把請求委託給它的構造器的原型;雖然JavaScript的對象最初都是由Object.prototype對象克隆而來的,但對象構造器的原型並不只限於Object.prototype上,而是能夠動態指向其餘對象;留意一點,原型鏈並非無限長的;
原型模式是一種設計模式,也是一種編程泛型,它構成了JavaScript這門語言的根本
第二章 this、call、apply
2.1.this
跟別的語言截然不同的是,JavaScript的this老是指向一個對象,而具體指向哪一個對象是在運行時基於函數的執行環境動態綁定的,而非函數被聲明時的環境
this的指向大體能夠分爲如下4種(做爲對象的方法調用;做爲普通函數的調用;構造器調用;Function.prototype.call或Function.prototype.apply調用);
1.做爲對象的方法調用:this指向該對象
2.做爲普通函數調用:this指向全局對象;在ECMAScript5的strict模式下,這種狀況下的this已經被規定爲不會指向全局對象,而是undefined;
3.構造器調用:當用new運算符調用函數時,該函數總會返回一個對象,一般狀況下,構造器裏的this就指向返回的這個對象
4.Function.prototype.call或Function.prototype.apply調用(能夠動態的改變傳入函數的this;call和apply方法能很好的體現JavaScript的函數式語言特性,在JavaScript中,幾乎每一次編寫函數式語言風格的代碼,都離不開call和apply)
丟失的this:當用另外一個變量getName2來引用obj.getName,而且調用getName2時,此時是普通函數調用方式,this是指向全局window的,因此程序的執行結果是undefined
2.2.call和apply
ECMA3給Function的原型定義了兩個方法,它們是Function.prototype.call和Function.prototype.apply
call和apply的區別:區別僅在於傳入參數形式的不一樣;apply接受兩個參數,第一個參數指定了函數體內this對象的指向,第二個參數爲一個帶下標的集合;call傳入的參數數量不固定,第一個參數也是表明函數體內的this指向,從第二個參數開始日後,每一個參數被依次傳入函數;當調用一個函數時,JavaScript的解釋器並不會計較形參和實參在數量、類型以及順序上的區別,JavaScript的參數在內部就是用一個數組表示的。從這個意義上說,apply比call的使用率更高,咱們沒必要關心具體有多少參數被傳入函數;
call和apply的用途:1.改變this指向(最多見的用途);2.Function.prototype.bind(大部分高級瀏覽器都實現了內置的Function.prototype.bind,用來指定函數內部的this指向);3.借用其餘對象的方法;
第三章 閉包和高階函數
函數式語言的鼻祖是LISP,JavaScript在設計之初參考了LISP兩大方言之一的Scheme,引入了Lambda表達式、閉包、高階函數等特性
3.1.閉包
閉包的造成與變量的做用域以及變量的生存週期密切相關
變量的做用域:指變量的有效範圍
變量的生存週期:全局變量的生成周期是永久的,除非主動銷燬它;在函數內用var關鍵字聲明的局部變量,當退出函數時,這些局部變量就失去了價值,他們會隨着函數調用的結束而被銷燬;
閉包的更多做用:1.封裝變量(閉包能夠幫助把一些不須要暴露在全局的變量封裝成「私有變量」);2.延長局部變量的壽命;
閉包和麪向對象設計:在JavaScript語言的祖先Scheme語言中,甚至都沒有提供面向對象的原生設計,但能夠使用閉包來實現一個完整的面向對象系統
用閉包實現命令模式:命令模式的意圖是把請求封裝爲對象,從而分離請求的發起者和請求的接收者之間的耦合關係
閉包與內存管理:一種慫人聽聞的說法是閉包會形成內存泄露,因此要儘可能減小閉包的使用;局部變量原本應該在函數退出的時候被解除引用,但若是局部變量被封閉在閉包造成的環境中,那麼這個局部變量就能一直生存下去;在基於引用技術策略的垃圾回收機制中,若是兩個對象之間造成了循環引用,那麼這兩個對象都沒法被回收,但循環引用形成的內存泄露在本質上也不是閉包形成的;
3.2.高階函數
高階函數是指至少知足下列條件之一的函數:1.函數做爲參數傳遞;2.函數做爲返回值輸出;3.高階函數實現AOP;4.高階函數的其餘應用;
1.函數做爲參數傳遞:其中一個重要應用場景就是常見的回調函數;Array.prototype.sort接受一個函數做爲參數,這個函數裏面封裝了數組元素的排序規則;
2.函數做爲返回值輸出:函數看成返回值輸出的應用場景也許更多,也更能體現函數式編程的巧妙;1.判斷數據的類型,isType函數;2.getSingle,單例模式的例子;
3.高階函數實現AOP:AOP(面向切面編程)的主要做用是把一些跟核心業務邏輯模塊無關的功能抽離出來,這些跟業務邏輯無關的功能包括日誌統計、安全控制、異常處理等;在Java語言中,能夠經過反射和動態代理機制來實現AOP技術。而在JavaScript這種動態語言中,AOP的實現更加簡單,這是JavaScript與生俱來的能力;使用AOP的方式來給函數添加職責,也是JavaScript語言中一種很是特別和巧妙的裝飾者模式實現;
4.高階函數的其餘應用:1.currying,函數柯里化;2.uncurrying;3.函數節流;4.分時函數;5.惰性加載函數;
第四章 單例模式
單例模式的定義是:保證一個類僅有一個實例,並提供一個訪問它的全局訪問點
單例模式是一種經常使用的模式,有一些對象咱們每每只須要一個,好比線程池、全局緩存、瀏覽器中的window對象等
4.1.實現單例模式
要實現一個標準的單例模式並不複雜,無非是用一個變量來標誌當前是否已經爲某個類建立過對象,若是是,則在下一次獲取該類的實例時,直接返回以前建立的對象
4.2.透明的單例模式
實現一個「透明」的單例類,用戶從這個類中建立對象的時候,能夠像使用其餘任何普通類同樣
4.3.用代理實現單例模式
把負責管理單例的代碼移除出去
4.4.JavaScript中的單例模式
Douglas Crockford屢次把全局變量稱爲JavaScript中最糟糕的特性
在對JavaScript的創造者Brendan Eich的訪談中,他本人也認可全局變量是設計上的失誤,是在沒有足夠的時間思考一些東西的狀況下致使的結果
如下幾種方式能夠相對下降全局變量帶來的命名污染
1.使用命名空間:適當的使用命名空間,並不會杜絕全局變量,但能夠減小全局變量的數量
2.使用閉包封裝私有變量:把一些變量封裝在閉包的內部,只暴露一些接口跟外界通訊
4.5.惰性單例
惰性單例指的是在須要的時候才建立對象實例
以WebQQ的登陸浮窗爲例:能夠用一個變量來判斷是否已經建立過登陸浮窗
4.6.通用的惰性單例
上一節還有以下問題:違反單一職責原則的,建立對象和管理單例的邏輯都放在createLoginLayer對象內部;若是下次須要建立頁面中惟一的iframe、script等用來跨域請求數據,必須照抄一遍代碼;
把如何管理單例的邏輯從原來的代碼中抽離出來
4.7.小結
單例模式是一種簡單但很是實用的模式,特別是惰性單例技術,在合適的時候才建立對象,而且只建立惟一的一個
更奇妙的是,建立對象和管理單例的職責被分佈在兩個不一樣的方法,這兩個方法組合起來才具備單例模式的威力
第五章 策略模式
策略模式的定義是:定義一系列的算法,把它們一個個封裝起來,而且使它們能夠相互替換
5.1.使用策略模式計算獎金
以年終獎的計算爲例:1.最初if判斷的代碼實現;2.使用組合函數重構代碼;3.使用策略模式重構代碼;
策略模式指的是定義一系列的算法,把它們一個個封裝起來。將不變的部分和變化的部分分隔開始每一個設計模式的主題,策略模式也不例外,策略模式的目的就是將算法的使用與算法的實現分離開來
一個基於策略模式的程序至少由兩部分組成。第一個部分是一組策略類,策略類封裝了具體的算法,並負責具體的計算過程。第二個部分是環境類Context,Context接受客戶的請求,隨後把請求委託給某一個策略類
5.2.JavaScript版本的策略模式
實際上在JavaScript語言中,函數也是對象,因此更簡單和直接的作法是把strategy直接定義爲函數
5.3.多態在策略模式中的體現
5.4.使用策略模式實現緩動動畫
實現動畫效果的原理:動畫片是把一些差距不大的原畫以較快ide幀數播放,來達到視覺上的動畫效果;JavaScript中,能夠經過連續改變元素的某個CSS屬性,好比left、top、background-position來實現動畫效果;
思路和一些準備工做:須要提早記錄一些有用的信息
讓小球運動起來:
Animate.prototype.start = function(propertyName, endPos, duration, easing){}
5.5.更廣義的「算法」
從定義上看,策略模式就是用來封裝算法的。但若是把策略模式僅僅用來封裝算法,未免有一點大材小用
在實際開發中,咱們一般會把算法的含義擴散開來,是策略模式也能夠用來封裝一系列的「業務規則」
5.6.表單校驗
表單校驗的第一個版本:多個if判斷
用策略模式重構表單校驗:先建立了一個validator對象,而後經過validator.add方法,往validator對象中添加一些校驗規則
給某個文本輸入框添加多種檢驗規則
5.7.策略模式的優缺點
策略模式是一種經常使用且有效的設計模式,本章提供了計算獎金、緩動動畫、表單校驗這Sanger例子來加深對策略模式的理解
總結策略模式的一些優勢:策略模式利用組合、委託和多態等技術和思想,能夠有效避免多重條件選擇語句;策略模式提供了對開放-封閉原則的完美支持,將算法封裝在獨立的strategy中,使得它們易於切換、易於理解、易於擴展;策略模式中的算法也能夠複用在系統的其餘地方,從而避免許多重複的複製粘貼模式;在策略模式中利用組合和委託來讓Context擁有執行算法的能力,這yeshiva繼承的一種更輕便的替代方案;
5.8.一等函數對象與策略模式
實際上在JavaScript這種將函數做爲一等對象的語言裏,策略模式已經融入到了語言自己當中,咱們常常用高階函數來封裝不一樣的行爲,而且把它傳遞到另外一個函數中
5.9.小結
在JavaScript語言的策略模式中,策略類每每被函數所代替,這是策略模式就成爲一種「隱形」的模式
第六章 代理模式
代理模式是爲一個對象提供一個代用品或佔位符,以便控制對它的訪問
代理模式的關鍵是:當客戶不方便直接訪問一個對象那個或者不知足須要的時候,提供一個替身對象來控制對這個對象的訪問,客戶實際上訪問的是替身對象。替身對象對請求作出一些處理以後,再把請求轉交給本體對象
6.1.第一個例子-小明追MM的故事
讓小明和MM共同的朋友代爲送花
6.2.保護代理和虛擬代理
保護代理用於控制不一樣權限的對象對目標對象的訪問,但在JavaScript並不容易實現保護代理,由於咱們沒法判斷誰訪問了某個對象
虛擬代理是最經常使用的一種代理模式
6.3.虛擬代理實現圖片預加載
圖片預加載,經常使用的作法是先用一張loading圖片佔位,而後用異步的方式加載圖片,等圖片加載好了再把它填充到img節點
6.4.代理的意義
實際上咱們須要的只是給img節點設置src,預加載圖片只是一個錦上添花的功能
代理的做用在這裏體現出來,代理負責預加載圖片,預加載的操做完成以後,把請求從新交給本體MyImage
6.5.代理和本體接口的一致性
其中關鍵是代理對象和本體都對外提供了setSrc方法,在客戶看來,代理對象和本體是一致的,代理接手請求的過程對於用戶來講是透明的,用戶並不清楚代理和本體的區別
在Java等語言中,代理和本體都須要顯式地實現同一個接口,一方面接口保證了它們會擁有一樣的方法,另外一方面,面向接口編程迎合依賴倒置原則,經過接口進行向上轉型,從而避開編譯器的類型檢查,代理和本體未來能夠被替換使用
在JavaScript這種動態類型語言中,咱們有時經過鴨子類型來檢測代理和本體是否都實現了setSrc方法,另外大多數時候甚至乾脆不作檢測,所有依賴程序員的自覺性,這對於程序的健壯性是有影響的
6.6.虛擬代理合並HTTP請求
文件同步的功能
解決方案是,能夠經過一個代理函數來收集一段時間以內的請求,最後一次性發送給服務器
6.7.虛擬代理在惰性加載中的應用
miniConsole.js開源項目,但願在按下F12來主動喚出控制檯的時候進行加載
6.8.緩存代理
緩存代理能夠爲一些開銷大的運算結果提供暫時的存儲,在下次運算時,若是傳遞進來的參數跟以前一致,則能夠直接返回前面存儲的運算結果
緩存代理的例子-計算乘積
緩存代理用於ajax異步請求數據
常見的分頁的需求,同一頁的數據理論上只須要去後臺拉取一次,這些已經拉取到的數據在某個地方被緩存以後,下次再請求同一頁的時候,即可以直接使用以前的數據
6.9.用高階函數動態建立代理
經過傳入高階函數這種更加靈活的方式,能夠爲各類計算方法建立緩存代理
6.10其餘代理模式
代理模式的變體種類很是多:防火牆代理;遠程代理;保護代理;智能引用代理;寫時複製代理;
6.11.小結
代理模式包括許多小分類,在JavaScript開發中最多見的是虛擬代理和緩存代理
咱們在編寫業務代碼的時候,每每不須要去預先猜想是否須要使用代理模式,當真正發現不方便直接訪問某個對象的時候,再編寫代理也不遲
第七章 迭代器模式
迭代器模式 是指提供一種方法順序訪問一個聚合對象中的各個元素,而又不須要暴露該對象的內部表示
7.1.jQuery中的迭代器
迭代器模式無非就是循環訪問聚合對象中的各個元素
7.2.實現本身的迭代器
7.3.內部迭代器和外部迭代器
迭代器能夠分爲內部迭代器和外部迭代器,它們有各自的適用場景
在一些沒有閉包的語言中,內部迭代器自己的實現也至關複雜
外部迭代器必須顯式的請求迭代下一個元素
外部迭代器雖然調用方式相對複雜,但它的實用面更廣,也能知足更多變的需求
7.5.倒序迭代器
迭代器模式提供了循環訪問一個聚合對象中每一個元素的方法,但它沒有規定咱們以順序、倒序仍是中序遍歷聚合對象
7.6.停止迭代器
jQuery的each函數約定若是回調函數的執行結果返回false,則提早終止循環
7.7.迭代器模式的應用舉例
根據不一樣的瀏覽器獲取相應的上傳組件對象
7.8.小結
迭代器模式是一種相對簡單的模式,簡單到不少時候咱們都不認爲它是一種設計模式
第八章 發佈訂閱模式
發佈-訂閱模式又叫觀察者模式,它定義對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時,全部依賴於它的對象都將獲得通知
在JavaScript開發中,咱們通常用事件模型來替代傳統的發佈-訂閱模式
8.1.現實中的發佈-訂閱模式
售樓的例子:購房者的電話號碼都被記在售樓處的花名冊上,新樓盤推出的時候,售樓MM會翻開花名冊,遍歷上面的電話號碼,依次發送一條短信來通知他們
8.2.發佈-訂閱模式的做用
發佈-訂閱模式能夠普遍應用於異步編程中,這是一種替代傳遞迴調函數的方案
發佈-訂閱模式能夠取代對象之間硬編碼的通知機制,一個對象不用再顯式的調用另一個對象的某個接口
8.3.DOM事件
實際上,只要咱們曾經在DOM節點上面綁定過事件函數,那咱們就曾經使用過發佈-訂閱模式
還能夠隨意增長或者刪除訂閱者,增長任何訂閱者都不會影響發佈者代碼的編寫
8.4.自定義事件
如何一步步實現發佈-訂閱模式:首先要指定好誰充當發佈者(好比售樓處);而後給發佈者添加一個緩存列表,用於存放回調函數以便通知訂閱者(售樓處的花名冊);最後發佈消息的時候,發佈者會遍歷這個緩存列表,依次觸發裏面的訂閱者回調函數(遍歷花名冊,挨個發短信);
8.5.發佈-訂閱模式的通用實現
把發佈-訂閱的功能提取出來,放在一個單獨的對象內
8.7.真實的例子-網站登陸
網站裏有header頭部、nav導航、消息列表、購物車等模塊。這些模塊有一個共同的前提條件,就是必須先用Ajax異步請求獲取用戶的登陸信息
更重要的一點是,咱們不知道除了header頭部、nav導航、消息列表、購物車以外,未來還有哪些模塊須要使用這些用戶信息
用發佈-訂閱模式重寫後,對用戶信息感興趣的業務模塊將自行訂閱登陸成功的消息事件。當登陸成功時,登陸模塊只須要發佈登陸成功的消息,而業務方接受到消息以後,就會開始進行各自的業務處理,登陸模塊並不關心業務究竟要作什麼,也不想去了解它們的內部細節
8.8.全局的發佈-訂閱對象
買房子未必要親自去售樓處,咱們只要把訂閱的請求交給中介公司,而各大房產公司也只須要經過中介公司來發布房子消息。這樣一來,咱們不用關心消息是來自哪一個房產公司,咱們在乎的是可否順利收到消息
發佈-訂閱模式能夠用一個全局的Event對象來實現,訂閱者不須要了解消息來自哪一個發佈者,發佈者也不知道消息會推送給哪些訂閱者,Event做爲一個相似」中介者「的角色,把訂閱者和發佈者聯繫起來
8.9.模塊間通訊
要留意一個問題,模塊之間若是用了太多的全局發佈-訂閱模式來通訊,那麼模塊與模塊之間的聯繫就被隱藏到了後面。最終搞不清楚消息來自哪一個模塊,或者消息會流向哪些模塊,這會給咱們的維護帶來一些麻煩,也許某個模塊的做用就是暴露一些接口給其餘模塊調用
8.10.必須先訂閱再發布嗎
在某些狀況下,咱們須要先將這條消息保存下來,等到有對象來訂閱它的時候,再從新把消息發佈給訂閱者。就如同QQ中的離線消息同樣,離線消息被保存在服務器中,接收人下次登陸上線以後,能夠從新收到這條消息
爲了知足這個需求,咱們要創建一個存放離線事件的堆棧,當事件發佈的時候,若是此時尚未訂閱者來訂閱這個事件,咱們暫時把發佈事件的動做包裹在一個函數裏,這些包裝函數將被存入堆棧中,等到終於有對象來訂閱此事件的時候,咱們將遍歷堆棧而且依次執行這些包裝函數,也就是從新發布里面的事件
8.12.JavaScript實現發佈-訂閱模式的便利性
在Java中實現一個本身的發佈-訂閱模式,一般會把訂閱者對象自身當成引用傳入發佈者對象中,同時訂閱者對象還需提供一個名爲諸如update的方法,供發佈者對象在適合的時候調用
而在JavaScript中,咱們用註冊回調函數的形式來代替傳統的發佈-訂閱模式,顯得更加優雅和簡單
在JavaScript中,咱們無需去選擇使用推模型仍是拉模型。推模型是指事件發生時,發佈者一次性把全部更改的狀態和數據都推送給訂閱者。拉模型不一樣的地方是,發佈者僅僅通知訂閱者事件已經發生了,此外發布者需提供一些公開的接口供訂閱者來主動拉去數據
8.13.小結
發佈-訂閱模式的優勢很是明顯,一是時間上的解耦,而是對象之間的解耦
應用很是普遍,既能夠用在異步編程中,也能夠幫助咱們完成更鬆耦合的代碼編寫
缺點:建立訂閱者自己要消耗必定的時間和內存,並且當你訂閱一個消息後,也許此消息最後都未發生,但這個訂閱者會始終存在於內存中。另外,發佈訂閱模式雖然能夠弱化對象之間的聯繫,但若是過分使用的話,對象和對象之間的必要聯繫也將被深埋在背後,會致使程序難以跟蹤維護和理解
第九章 命令模式
9.1.命令模式的用途
命令模式是最簡單和優雅的模式之一,命令模式中的命令(command)指的是一個執行某些特定事情的指令
命令模式最多見的應用場景是:有時候須要向某些對象發送請求,可是並不知道請求的接收者是誰,也不知道被請求的操做是什麼。此時但願用一種鬆耦合的方式來設計程序,使得請求發送者和請求接收者可以消除彼此之間的耦合關係
9.2.命令模式的例子-菜單程序
在這裏運用命令模式的理由:點擊了按鈕以後,必須向某些負責具體行爲的對象發送請求,這些對象就是請求的接收者
設計模式的主題老是把不變的事務和變化的事物分離開來,命令模式也不例外
9.3.JavaScript中的命令模式
在面向對象設計中,命令模式的接收者被當成command對象的屬性保存起來,同時約定執行命令的操做command.execute方法
9.4.撤銷命令
命令模式的做用不只是封裝運算塊,並且能夠很方便地給命令對象增長撤銷操做
撤銷是命令模式裏一個很是有用的功能,試想一下開發一個圍棋程序的時候,咱們把每一步棋子的變化都封裝成命令,則能夠垂手可得的實現毀棋功能
9.6.命令隊列
命令對象的生命週期跟初始請求發生的時間無關,command對象的execute方法能夠在程序運行的任什麼時候刻執行,即便點擊按鈕的請求早已發生,但咱們的命令對象仍然是有生命的
9.7.宏命令
宏命令是命令模式與組合模式的聯用產物
9.9.小結
跟許多其餘語言不一樣,JavaScript能夠用高階函數很是方便地實現命令模式
命令模式在JavaScript語言中是一種隱形的模式
第十章 組合模式
組合模式就是用小的子對象來構建更大的對象,而這些小的子對象自己也許是由更小的」孫對象「構成的
10.1.回顧宏命令
宏命令對象包含了一組具體的子命令對象,無論是宏命令對象,仍是子命令對象,都有一個execute方法負責執行命令
在macroCommand的execute方法裏,並不執行真正的操做,而是遍歷它所包含的葉對象,把真正的execute請求委託給這些葉對象
10.2.組合模式的用途
組合模式將對象組合成樹形結構,以表示」部分-總體「的層次結構。除了用來表示樹形結構以外,組合模式的另外一個好吃是經過對象的多態性表現,使得用戶對單個對象和組合對象的使用具備一致性
10.3.請求在樹中傳遞的過程
在組合模式中,請求在樹中傳遞的過程老是遵循一種邏輯
做爲客戶,只須要關心樹最頂層的組合對象,客戶只須要請求這個組合對象,請求便會沿着樹往下傳遞,依次到達全部的葉對象
10.4.更強大的宏命令
基本對象能夠被組合成更復雜的組合對象,組合對象又能夠被組合,這樣不斷遞歸下去,這棵樹的結構能夠支持任意多的複雜度
10.5.抽象類在組合模式中的做用
組合模式最大的優勢在於能夠一致地對待組合對象和基本對象。客戶不須要知道當前處理的宏命令仍是普通命令,只要它是一個命令,而且有execute方法,這個命令就能夠被添加到樹中
在JavaScript這種動態類型語言中,對象的多態性是與生俱來的,也沒有編譯器去檢查變量的類型,因此咱們一般不會去模擬一個」怪異「的抽象類,在JavaScript中實現組合模式的難點在於要保證組合對象和葉對象擁有一樣的方法,這一般須要用鴨子類型的思想對它們進行接口檢查
在JavaScript中實現組合模式,看起來缺少一些嚴謹性,咱們的代碼算不上安全,但能更快速和自由地開發,這既是JavaScript的缺點,也是它的優勢
10.6.透明性帶來的安全問題
組合模式的透明性使得發起請求的客戶不用去顧忌樹中組合對象和葉對象的區別,但它們在本質上有區別的
10.7.組合模式的例子-掃描文件夾
文件夾和文件之間的關係,很是適合用組合模式來描述。文件夾裏既能夠包含文件,又能夠包含其餘文件夾,最終可能組合成一棵樹
在添加一批文件的操做過程當中,客戶不用分辨它們究竟是文件仍是文件夾。新增長的文件和文件夾可以很容易地添加到原來的樹結構中,和樹裏已有的對象一塊兒工做
10.8.一些值得注意的地方
1.組合模式不是父子關係:組合模式是一種HAS-A(聚合)的關係,而不是IS-A
2.對葉對象操做的一致性:組合模式除了要求組合對象和葉對象擁有相同的接口以外,還有一個必要條件,就是對一組葉對象的操做必須具備一致性
3.雙向映射關係
4.用職責鏈模式提升組合模式性能
10.10.什麼時候使用組合模式
適用於如下兩種狀況:表示對象的部分-總體層次結構;客戶但願統一對待樹中的全部對象;
10.11.小結
組合模式可讓咱們使用樹形方式建立對象的結構。咱們能夠把相同的操做應用在組合對象和單個對象上
組合模式並非完美的,它可能會產生一個這樣的系統:系統中的每一個對象看起來都與其餘對象差很少/它們的區別只有在運行的時候纔會顯現出來,這會使代碼難以理解
第十一章 模板方法模式
一種基於繼承的設計模式
11.1.模板方法模式的定義和組成
模板方法模式是一種只需使用繼承就能夠實現的很是簡單的模式
模板方法模式由兩部分結構組成,第一部分是抽象父類,第二部分是具體的實現子類。
一般在抽象父類中封裝了子類的算法框架,包括實現一些公共方法以及封裝子類中全部方法的執行順序。子類經過繼承這個抽象類,也繼承了整個算法結構,而且能夠選擇重寫父類的方法
11.2.第一個例子-Coffee or Tea
先泡一杯咖啡
泡一壺茶
3.分離出共同點:都能整理爲下面四步:1.把水煮沸;2.用沸水沖泡飲料;3.把飲料倒進杯子;4.加調料
4.建立Coffee子類和Tea子類:Beverage.prototype.init被稱爲模板方法的緣由是,該方法中封裝了子類的算法框架,它做爲一個算法的模板,指導子類以何種順序去執行哪些方法
11.3.抽象類
首先要說明的是,模板方法是一種嚴重依賴抽象類的設計模式
抽象類的做用
抽象方法和具體方法
用Java實現Coffee or Tea的例子
JavaScript沒有抽象類的缺點和解決方案:JavaScript並無從語法層面提供對抽象類的支持。抽象類的第一個做用是隱藏對象的具體類型,因爲JavaScript是一門「類型模糊」的語言,因此隱藏對象的類型在JavaScript中並不重要;另外一方面,當咱們在JavaScript中使用原型繼承來模擬傳統的類式繼承時,並無編譯器幫助咱們進行任何形式的檢查,咱們也沒有辦法保證子類會重寫父類中的「抽象方法」;在Java中編譯器會保證子類會重寫父類中德抽象方法,但在JavaScript中卻沒有進行這些檢查工做;兩種變通的解決方案(第一種方案是用鴨子類型來模擬接口檢查,以便確保子類中確實重寫了父類的方法;第2種方案是讓Beverage.prototype.brew等方法直接拋出一個異常)
11.4.模板方法模式的使用場景
模板方法模式常被架構師用於搭建項目的框架,架構師定好了框架的骨架,程序員繼承框架的結構以後,負責往裏面填空
11.5.鉤子方法
鉤子方法(hook)能夠用來解決這個問題(讓子類不受某個步驟的約束),放置鉤子是隔離變化的一種常見手段。咱們在父類中容易變化的地方放置鉤子,鉤子能夠有一個默認的實現,究竟要不要」掛鉤「,這由子類自行決定。鉤子方法的返回結果決定了模板方法後面部分的執行步驟,也就是程序接下來的走向,這樣一來,程序就擁有了變化的可能
11.6.好萊塢原則
好萊塢原則:不要來找我,我會給你打電話
在這一原則的指導下,咱們容許底層組件將本身掛鉤到高層組件中,而高層組件會決定何時,以何種方式去使用這些底層組件,高層組件對待底層組件的方式,跟演藝公司對待新人演員同樣,都是「別調用咱們,咱們會調用你」
當咱們用模板方法模式編寫一個程序時,就意味着子類放棄對本身的控制權,而是改用父類通知子類,哪些方法應該在何時被調用
好萊塢原則還經常應用於其餘模式和場景,例如發佈-訂閱模式和回調函數
11.7.真的須要」繼承「嗎
模板方法模式是爲數很少的基於繼承的設計模式,但JavaScript語言實際上沒有提供真正的類式繼承,繼承是經過對象與對象之間的委託來實現的
11.8.小結
模板方法模式是一種典型的經過封裝變化提升系統擴展性的設計模式
在傳統的面嚮對象語言中,一個運用了模板方法模式的程序中,子類的方法種類和執行順序都是不變的,因此咱們把這部分邏輯抽象到父類的模板方法裏面
而子類的方法具體怎麼實現則是可變的,因而咱們把這部分變化的邏輯封裝到子類中。經過增長新的子類,咱們便能給系統增長新的功能,並不須要改動抽象父類以及其餘子類,這也符合開放-封閉原則
第十二章 享元模式
寫在前面
享元(flyweight)模式是一種用於性能優化的模式
享元模式的核心是運用共享技術來有效支持大量細粒度的對象
若是系統中由於建立了大量相似的對象而致使內存佔用太高,享元模式就很是有用了
12.1.初識享元模式
50種男士內衣和50種女士內衣穿在塑料模特上拍成廣告照片的例子
12.2.內部狀態與外部狀態
享元模式要求將對象的屬性劃分爲內部狀態與外部狀態(狀態在這裏一般指屬性)
享元模式的目標是儘可能減小共享對象的數量
關於如何劃份內部狀態和外部狀態的幾條經驗:內部狀態存儲於對象內部;內部狀態能夠被一些對象共享;內部狀態獨立於具體的場景,一般不會改變;外部狀態取決於具體的場景,並根據場景而變化,外部狀態不能被共享;
享元模式是一種用時間換空間的優化模式
一般來說,內部狀態有多少種組合,系統中便最多存在多少個對象
使用享元模式的關鍵時如何區別內部狀態和外部狀態
能夠被對象共享的屬性一般被劃分爲內部狀態,如同無論什麼樣式的衣服,均可以按照性別不一樣,穿在同一個男模特或者女模特身上,模特的性別就能夠做爲內部狀態儲存在共享對象的內部
外部狀態取決於具體的場景,並根據場景而變化,就像例子中每件衣服都是不一樣的,它們不能被一些對象共享,所以只能被劃分爲外部狀態
12.3.享元模式的通用結構
12.4.文件上傳的例子
在微雲上傳模塊的開發中,就曾經藉助享元模式提高了程序的性能
1.對象爆炸:支持同時選擇2000個文件,每個文件都對應着一個JavaScript上傳對象的建立;支持好幾種上傳方式,好比瀏覽器插件、Flash和表單上傳等;
2.享元模式重構文件上傳:upload對象必須依賴uploadType屬性才能工做,這是由於插件上傳、Flash上傳、表單上傳的實際工做原理有很大的區別,它們各自調用的接口也是徹底不同的,必須在對象建立之初就明確它是什麼類型的插件,才能夠在程序的運行過程當中,讓它們分別調用各自的start、pause、cancel、del等方法
3.剝離外部狀態:明確了uploadType做爲內部狀態以後,再把其餘的外部狀態從構造函數中抽離出來,Upload構造函數中只保留uploadType參數
4.工廠進行對象實例化
5.管理器封裝外部狀態
12.5.享元模式的實用性
通常來講,如下狀況發生時即可以使用享元模式
1.一個程序中使用了大量的類似對象
2.因爲使用了大量對象,形成很大的內存開銷
3.對象的大多數狀態均可以變爲外部狀態
4.剝離出對象的外部狀態以後,能夠用相對較少的共享對象取代大量對象
12.6.再談內部狀態和外部狀態
有多少種內部狀態的組合,系統中便最多存在多少個共享對象,而外部狀態儲存在共享對象的外部,在必要時被傳入共享對象來組裝成一個完整的對象
1.沒有內部狀態的享元:管理器部分的代碼不須要改動,仍是負責剝離和組裝外部狀態。能夠看到,當對象沒有內部狀態的時候,生產共享對象的工廠實際上變成了一個單例工廠
2.沒有外部狀態的享元:享元模式的關鍵時區別內部狀態和外部狀態。享元模式的過程是剝離外部狀態,並把外部狀態保存在其餘地方,在合適的時刻再把外部狀態組成進共享對象
12.7.對象池
對象池技術的應用很是普遍,HTTP鏈接池和數據庫鏈接池都是其表明應用
1.對象池實現
2.通用對象池實現
對象池是另一種性能優化方案,它跟享元模式有一些類似之處,但沒有分離內部狀態和外部狀態這個過程
12.8.小結
享元模式是爲了解決性能問題而生的模式
在一個存在大量類似對象的系統中,享元模式能夠很好的解決大量對象帶來的性能問題
第十三章 職責鏈模式
職責鏈模式的定義是:使多個對象都有機會處理請求,從而避免請求的發送者和接收者之間的耦合關係,將這些對象連成一條鏈,並沿着這條鏈傳遞該請求,直到有一個對象處理它爲止
13.1.現實中的職責鏈模式
公交車上遞硬幣的例子
職責鏈模式的最大優勢:請求發送者只須要知道鏈中的第一個節點,從而弱化了發送者和一組接收者之間的強聯繫
13.2.實際開發中的職責鏈模式
if..else再套if..else
13.3.用職責鏈模式重構代碼
去掉嵌套的條件分支語句,拆分紅多個小函數
13.5.異步的職責鏈
遇到異步的問題,好比要在節點函數中發起一個ajax異步請求,異步請求返回的結果才能決定是否繼續在職責鏈中passRequest
異步的職責鏈加上命令模式(把ajax請求封裝成命令對象),能夠很方便的建立一個異步ajax隊列庫
13.6.職責鏈模式的優缺點
職責鏈模式的最大優勢就是解耦了請求發送者和N個接收者之間的複雜關係,因爲不知道鏈中的哪一個節點能夠處理你發出的請求,因此你只需把請求傳遞給第一個節點便可
其次,使用了職責鏈模式以後,鏈中的節點對象能夠靈活地拆分重組。增長或者刪除一個節點,或者改變節點在鏈中的位置都是垂手可得的事情
還有一個優勢,那就是能夠手動指定起始節點,請求並非非得從鏈中的第一個節點開始傳遞
一個弊端,首先咱們不能保證某個請求必定會被鏈中德節點處理
另外,職責鏈模式使得程序中多了一些節點對象,可能在某一次的請求傳遞過程當中,大部分節點並無起到實質性的做用,它們的做用僅僅是讓請求傳遞下去,從性能方面考慮,要避免過長的職責鏈帶來的性能損耗
13.7.用AOP實現職責鏈
利用JavaScript的函數式特性,有一種更加方便的方法來建立職責鏈
改寫一下以前的Function.prototype.after函數,使得第一個函數返回'nextSuccessor'時,將請求繼續傳遞給下一個函數
13.8.用職責鏈模式獲取文件上傳對象
以前建立了一個迭代器來迭代獲取合適的文件上傳對象,其實用職責鏈模式能夠更簡單,徹底不用建立這個多餘的迭代器
13.9.小結
在JavaScript開發中,職責鏈模式是最容易被忽視的模式之一
職責鏈模式能夠很好的幫助咱們管理代碼,下降發起請求的對象和處理請求時的對象之間的耦合性。職責鏈中的節點數量和順序是能夠自由變化的,咱們能夠在運行時決定鏈中包含哪些節點
不管是做用域鏈、原型鏈,仍是DOM節點中的事件冒泡,咱們都能從中找到職責鏈模式的影子
職責鏈還能夠和組合模式結合在一塊兒,用來鏈接部件和父部件,或是提升組合對象的效率
第十四章 中介者模式
寫在前面
面向對象設計鼓勵將行爲分佈到各個對象中,把對象劃分紅更小的粒度,有助於加強對象的可複用性,但因爲這些細粒度對象之間的聯繫激增,又有可能會反過來下降它們的可複用性
中介者模式的做用就是解除對象與對象之間的緊耦合關係
增長一箇中介者對象後,全部的相關對象都經過中介者對象來通訊,而不是互相引用,因此當一個對象發生改變時,只須要通知中介者對象便可。中介者使各對象之間耦合鬆散,並且能夠獨立的改變它們之間的交互。中介者模式使網狀的多對多關係變成了相對簡單的一對多關係
14.1.現實中的中介者
1.機場指揮塔
14.2.中介者模式的例子--泡泡堂遊戲
1.爲遊戲增長隊伍:須要在每一個玩家死亡的時候,都遍歷其餘隊友的生存情況,若是隊友所有死亡,則這局遊戲失敗,同時敵人隊伍的全部玩家都取得勝利
2.玩家增多帶來的困擾:能夠隨意地爲遊戲增長玩家或者隊伍,但問題是,每一個玩家和其餘玩家都是牢牢耦合在一塊兒的
3.用中介者模式改造泡泡堂:playerDirector開放一個對外暴露的接口receiveMessage,負責接收player對象發送的消息,而player對象發送消息的時候,老是把自身this做爲參數發送給playerDirector,以便playerDirector識別消息來自於哪一個玩家對象;除了中介者自己,沒有一個玩家知道其餘任何玩家的存在,玩家與玩家之間的耦合關係已經徹底解除,某個玩家的任何操做都不須要通知其餘玩家,而只須要給中介者發送一個消息,中介者處理完消息以後會把處理結果反饋給其餘的玩家對象;
14.3.中介者的例子--購買商品
1.開始編寫代碼
2.對象之間的聯繫
3.可能遇到的困難
4.引入中介者
14.4.小結
中介者模式是迎合迪米特法則的一種實現。迪米特法則也叫最少知識原則,是指一個對象應該儘量少地瞭解另外的對象(相似不和陌生人說話)
在中介者模式裏,對象之間幾乎不知道彼此的存在,它們只能經過中介者對象來互相影響對方
中介者模式使各個對象之間得以解耦,以中介者和對象之間的一對多關係取代了對象之間的網狀多對多關係。各個對象只需關注自身功能的實現,對象之間的交互關係交給了中介者對象來實現和維護
最大的缺點是系統中會新增一箇中介者對象,由於對象之間交互的複雜性,轉移成了中介者對象的複雜性,使得中介者對象常常是巨大的。中介者對象自身每每就是一個難以維護的對象
通常來講,若是對象之間的複雜耦合確實致使調用和維護出現了困難,並且這些耦合度隨項目的變化呈指數增加曲線,那咱們就能夠考慮用中介者模式來重構代碼
第十五章 裝飾者模式
寫在前面
在程序開發中,許多時候都並不但願某個類天生就很是龐大,一次性包含許多職責
裝飾者模式能夠動態地給某個對象添加一些額外的職責,而不會影響從這個類中派生的其餘對象
裝飾者模式可以在不改變對象自身的基礎上,在程序運行期間給對象動態地添加職責
15.1.模擬傳統面嚮對象語言的裝飾者模式
這種給對象動態增長職責的方式,並無真正地改動對象自身,而是將對象放入另外一個對象之中,這些對象以一條鏈的方式進行引用,造成一個聚合對象
15.2.裝飾者也是包裝器
GoF原想把裝飾者(decorator)模式稱爲包裝器(wrapper)模式
從功能上而言,decorator能很好地描述這個模式,但從結構上看,wrapper的說法更加貼切。裝飾者模式將一個對象嵌入另外一個對象之中,實際上至關於這個對象被另外一個對象包裝起來,造成一條包裝鏈。請求隨着這條鏈依次傳遞到全部的對象,每一個對象都有處理這條請求的機會
15.3.回到JavaScript的裝飾者
15.4.裝飾函數
在JavaScript中能夠很方便地給某個對象擴展屬性和方法,但卻很難在不改動某個函數源代碼的狀況下,給該函數添加一些額外的功能。在代碼的運行期間,咱們很難切入某個函數的執行環境
如今須要一個辦法,在不改變函數源代碼的狀況下,能給函數增長功能,這正是開放-封閉原則給咱們指出的光明道路
一種答案,經過保存原引用的方式就能夠改寫某個函數
15.5.用AOP裝飾函數
首先給出Function.prototype.before方法和Function.prototype.after方法
把當前的this保存起來,這個this指向原函數,而後返回一個「代理」函數,這個「代理」函數只是結構上像代理而已,並不承擔代理的職責(好比控制對象的訪問等)。它的工做是把請求分別轉發給新添加的函數和原函數,且負責保證它們的執行順序,讓新添加的函數在原函數以前運行(前置裝飾),這樣就實現了動態裝飾的效果
Function.prototype.after的原理跟Function.prototype.before如出一轍,惟一不一樣的地方在於讓新添加的函數在原函數執行以後再執行
15.6.AOP的應用實例
不管是業務代碼的編寫,仍是框架層面,咱們均可以把行爲依照職責分紅粒度更細的函數,隨後經過裝飾把它們合併到一塊兒,這有助於咱們編寫一個鬆耦合和高複用性的系統
1.數據統計上報:分離業務代碼和數據統計代碼,不管在什麼語言中,都是AOP的經典應用之一
2.用AOP動態改變函數的參數:解決CSRF攻擊最簡單的一個辦法就是在HTTP請求中帶上一個Token參數
3.插件式的表單驗證:分離校驗輸入和提交Ajax請求的代碼,把校驗輸入的邏輯放到validata函數中,而且約定當validata函數返回false的時候,表示校驗未經過
這種裝飾方式疊加了函數的做用域,若是裝飾的鏈條過長,性能上也會受到一些影響
15.7.裝飾者模式和代理模式
這兩種模式都描述了怎樣爲對象提供必定程度上的間接引用,它們的實現部分都保留了對另一個對象的引用,而且向那個對象發送請求
代理模式的目的是,當直接訪問本體不方便或者不符合須要時,爲這個本體提供一個替代者。本體定義了關鍵功能,而代理提供或拒絕對它的訪問,或者在訪問本體以前作一些額外的事情
裝飾者模式的做用就是爲對象動態加入行爲
代理模式一般只有一層代理-本體的引用,而裝飾者模式常常會造成一條長長的裝飾鏈
第十六章 狀態模式
狀態模式的關鍵是區分事務內部的狀態,事務內部狀態的改變每每會帶來事物的行爲改變
16.1.初識狀態模式
電燈燈光切換的例子
一般咱們談到封裝,通常都會優先封裝對象的行爲,而不是對象的狀態。但在狀態模式中恰好相反,狀態模式的關鍵是把事物每種狀態都封裝成單獨的類,跟此種狀態有關的行爲都被封裝在這個類的內部
使用狀態模式的好處很明顯,它能夠使一種狀態和它對應的行爲之間的關係局部化,這些行爲被分散和封裝在各自對應的狀態類之中,便於閱讀和管理代碼
另外,狀態之間的切換都被分佈在狀態類內部,這使得咱們無需編寫過多的if、else條件分支語言來控制狀態之間的轉換
16.2.狀態模式的定義
GoF中對狀態模式的定義:容許一個對象在其內部狀態改變時改變它的行爲,對象看起來彷佛修改了它的類
第一部分的意思是將狀態封裝成獨立的類,並將請求委託給當前的狀態對象,當對象的內部狀態改變時,會帶來不一樣的行爲變化
第二部分是從客戶的角度來看,咱們使用的對象,在不一樣的狀態下具備大相徑庭的行爲,這個對象看起來是從不一樣的類中實例化而來的,實際上這是使用了委託的效果
16.4.缺乏抽象類的變通方式
在Java中,全部的狀態必須繼承自一個State抽象父類,固然若是沒有共同的功能值得放入抽象父類中,也能夠選擇實現State接口
16.5.另外一個狀態模式示例--文件上傳
文件上傳程序中有掃描、正在上傳、暫停、上傳成功、上傳失敗這幾種狀態,音樂播放器能夠分爲加載中、正在播放、暫停、播放完畢這幾種狀態
1.更復雜的切換條件
2.一些準備工做
3.開始編寫代碼
4.狀態模式重構文件上傳
16.6.狀態模式的優缺點
狀態模式的優勢以下:一、狀態模式定義了狀態與行爲之間的關係,並將它們封裝在一個類裏。經過增長新的狀態類,很容易增長新的狀態和轉換;二、避免Context無限膨脹,狀態切換的邏輯被分佈在狀態類中,也去掉了Context中本來過多的條件分支;三、用對象代替字符串來記錄當前狀態,使得狀態的切換更加一目瞭然;四、Context中的請求動做和狀態類中封裝的行爲能夠很是容易地獨立變化而互不影響;
狀態模式的缺點:一、會在系統中定義許多狀態類,編寫20個狀態類是一項枯燥乏味的工做,並且系統中會所以而增長很多對象;二、因爲邏輯分散在狀態類中,雖然避開了不受歡迎的條件分支語句,但也形成了邏輯分散的問題,咱們沒法在一個地方就看出整個狀態機的邏輯;
16.7.狀態模式中的性能優化點
一些比較大的優化點:有兩種選擇來管理state對象的建立和銷燬。第一種是僅當state對象被須要時才建立並隨後銷燬,另外一種是一開始就建立好全部的狀態對象,而且始終不銷燬它們;爲每一個Context對象都建立了一組state對象,實際上這些state對象之間是能夠共享的,各Context對象能夠共享一個state對象;
16.8.狀態模式和策略模式的關係
狀態模式和策略模式向一對雙胞胎,它們都封裝了一系列的算法或者行爲,它們的類圖看起來幾乎如出一轍,但在乎圖上有很大不一樣
策略模式和狀態模式的相同點是,它們都有一個上下文、一些策略或者狀態類,上下文把請求委託給這些類來執行
它們之間的區別是策略模式中的各個策略類之間是平等又平行的,它們之間沒有任何聯繫,因此客戶必須熟知這些策略類的做用,以便客戶能夠隨時主動切換算法;而在狀態模式中,狀態和狀態對應的行爲是早已被封裝好的,狀態之間的切換也早被規定完成,「改變行爲」這件事發生在狀態模式內部
16.9.JavaScript版本的狀態機
狀態模式是狀態機的實現之一,但在JavaScript這種「無類」語言中,沒有規定讓狀態對象必定要從類中建立而來。另一點,JavaScript能夠很是方便地使用委託技術,並不須要事先讓一個對象持有另外一個對象
16.10.表驅動的有限狀態機
另一種實現狀態機的方法,核心是基於表驅動的。能夠在表中很清楚的看到下一個狀態是由當前狀態和行爲共同決定的。這樣一來,咱們就能夠在表中查找狀態,而沒必要定義不少條件分支
16.11.實際項目中的其餘狀態機
在實際開發中,不少場景均可以用狀態機來模擬,好比一個下拉菜單在hover動做下有顯示、懸浮、隱藏等狀態;一次TCP請求有創建鏈接、監聽、關閉等狀態;一個格鬥遊戲中人物有攻擊、防護、跳躍、跌倒等狀態
16.12.小結
狀態模式也許是被你們低估的模式之一
實際上,經過狀態模式重構代碼以後,不少雜亂無章的代碼會變得清晰
第十七章 適配器模式
寫在前面
適配器模式的做用是解決兩個軟件實體間的接口不兼容的問題
在程序開發中有許多這樣的場景:當咱們試圖調用模塊或者對象的某個接口時,卻發現這個接口的格式並不符合目前的需求
兩種解決辦法,第一種是修改原來的接口實現,第二種辦法是建立一個適配器,將原接口轉換爲客戶但願的另外一個接口,客戶只須要和適配器打交道
17.1.現實中的適配器
幾個現實生活中的適配器模式:1.港式插頭轉換器;2.電源適配器;3.USB轉接口
17.2.適配器模式的應用
適配器模式是一種「亡羊補牢」的模式,沒有人會在程序的設計之初就使用它
17.3.小結
適配器模式是一種相對簡單的模式
有一些模式跟適配器模式的結構很是類似,好比裝飾者模式、代理模式和外觀模式
適配器模式主要用來解決兩個已有接口之間不匹配的問題,它不考慮這些接口是怎樣實現的,也不考慮它們未來可能會如何演化
裝飾者模式和代理模式也不會改變原有對象的接口,但裝飾者模式的做用是爲了給對象增長功能
外觀模式的做用卻是和適配器模式比較類似,有人把外觀模式當作一組對象的適配器,但外觀模式最顯著的特色是定義了一個新的接口
第十八章 單一職責原則
前輩總結的這些設計原則一般指的是單一職責原則、里氏替換原則、依賴倒置原則、接口隔離原則、合成複用原則和最少知識原則
寫在前面
單一職責原則(SRP)的職責被定義爲「引發變化的緣由」
SRP原則體現爲:一個對象(方法)只作一件事情
18.1.設計模式中的SRP原則
SRP原則在不少設計模式中都有着普遍的運用,例如代理模式、迭代器模式、單例模式和裝飾者模式
18.2.什麼時候應該分離職責
SRP原則是全部原則中最簡單也是最難正確運用的原則之一
一方面,若是隨着需求的變化,有兩個職責老是同時變化,那就沒必要分離他們
另外一方面,職責的變化軸線僅當它們肯定會發生變化時才具備意義
18.4.SRP原則的優缺點
SRP原則的優勢時下降單個類或者對象的複雜度,按照職責把對象分解成更小的粒度,這有助於代碼的複用,也有利於進行單元測試
SRP原則的一些缺點,最明顯的是會增長編寫代碼的複雜度
第十九章 最少知識原則
最少知識原則(LKP)說的是一個軟件實體應當儘量少地與其餘實體發生相互做用。
這裏的軟件實體是一個廣義的概念,不只包括對象,還包括系統、類、模塊、函數、變量等
19.1.減小對象之間的聯繫
最少知識原則要求咱們在設計程序時,應當儘可能減小對象之間的交互
19.2.設計模式中的最少知識原則
最少知識原則在設計模式中體現得最多的地方是中介者模式和外觀模式
1.中介者模式(經過增長一箇中介者對象,讓全部的相關對象都經過中介者對象來通訊,而不是互相引用)
2.外觀模式(外觀模式的做用是對客戶屏蔽一組子系統的複雜性。外觀模式對客戶提供一個簡單易用的高層接口,高層接口會把客戶的請求轉發給子系統來完成具體的功能實現;外觀模式的做用主要有兩點(爲一組子系統提供一個簡單便利的訪問入口;隔離客戶與複雜子系統之間的聯繫,客戶不用去了解子系統的細節))
19.3.封裝在最少知識原則中的體現
封裝在很大程度上表達的是數據的隱藏。一個模塊或者對象能夠將內部的數據或者實現細節隱藏起來,只暴露必要的接口API供外界訪問
最少知識原則也叫作迪米特法則(Law of Demeter,LoD)
第二十章 開放-封閉原則
寫在前面
在面向對象的程序設計中,開放-封閉原則(OCP)是最重要的一條原則
開放-封閉原則定義以下:軟件實體(類、模塊、函數)等應該是能夠擴展的,可是不可修改
20.1.擴展window.onload函數
經過增長代碼,而不是修改代碼的方式,來給window.onload函數添加新的功能
經過動態裝飾函數的方式,咱們徹底不用理會從前window.onload函數的內部實現,不管它的實現優雅或是醜陋
20.2.開放和封閉
引出開放-封閉原則的思想-當須要改變一個程序的功能或者給這個程序增長新功能的時候,能夠使用增長代碼的方式,可是不容許改動程序的源代碼
20.3.用對象的多態性消除條件分支
利用多態的思想,咱們把程序中不變的部分隔離出來(動物都會叫),而後把可變的部分封裝起來(不一樣類型的動物發出不一樣的叫聲),這樣一來程序就具備了可擴展性
20.4.找出變化的地方
咱們仍是能找到一些讓程序儘可能遵照開放-封閉原則的規律,最明顯的就是找出程序中將要發生變化的地方,而後把變化封裝起來
經過封裝變化的方式,能夠把系統中穩定不變的部分和容易變化的部分隔離出來
除了利用對象的多態性以外,還有其餘方式能夠幫助咱們編寫遵照開放-封閉原則的代碼:1.放置掛鉤(hook);2.使用回調函數
20.5.設計模式中的開放-封閉原則
幾乎全部的設計模式都是遵照開放-封閉原則的,咱們見到的好設計,一般都經得起開放-封閉原則的考驗
20.6.開放-封閉原則的相對性
實際上,讓程序保持徹底封閉是不容易作到的
並且讓程序符合開放-封閉原則的代價是引入更多的抽象層次,更多的抽象有可能會增大代碼的複雜度
第二十一章 接口和麪向接口編程
談到接口時一般涉及如下幾種含義
經過主動暴露的接口來通訊,能夠隱藏軟件系統內部的工做細節。這也是咱們最熟悉的第一種接口含義
第二種接口是一些語言提供的關鍵字,好比Java的interface。interface關鍵字能夠產生一個徹底抽象的類
第三種接口便是咱們談論的「面向接口編程」中的接口,接口的含義在這裏體現得更爲抽象
21.1.回到Java的抽象類
靜態類型語言一般設計爲能夠「向上轉型」。當給一個類變量賦值時,這個變量的類型既能夠使用這個類自己,也能夠使用這個類的超類
從過程上來看,「面向接口編程」實際上是「面向超類型編程」
21.2.interface
雖然不少人在實際使用中刻意區分抽象類和interface,但使用interface實際上也是繼承的一種方式,叫作接口繼承
21.3.JavaScript語言是否須要抽象類和interface
抽象類和interface的做用主要都是如下兩點(1.經過向上轉型來隱藏對象的真正類型,以表現對象的多態性;2.約定類與類之間的一些契約行爲)
不多人在JavaScript開發中去關心對象的真正類型
由於不須要進行向上轉型,接口在JavaScript中的最大做用就退化到了檢查代碼的規範性
21.4.用鴨子類型進行接口檢查
鴨子類型是動態語言面向對象設計中德一個重要概念。利用鴨子類型的思想,沒必要藉助超類型的幫助,就能在動態類型語言中輕鬆地實現本章提到的設計原則:面向接口編程,而不是面向實現編程
21.5.用TypeScript編寫基於interface的命令模式
Typescript是微軟開發的一種編程語言,是JavaScript的一個超集。Typescript代碼最終會被編譯成原生的JavaScript代碼執行。經過Typescript,咱們能夠使用靜態語言的方式來編寫JavaScript程序
第二十二章 代碼重構
模式和重構之間有着一種與生俱來的關係。從某種角度來看,設計模式的目的即便爲許多重構行爲提供目標
22.1.提煉函數
這是一種很常見的優化工做,這樣作的好處主要有如下幾點:1.避免出現超大函數;2.獨立出來的函數有助於代碼複用;3.獨立出來的函數更容易被覆寫;4.獨立出來的函數若是擁有一個良好的命名,自己就起到註釋的做用;
22.2.合併重複的條件片斷
條件分支語句內部散佈了一些重複的代碼,那麼就有必要進行合併去重工做
22.3.把條件分支語句提煉成函數
複雜的條件分支語句是致使程序難以閱讀和理解的重要緣由,並且容易致使一個龐大的函數
22.4.合理使用循環
合理利用循環不只能夠完成一樣的功能,還能夠使代碼量更少
22.5.提早讓函數退出代替嵌套條件分支
關於「函數只有一個出口」,每每會有一些不一樣的見解
有一個常見的技巧,即在面對一個嵌套的if分支時,咱們能夠把外層if表達式進行反轉
22.6.傳遞對象參數代替過長的參數列表
有時候一個函數可能接受多個參數,而參數的數量越多,函數就越難以理解和使用
22.7儘可能減小參數數量
在實際開發中,向函數傳遞參數不可避免,但咱們應該儘可能減小函數接收的參數數量
22.8.少用三目運算符
三目運算符性能高,代碼量少,這理由很難站住腳
相比損失的代碼可讀性和可維護性,三目運算符節省的代碼量也能夠忽略不計
寫在後面
pdf書籍、筆記思惟導圖、隨書代碼打包下載地址:
https://pan.baidu.com/s/17QLgFO2zcTBJNKCJz05Lsg(提取碼:j29j)
紙質書京東購買地址:
https://u.jd.com/wlo6gr
(推薦購買紙質書來學習)
相關文章
1.
[書籍精讀]《React Native精解與實戰》精讀筆記分享
2.
《JavaScript設計模式與開發實踐》讀書筆記 一
3.
JavaScript 設計模式與開發實踐讀書筆記
4.
《JavaScript設計模式與開發實踐》讀書筆記
5.
《Javascript設計模式與開發實踐》--讀書筆記
6.
[書籍精讀]《CSS世界》精讀筆記分享
7.
[書籍精讀]《深刻淺出Node.js》精讀筆記分享
8.
[書籍精讀]《React進階之路》精讀筆記分享
9.
[書籍精讀]《你不知道的JavaScript(下卷)》精讀筆記分享
10.
[書籍精讀] 《你不知道的JavaScript(上卷)》精讀筆記分享
更多相關文章...
•
RSS 閱讀器
-
RSS 教程
•
Redis發佈訂閱模式
-
Redis教程
•
JDK13 GA發佈:5大特性解讀
•
Tomcat學習筆記(史上最全tomcat學習筆記)
相關標籤/搜索
精讀
讀書分享
讀書筆記
原創精讀
精讀貓說
3000字精讀
精讀源碼
-原創精讀
FSFA 讀書筆記
JavaScript
紅包項目實戰
網站建設指南
Docker教程
設計模式
開發工具
委託模式
0
分享到微博
分享到微信
分享到QQ
每日一句
每一个你不满意的现在,都有一个你没有努力的曾经。
最新文章
1.
JDK JRE JVM,JDK卸載與安裝
2.
Unity NavMeshComponents 學習小結
3.
Unity技術分享連載(64)|Shader Variant Collection|Material.SetPassFast
4.
爲什麼那麼多人用「ji32k7au4a83」作密碼?
5.
關於Vigenere爆0總結
6.
圖論算法之最小生成樹(Krim、Kruskal)
7.
最小生成樹 簡單入門
8.
POJ 3165 Traveling Trio 筆記
9.
你的快遞最遠去到哪裏呢
10.
雲徙探險中臺賽道:借道雲原生,尋找「最優路線」
本站公眾號
歡迎關注本站公眾號,獲取更多信息
相關文章
1.
[書籍精讀]《React Native精解與實戰》精讀筆記分享
2.
《JavaScript設計模式與開發實踐》讀書筆記 一
3.
JavaScript 設計模式與開發實踐讀書筆記
4.
《JavaScript設計模式與開發實踐》讀書筆記
5.
《Javascript設計模式與開發實踐》--讀書筆記
6.
[書籍精讀]《CSS世界》精讀筆記分享
7.
[書籍精讀]《深刻淺出Node.js》精讀筆記分享
8.
[書籍精讀]《React進階之路》精讀筆記分享
9.
[書籍精讀]《你不知道的JavaScript(下卷)》精讀筆記分享
10.
[書籍精讀] 《你不知道的JavaScript(上卷)》精讀筆記分享
>>更多相關文章<<