前言:
設計模式不容易用文字描述清楚,而過多的代碼,看起來也讓人摸不到頭腦,加上詞語或者文字描述的抽象感,很容易讓人看了無數設計模式的文章,也仍然理解不了。 所以我一直打算寫此系列博客,首先我會從大量文章里去理解這些設計模式,最后我用自己的語言組織轉化為博客,希望用更少的代碼,更容易理解的文字,來聊一聊這些設計模式。 我所理解、所描述的每一個設計模式也可能有些是錯誤的,甚至也不一定有非常深刻的理解,所以希望有人指出,我可以更改博客內容。 因為我是前端,所以設計模式的代碼以前端代碼和視角為主。 此博客內容對每一種模式並不會寫得非常深入,也許能為讀者打通一些認知,如果看了此系列博客,再去看其他更深入的博客,可能是一種比較好的方式。
單例模式
單例是保證一個類只創建一個實例,實例不存在新建一個實例,實例存在返回已經存在的實例。 單例模式很好理解,使用情況也很多,比如我最近做的ng4的項目,會定義一些service(ng的service都是單例),service里面存放的數據供全局使用,所有組件共享這個service。 再說一個應用實例: 全局toast框,我們只需要每次調用同一個實例改變toast框里的文本,並控制其隱藏顯示。
工廠模式
下面是書中工廠模式的例子,關於它的實現和優劣我就不說了,這里只是以個人的理解來說為什么它叫工廠模式。 創建對象的時候,都會先新建一個新的原生對象,再對它進行屬性賦值,最后返回一個成品對象。所有屬性和方法都在它內部,都是它唯一擁有的。那么傳統工廠生產出來的電視手機等這些硬件,他們就是這種模式,他們的在出廠前就會在產品內部定義好所有的組件,最后加工為成品。所以我們就這樣來理解工廠模式的命名吧。
function createPerson(name, age, job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ alert(this.name); }; return o; } var person1 = createPerson("Nicholas", 29, "Software Engineer"); var person2 = createPerson("Greg", 27, "Doctor");
建造者模式
使用多個簡單的對象一步一步構建成一個復雜的對象。它使組裝過程和每個部件的開發分離開來。
直接舉個例子: 假如我們要設計一個h5飛機游戲,那么我們設計一款飛機的前端代碼,把飛機先簡單拆分為如下兩個部件: 機體、子彈。每個部件定義一個對象,然后設置它們的參數(比如飛機圖片地址,子彈設置傷害100的威力值和子彈圖片地址),最后通過組裝代碼邏輯來組裝成一架完整的游戲飛機。 那么飛機是一個復雜對象,這兩個部件就是更單純更簡單的對象。來點代碼示例:
function plane(){ this.buildBodyModule();
this.buildBulletModule(); } plane.prototype = { buildBodyModule: function(){ this.body = {
imgUrl:'xxx.png',
destoryImgUrl: 'xx2.png'
} }, buildBulletModule: function(){ this.bullet = {
imgUrl: 'xx3.png',
power: 100
} } }
當需求變動的時候,我們只需修改對應的單個部件,甚至可以隨時在移除或者添加其它部件。 所以這樣我們就大致能理解為什么它叫建造者模式了,現實生活中,一個復雜工程,就比如汽車、建築、飛機等,他們都是由不同的精細設計的部件通過合理的組裝才生產出成品的。 建造者模式和裝飾者模式看起來實際有點類似,下面就說裝飾者模式。
裝飾者模式
裝飾模式指的是在不必改變原類文件和使用繼承的情況下,動態地擴展一個對象的功能。它是通過創建一個包裝對象,也就是裝飾來包裹真實的對象。 其實裝飾者模式就是通過一個特定的方法,給一個實例對象添加特定的功能。比如一個基礎汽車類,有引擎、輪胎和車架,代碼如下:
function BaseCar(){} BaseCar.prototype = { engine: e300, tyre: t200, frame: f10 }
我們在車的基礎上,貼一些裝飾紙,以下代碼就是最基礎的裝飾者模式的使用。
var car = new BaseCar(); setStyle(car); setStyle(carObj){ carObj.style = '達康書記玻璃貼紙'; }
所謂裝飾者模式,就是通過一個外部的方法,來包裝一個對象,並且擴展它。
裝飾者模式 VS 建造者模式
實際裝飾者模式和建造者模式,都有類似分步的方式去建立一個對象。而建造者模式是在類的內部,一步步建造對象,很強調整體性和邏輯性,當然我上面的例子並沒有體現邏輯性之說,如果我們把飛機的機體再拆分一下,我們可能需要先建立機體的機艙(圖片),再根據機艙位置,放置或者說定位側翼和尾翼圖片位置,那么這就是所謂邏輯性了。 而裝飾者模式,是傳入對象到外部函數,通過函數體內部對對象進行擴展。其實通過外部函數,也能達到邏輯性的要求,通過外部函數的多次調用,讓對象添加和迭代功能。但是如果所有的組裝過程都是通過外部函數進行,在對象屬性和子對象的引用上可能會讓代碼變得非常復雜,甚至可能需要函數里返回對象,再用另一個函數包裹這類的函數嵌套,整體的邏輯從代碼層面看來也會變得復雜和難以理解。 對於裝飾者模式的使用,還是應該如它的名字一樣,就應該是為一個基礎功能完整的對象添加一些額外的擴展。 在面向對象編程的程序里,建造者模式和裝飾者模式應該是常會結合在一起使用,把上面的汽車例子,改編成一個更像這兩模式結合的代碼:
function BaseCar(){ this.buildFrame(); this.buildEngine(); this.buildTyre(); } BaseCar.prototype = { buildFrame: function(){ this.frame = 'f10'; //todo sth }, buildEngine: function(){ this.engine = 'e300'; //todo sth }, buildTyre: function(){ this.tyre = 't200'; //todo sth } } var car = new BaseCar(); setStyle(car); setStyle(carObj){ carObj.style = '達康書記玻璃貼紙'; }
外觀模式 門面模式

看了這個圖實際還不能徹底對它進行定性,當我再看到下面的代碼示例的時候,我就徹底清楚了外觀模式的定義:
function SubSystemOne(){ //xxx } function SubSystemTwo(){ //xxx } function SubSystemThree(){ //xxx } facade(){ SubSystemOne(); SubSystemTwo(); SubSystemThree(); }
外觀模式是把一系列邏輯封裝到一個方法中,使當前邏輯更易使用,更易維護。那么我們實際前端開發中,會常常用到,說一個前端開發中的例子: 一個后台管理系統,有一個用戶列表頁面,添加用戶和修改用戶信息,觸發添加或者修改按鈕的時候,都是使用的同一個彈窗模板,然后根據不同的傳參去判斷是否是新增或者修改,再去改變彈框模板表單里的數據,添加的時候所有表單為空,編輯的時候把之前數據載入到表單。那么這個彈窗模板就是門面、也就是上圖的Facade。表單的html代碼是不變的,沒有必要寫兩個模板,所以這也可以叫代碼去重。
再舉一個例子: 上面說的彈框模板的表單,在新增用戶成功的回調函數里我們需要把表單里的數據給重置了,代碼如下:
addUser(){ //獲取form數據, addModel 設置參數 addModel(function(){ //添加成功回調 this.nodes.$form.address.val(''); this.nodes.$form.cell.val(''); this.nodes.$form.name.val(''); }) }
這時編輯成功后也需要去執行上面回調里的重置代碼,我平時為了代碼去重,就需要寫一個單獨的重置方法,在兩個回調里調用,代碼如下:
addUser(){ //獲取form數據, addModel 設置參數 addModel(function(){ //添加成功回調 reset() }) } editUser(){ //獲取form數據, editModel 設置參數 editModel(function(){ //添加成功回調 reset() }) } function reset(){ this.nodes.$form.address.val(''); this.nodes.$form.cell.val(''); this.nodes.$form.name.val(''); }
reset方法就是Facade,甚至說有更復雜的需求的時候,reset方法還可以傳參數,根據參數來重置某幾個表單,總之reset方法把重置表單的邏輯封裝在此方法內,其他地方需要調用重置相關功能,都用經過它才行,這樣可以減少代碼的耦合性,去除很多重復代碼,后期維護也非常清晰、改動也方便。 JQ的 $ 選擇器,其實也是此模式,它專門處理DOM選擇,集合了id、class等等選擇器,我們只需要在傳參數的時候前面加上#或者.,$('#id'),$('.class'),就能選擇相應的dom。
結語:
其實設計模式並不是很神秘,很牛逼沖天的技巧,也許你在不經意間寫出的代碼就是一種模式,這些設計模式只是針對一些寫法做了定義和命名。 這是此系列博客的第一篇。我不知道自己描述出來的設計模式,是否能被廣大同行所接受或者讓人能看明白。 如果你覺得此博客對你有幫助,歡迎留言或者點擊推薦,更多的反饋和支持可能是我堅持寫下去的理由和動力!
此系列博客目錄:
子慕談設計模式系列(一)
子慕談設計模式系列(二)——設計模式六大原則
子慕談設計模式系列(三)