2018任務繁重,今年打算把重點放在js的深入上,往大前端方向發展.
年初有空看完了《JavaScript高級程序設計》和《JavaScript+DOM編程藝術》兩本書,看的比較粗糙,回想起來,當時是有些收獲,但現在已記不清具體得到了什么.還是太過于浮躁了.
以后打算靜下心來好好沉淀自己,每看一本書盡量做好閱讀筆記.就從這本《JavaScript設計模式與開發實踐》開始吧.
前言
在前言部分,作者引用了一個例子來描述設計模式:
設想有一個電子愛好者,雖然他沒有經過正規的培訓,但是卻日積月累地設計并制造出許多有用的電子設備:業余無線電、蓋革計數器、報警器等。有一天這個愛好者決定重新回到學校去攻讀電子學學位,來讓自己的才能得到真實的認可。隨著課程的展開, 這個愛好者突然發現課程內容都似曾相識。似曾相識的并不是術語或者表述的方式,而是背后的概念。這個愛好者不斷學到一些名稱和原理,雖然這些名稱和原理原來他不知道,但事實上他多年來一直都在使用。整個過程只不過是一個接一個的頓悟。
軟件開發中的設計也是如此,這些“好的設計”早已存在于軟件開發中。一個稍有經驗的程序員也許在不知不覺中數次使用過這些模式,這其實是一種軟件開發過程中收獲的解決某類問題的經驗,是一些經過了大量實際項目驗證的優秀解決方案。,"設計模式"是給這些經驗賦予了名字而已,方便了傳播和學習它們.
第一部分.基礎知識
在第一章中作者首先介紹了動態語言和鴨子類型,鴨子類型的通俗說法是:“如果一個動物走起路來像鴨子,叫起來也是鴨子,那么它就是鴨子。”
那鴨子類型有什么用呢:
利用鴨子類型的思想,我們不必借助超類型的幫助,就能輕松地在動態類型語言中實現一個原則:“面向接口編程,而不是面向實現編程”。例如,一個對象若有 push 和 pop 方法,并且這些方法提供了正確的實現,它就可以被當作棧來使用。
-
封裝變化
在面向對象的特性"封裝"里,介紹到了封裝變化
- 創建型模式:要創建一個對象,是一種抽象行為,而具體創建什么對象則是可以變化 的,創建型模式的目的就是封裝創建對象的變化。
- 結構型模式:封裝的是對象之間的組合關系
- 行為型模式:封裝的是對象的行為變化
通過封裝變化的方式,把系統中穩定不變的部分和容易變化的部分隔離開來,在系統的演變過程中,我們只需要替換那些容易變化的部分,如果這些部分是已經封裝好的,替換起來也相對容易。這可以最大程度地保證程序的穩定性和可擴展性。
-
原型模式
- 一般我們創建對象:先指定它的類型,然后通過類來創建這個對象.
- 原型模式創建對象:找到一個對象,然后通過克隆來創建一個一模一樣的對象(包括對象中保存的數據)。實現關鍵,是語言本身是否提供了 clone 方法,。ECMAScript 5提供了 Object.create 方法,可以用來克隆對象。
(個人理解這里的克隆是 淺拷貝)否則怎么解釋下面的現象//
var obj = new Object()//假設這里是從Object.prototype深拷貝了一個對象
console.log(obj.a);//undefined
Object.prototype.a = "aaa";
console.log(obj.a);//輸出:aaa.(如果是深拷貝的話,輸出應該是undefined)
在 JavaScript 語言中不存在類的概念,對象也并非從類中創建,出來的,所有的 JavaScript 對象都是從某個對象上克隆而來的。既然每個對象都是由其他對象克隆而來的,那么我們猜測語言本身至少要提供一個根對象,其他對象都發源于這個根對象。這個猜測是正確的,根對象是Object.prototype對象,我們在 JavaScript 遇到的每個對象,實際上都是從 Object.prototype 對象克隆而來的, Object.prototype 對象就是它們的原型。
原型繼承
JavaScript遵循的原型編程的基本規則:
- 所有的數據都是對象
我們在 JavaScript 遇到的每個對象,實際上都是從 Object.prototype 對象克隆而來的, Object.prototype 對象就是它們的原型
- 要得到一個對象,不是通過實例化類,而是找到一個對象作為原型并克隆它。
在 JavaScript 語言里,我們并不需要關心克隆的細節,因為這是引擎內部負責實現的。我們所需要做的只是顯式地調用 var obj1 = new Object()或者 var obj2 = {}。此時,引擎內部會從 Object.prototype 上面克隆一個對象出來,我們最終得到的就是這個對象。 用 new運算符來創建對象的過程,實際上也只是先克隆 Object.prototype 對象,再進行一些其他額 外操作的過程。
- 對象會記住它的原型
通過 __ proto __ - 如果對象無法響應某個請求,它會把這個請求委托給它自己的原型。
這就是原型繼承的原理
this指向
- 作為對象的方法調用:指向當前對象
- 作為普通函數調用:指向全局對象。在瀏覽器的 JavaScript 里,這個全局對象是 window 對象。
- 構造器調用:指向返回的這個對象
- Function.prototype.call 或 Function.prototype.apply 調用:動態改this
第二部分 設計模式
(簡單的就略過了)
1. 單例模式
略
2. 策略模式
策略模式的定義是:定義一系列的算法,把它們一個個封裝起來(策略類strategy),并且使它們可以相互替換。再由一個Context類負責接收用戶的請求 并交給這些策略對象處理,這些策略對象會根據請求返回不同的執行結果,這樣便能表現出對象的多態性。
3.代理模式
舉個例子,小明追女神,不好意思送花,找了小紅幫忙,讓小紅趁著女神心情好的時候幫他把花送給女神,這樣成功率會高一些.這里小紅就是代理..這里代理(小紅)的作用就是監測就是監測女神的心情+替小明送花.這兩件事小明做起來是很困難的,但是小紅做起來很簡單,這就叫代理模式.
- 虛擬代理:上面的例子更詳細的說叫做虛擬代理.
- 保護代理:女神不喜歡小明,也不好意思直接拒絕,于是找了小紅幫忙拒絕小明,這時候小紅就叫做保護代理.
4.迭代器模式
迭代器模式是指提供一種方法順序訪問一個聚合對象中的各個元素,而又不需要暴露該對象 的內部表示。迭代器模式可以把迭代的過程從業務邏輯中分離出來,在使用迭代器模式之后,即使不關心對象的內部構造,也可以按順序訪問其中的每個元素。
5.發布—訂閱模式(觀察者模式)
它定義對象間的一種一對多的依賴關系,當一個對象的狀態發生改變時,所有依賴于它的對象都將得到通知。在 JavaScript 開發中,我們一般用事件模型來替代傳統的發布—訂閱模式。
6.命令模式
作者舉的例子:
假設有一個快餐店,而我是該餐廳的點餐服務員,那么我一天的工作應該是這樣的:當某位客人點餐或者打來訂餐電話后,我會把他的需求都寫在清單上,然后交給廚房,客人不用關心是哪些廚師幫他炒菜。我們餐廳還可以滿足客人需要的定時服務,比如客人可能當前正在回家的路 上,要求 1 個小時后才開始炒他的菜,只要訂單還在,廚師就不會忘記。客人也可以很方便地打電話來撤銷訂單。另外如果有太多的客人點餐,廚房可以按照訂單的順序排隊炒菜。
這些記錄著訂餐信息的清單,便是命令模式中的命令對象。
命令模式是為了把命令的發起者與執行者徹底分開,比如可能命令發起者在發起這個命令后就馬上被殺死了,但他發的這個命令滿足條件時就會被執行;比如由于命令與發起者無關,于是我們在某些情況下可以做到撤銷之前執行過的命令。
命令模式的使用場景:
- 作者提到的:
有時候需要向某些對象發送請求(比如客人向餐廳發出訂單請求),但是并不知道請求的接收者是誰(并不知道到底是哪個廚師炒菜),也不知道被請求的操作是什么(不知道廚師到底怎么炒菜的),此時就可以用命令模式,使得請求發送者和請求接收者能夠消除彼此之間的耦合關系。 - 我認為主要有兩點:
1.更方便的對命令進行擴展
2.對多個命令的統一控制(這種控制包括但不限于:隊列、撤銷/恢復、記錄日志等等)
查閱資料,得到命令模式的UML類圖:
簡單的命令模式有四個角色:
- 命令發出者(Client):客戶端,命令發送者(上面為客人)
- 命令管理者(Invoker/manager):管理類,管理所有接收到的命令,執行、保存和撤銷等.(訂單)
- 命令接收者(Receiver):行為具體實現者,說白了就是實現具體功能代碼的.(廚師)
- 命令對象(ConcreteCommand):命令本身.(訂單上的客戶的每條需求)
下面介紹一下宏命令,因為后面還會用到
宏命令是一組命令的集合,通過執行宏命令的方式,可以一次執行一批命令。想象一下,家 里有一個萬能遙控器,每天回家的時候,只要按一個特別的按鈕,它就會幫我們關上房間門,順 便打開電腦并登錄 QQ。
var closeDoorCommand = { execute: function(){ console.log( '關門' ); } };
var openPcCommand = { execute: function(){ console.log( '開電腦' ); } };
var openQQCommand = { execute: function(){ console.log( '登錄 QQ' ); } };
var MacroCommand = function(){
return {
commandsList: [],
add: function( command ){ this.commandsList.push( command ); },
execute: function(){
for ( var i = 0, command; command = this.commandsList[ i++ ];)
{ command.execute(); }
}
}
};
var macroCommand = MacroCommand();
macroCommand.add( closeDoorCommand );
macroCommand.add( openPcCommand );
macroCommand.add( openQQCommand );
macroCommand.execute();
宏命令是命令模式與組合模式的聯用產物,關于組合模式的知識,我們將在第 10 章詳細介紹。
6. 組合模式
回顧上面的宏命令,其中,marcoCommand 被稱為組合對象,closeDoorCommand、openPcCommand、openQQCommand 都是葉對象。在 macroCommand 的 execute 方法里,并不執行真正的操作,而是遍歷它所包含的葉對象, 把真正的 execute 請求委托給這些葉對象。
macroCommand 表現得像一個命令,但它實際上只是一組真正命令的“代理”。并非真正的代理, 雖然結構上相似,但 macroCommand 只負責傳遞請求給葉對象,它的目的不在于控制對葉對象的訪問。
組合模式:組合模式將對象組合成樹形結構,以表示“部分-整體”的層次結構。 除了用來表示樹形結構之外,組合模式的另一個好處是通過對象的多態性表現,使得用戶對單個對象和組合對象的使用具有一致性,下面分別說明
- 利用對象多態性統一對待組合對象和單個對象。利用對象的多態性可以使客戶端忽略組合對象和單個對象的不同。在組合模式中,客戶將統一地使用組合結構中的所有 對象,而不需要關心它究竟是組合對象還是單個對象。
-
表示樹形結構。提供了一 種遍歷樹形結構的方案,通過調用組合對象的 execute 方法,程序會遞歸調用組合對象下面的葉對象的 execute 方法.
image.png
請求從上到下沿著樹進行傳遞,直到樹的盡頭。
使用場景:
只有用一致的方式對待列表中的每個葉對象的時候,才適合使用組合模式。
缺點:
然而,組合模式并不是完美的,它可能會產生一個這樣的系統:系統中的每個對象看起來都 與其他對象差不多。它們的區別只有在運行的時候會才會顯現出來,這會使代碼難以理解。此外, 如果通過組合模式創建了太多的對象,那么這些對象可能會讓系統負擔不起。
7.中介者模式
面向對象設計鼓勵將行為分布到各個對象中,把對象劃分成更小的粒度,有助于增強對象的可復用性,但由于這些細粒度對象之間的聯系激增,又有可能會反過來降低它們的可復用性。
當程序的規模增大對象變多,它們間的關系也變復雜,難免會形成網狀的交叉引用。當我們改變其中之一,很可能需要通知所有引用到它的對象。這樣一來,就像在心臟旁邊拆掉一根毛細血管一般, 即使一點很小的修改也必須小心翼翼.
變成下面這個樣子:
中介者模式的作用就是解除對象相互之間的耦合關系。增加一個中介者對象后,所有的相關對象都通過中介者對象來通信,而不是互相引用,當一個對象改變時,只需通知中介者即可。如下
中介者模式可以非常方便地對模塊或者對象進行解耦,但對象之間并非一定需要解耦。在實際項目中,模塊或對象之間有一些依賴關系是很正常的。畢竟我們寫程序是為了快速完成項目交付生產,而不是堆砌模式和過度設計。關鍵就在于如何去衡量對象之間的耦合程度。
一般來說, 如果對象之間的復雜耦合確實導致調用和維護出現了困難,而且這些耦合度隨項目的變化呈指數 增長曲線,那我們就可以考慮用中介者模式來重構代碼。
8.模板方法模式
這個比較簡單
定義一個抽象類,封裝一些方法,由子類來實現它們.
好萊塢原則
允許底層組件將自己掛鉤到高層組件中,而高層組件會決定什么時候去使用這些底層組件,高層組件對待底層組件的方式,跟演藝公司對待新人演員一樣,都是“別調用我們,我們會調用你”。
在js中使用好萊塢原則可以實現和繼承一樣的效果.
========================繼承實現模板方法模式========================
var Beverage = function(){};
Beverage.prototype.boilWater = function(){ console.log( '把水煮沸' ); };
Beverage.prototype.brew = function(){ throw new Error( '子類必須重寫 brew 方法' ); };
Beverage.prototype.pourInCup = function(){ throw new Error( '子類必須重寫 pourInCup 方法' ); };
Beverage.prototype.addCondiments = function(){ throw new Error( '子類必須重寫 addCondiments 方法' ); };
Beverage.prototype.init = function(){
this.boilWater();
this.brew();
this.pourInCup();
this.addCondiments();
};
var Coffee = function(){};
Coffee.prototype = new Beverage();
Coffee.prototype.brew = function(){ console.log( '用沸水沖泡咖啡' ); };
Coffee.prototype.pourInCup = function(){ console.log( '把咖啡倒進杯子' ); };
Coffee.prototype.addCondiments = function(){ console.log( '加糖和牛奶' ); };
var coffee= new Coffee();
coffee.init();
======================在js中使用好萊塢原則可以實現和繼承一樣的效果.========================
var Beverage = function( param ){
var boilWater = function(){ console.log( '把水煮沸' ); };
var brew = param.brew || function(){ throw new Error( '必須傳遞 brew 方法' ); };
var pourInCup = param.pourInCup || function(){ throw new Error( '必須傳遞 pourInCup 方法' ); };
var addCondiments = param.addCondiments || function(){ throw new Error( '必須傳遞 addCondiments 方法' ); };
var F = function(){};
F.prototype.init = function(){
boilWater();
brew();
pourInCup();
addCondiments(); };
return F;
};
var Coffee = Beverage({
brew: function(){ console.log( '用沸水沖泡咖啡' ); },
pourInCup: function(){console.log( '把咖啡倒進杯子' ); },
addCondiments: function(){console.log( '加糖和牛奶' ); }
});
var coffee = new Coffee();
coffee.init();
在這段代碼中,我們把 brew、pourInCup、addCondiments 這些方法依次傳入 Beverage 函數, Beverage 函數被調用之后返回構造器 F。F 類中包含了“模板方法”F.prototype.init。跟繼承得到的效果一樣,該“模板方法”里依然封裝了飲料子類的算法框架。
9.享元模式
舉例子:
作者舉的例子簡單來說就是:假如一家內衣工廠有100件內衣需要拍成廣告照片,如果找100個模特(類似于創建100個對象)過來穿起來拍照就很浪費資源,如果找一個模特(只創建一個對象)穿和拍就很節省資源了.這就叫享元模式.
定義:享元(flyweight)模式是一種用于性能優化的模式,核心是運用共享技術來有效支持大量細粒度的對象。享元模式是為解決性能問題而生的模式,這跟大部分模式的誕生原因都不一樣。在一個存在大量相似對象的系統中,享元模式可以很好地解決大量對象帶來的性能問題。
用途:
- 一個程序中使用了大量的相似對象,導致內存占用過高
- 對象的大多數狀態都可以變為外部狀態,剝離出對象的外部狀態之后,可以用相對較少的共享對象取代大量對象。
享元模式要求將對象的屬性劃分為內部屬性與外部屬性(也叫內部狀態與外部狀態)
關于如何劃分內部屬性和外部屬性,有下面的幾條經驗:
- 內部狀態存儲于對象內部。
- 內部狀態可以被一些對象共享
- 內部狀態獨立于具體的場景,通常不會改變。
- 外部狀態取決于具體的場景,并根據場景而變化,外部狀態不能被共享。
對象池
對象池也是一種共享技術,對象池維護一個裝載空閑對象的池子,如果需要對象的時候,不是直接 new,而是轉從對象池里獲取。如 果對象池里沒有空閑對象,則創建一個新的對象,當獲取出的對象完成它的職責之后, 再進入池子等待被下次獲取。
(一下子想起了iOS開發中的UITableView也是維護了一個對象池來復用cell的概念)
10.職責鏈模式
職責鏈模式的定義是:使多個對象都有機會處理請求,從而避免請求的發送者和接收者之間 的耦合關系,將這些對象連成一條鏈,并沿著這條鏈傳遞該請求,直到有一個對象處理它為止。
職責鏈模式的名字非常形象,一系列可能會處理請求的對象被連接成一條鏈,請求在這些對 象之間依次傳遞,直到遇到一個可以處理它的對象,我們把這些對象稱為鏈中的節點,如圖所示。
(第一反應就是事件冒泡)
11.裝飾者模式
在程序開發中,許多時候都并不希望某個類天生就非常龐大,一次性包含許多職責。那么我們就可以使用裝飾者模式。裝飾者模式可以動態地給某個對象添加一些額外的職責,而不會影響從這個類中派生的其他對象。跟繼承相比,裝飾者是一種更輕便靈活的做法,這是一種“即用即付”的 方式,比如天冷了就多穿一件外套,需要飛行時就在頭上插一支竹蜻蜓. (我覺得前端開發就是這樣,還要裝一推插件,巨煩!這點iOS就好點,一個Xcode就夠了)
在 JavaScript 中可以很方便地給某個對象擴展屬性和方法, 但卻很難在不改動某個函數源代碼的情況下,給該函數添加一些額外的功能。在代碼的運行期間, 我們很難切入某個函數的執行環境。
要想為函數添加一些功能,最簡單粗暴的方式就是直接改寫該函數,但這是最差的辦法,直 接違反了開放-封閉原則:
var a = function(){ alert (1); }
// 改成:
var a = function(){ alert (1); alert (2); }
現在需要一個辦法,在不改變函數源代碼的情況下,能給函數增加功能,這正是開放?封閉原則給我們指出的光明道路。
我們已經找到了一種答案,通過保存原引用的方式就可以改寫某個函數:
var a = function(){ alert (1); }
var _a = a;
a = function(){ _a(); alert (2); }
a();
但是這樣寫不太好,用 AOP裝飾函數會更好些
Function.prototype.before = function(fn){
var self = this;
var a = function(){
fn.apply(this,arguments);// 執行新函數,且保證 this 不被劫持,新函數接受的參數 ,也會被原封不動地傳入原函數,新函數在原函數之前執行
self.apply(self,arguments);// 執行原函數并返回原函數的執行結果,并且保證 this 不被劫持
}
return a;
}
var fn1 = function(){
console.log('a');
}
var fn2 = fn1.before(function(){
console.log('xiagian');
})
fn2()
12.狀態模式
狀態模式的定義:
允許一個對象在其內部狀態改變時改變它的行為,對象看起來似乎修改了它的類。
我們以逗號分割,把這句話分為兩部分來看:
- 第一部分的意思是將狀態封裝成獨立的類,并將請求委托給當前的狀態對象,當對象的內部狀態改變時,會帶來不同的行為變化。
- 第二部分是從客戶的角度來看,我們使用的對象,在不同的狀態下具有截然不同的行為,這個對象看起來是從不同的類中實例化而來的,實際上這是使用了委托的效果。
狀態模式和策略模式的關系
狀態模式和策略模式像一對雙胞胎,它們都封裝了一系列的算法或者行為,它們的類圖看起來幾乎一模一樣,但在意圖上有很大不同,因此它們是兩種迥然不同的模式。
策略模式和狀態模式的相同點是,它們都有一個上下文、一些策略或者狀態類,上下文把請 求委托給這些類來執行。
它們之間的區別是策略模式中的各個策略類之間是平等又平行的,它們之間沒有任何聯系, 所以客戶必須熟知這些策略類的作用,以便客戶可以隨時主動切換算法;而在狀態模式中,狀態 和狀態對應的行為是早已被封裝好的,狀態之間的切換也早被規定完成,“改變行為”這件事情 發生在狀態模式內部。對客戶來說,并不需要了解這些細節。這正是狀態模式的作用所在。
一個有限狀態機庫: https://github.com/jakesgordon/javascript-state-machine,以及中文教程 https://www.cnblogs.com/lyzg/p/5058335.html
13.適配器模式
適配器模式是一對相對簡單的模式,作用是解決兩個軟件實體間的接口不兼容的問題。使用適配器模式之后,原本 由于接口不兼容而不能工作的兩個軟件實體可以一起工作。(略)
設計原則部分
可以說每種設計模式都是為了讓代碼迎合其中一個或多個原則而出現的, 它們本身已經融入了設計模式之中,給面向對象編程指明了方向。
1.單一職責原則
一個對象(方法)只做一件事情。
難點就是如何去分離職責,要明確的是,并不是所有的職責都應該一一分離。
一方面,如果隨著需求的變化,有兩個職責總是同時變化,那就不必分離他們。比如在 ajax 請求的時候,創建 xhr 對象和發送 xhr 請求幾乎總是在一起的,那么創建 xhr 對象的職責和發送 xhr 請求的職責就沒有必要分開。
另一方面,職責的變化軸線僅當它們確定會發生變化時才具有意義,即使兩個職責已經被耦 合在一起,但它們還沒有發生改變的征兆,那么也許沒有必要主動分離它們,在代碼需要重構的 時候再進行分離也不遲。
優缺點
- 優點:降低了單個類或者對象的復雜度,按照職責把對象分解成更小的粒度, 這有助于代碼的復用,也有利于進行單元測試。當一個職責需要變更的時候,不會影響到其他的職責。
- 缺點:最明顯的是會增加編寫代碼的復雜度。當我們按照職責把對象分解成更小的粒度之后,實際上也增大了這些對象之間相互聯系的難度。
2.最少知識原則
最少知識原則要求我們在設計程序時,應當盡量減少對象之間的交互。如果兩個對象之間不 必彼此直接通信,那么這兩個對象就不要發生直接的相互聯系。常見的做法是引入一個第三者對 象,來承擔這些對象之間的通信作用。如果一些對象需要向另一些對象發起請求,可以通過第三 者對象來轉發這些請求。
中介者模式很好地體現了最少知識原則。通過增加一個中介者對象,讓所有的相關對象都通 過中介者對象來通信,而不是互相引用。所以,當一個對象發生改變時,只需要通知中介者對象 即可。
3.開放-封閉原則
軟件實體(類、模塊、函數)等應該是可以擴展的,但是不可修改。
開放-封閉原則的思想:當需要改變一個程序的功能或者給這個程序增加新功 能的時候,可以使用增加代碼的方式,但是不允許改動程序的源代碼。
舉的例子很有意思:
有一家生產肥皂的大企業,從歐洲花巨資引入了一條生產線。這條生產線可以自動 完成從原材料加工到包裝成箱的整個流程,但美中不足的是,生產出來的肥皂有一定的 空盒幾率。于是老板又從歐洲找來一支專家團隊,花費數百萬元改造這一生產線,終于 解決了生產出空盒肥皂的問題。
另一家企業也引入了這條生產線,他們同樣遇到了空盒肥皂的問題。但他們的解決 辦法很簡單:用一個大風扇在生產線旁邊吹,空盒肥皂就會被吹走。
淺顯易懂的講明了開放-封閉原則的好處
幾乎所有的設計模式都是遵守開放-封閉原則的,我們見到的好設計,通常都經得起開放?封閉原則的考驗。不管是具體的各種設計 模式,還是更抽象的面向對象設計原則,比如單一職責原則、最少知識原則、依賴倒置原則等, 都是為了讓程序遵守開放?封閉原則而出現的。可以這樣說,開放-封閉原則是編寫一個好程序的目標,其他設計原則都是達到這個目標的過程。
舉幾個模式, 來更深一步地了解設計模式在遵守開放?封閉原則方面做出的努力。
- 發布-訂閱模式
發布-訂閱模式用來降低多個對象之間的依賴關系,它可以取代對象之間硬編碼的通知機制, 一個對象不用再顯式地調用另外一個對象的某個接口。當有新的訂閱者出現時,發布者的代碼不 需要進行任何修改;同樣當發布者需要改變時,也不會影響到之前的訂閱者。 - 模板方法模式
模板方法模式是一種典型的通過封裝變化來提高系統擴展性的設計模式。在一個運用了模板方法模式的程序中,子類的方法種類和執行順序都是不變的,所以 我們把這部分邏輯抽出來放到父類的模板方法里面;而子類的方法具體怎么實現則是可變的,于是把這部分變化的邏輯封裝到子類中。通過增加新的子類,便能給系統增加新的功能,并不需要改動抽象父類以及其他的子類,這也是符合開放- 封閉原則的。
其實仔細想想,前端新興的框架如React,Vue中的組件的概念,就是典型的開放-封閉原則的例子
4. 接口和面向接口編程
通常提到接口有3種意思
- 一個庫或者模塊對外提供了某某 API 接口。通過主動暴露的接口來通信,可以隱 藏軟件系統內部的工作細節。
- 一些語言提供的關鍵字,比如 Java 的 interface。interface 關鍵字可以產生一 個完全抽象的類。這個完全抽象的類用來表示一種契約,專門負責建立類與類之間的聯系。
- 接口即是我們談論的“面向接口編程”中的接口,接口是對象能響應的請求的集合。(Objective-C中的protocol,是約定的方法和行為)
5. 代碼重構
設計模式目的就是為重構行為提供目標。
對重構提出的建議:
- 提煉函數
- 合并重復的條件片段
- 條件分支提煉成函數
- 傳遞對象參數代替過多的參數
- 少用三目運算符
- 分解大型類
- 用return退出多重循環
結束
看完本書之后,我的感受果然如作者所說,"這些名稱和原理原來不知道,但事實上一直都在使用。整個過程只不過是一個接一個的頓悟。"