JavaScript面向對象的設計原則(二)
來自: http://www.cnblogs.com/jackyKin/p/5183463.html
這一期,我們來看下JavaScript面向對象設計中的核心--對象.面向對象的本質是將現實世界抽象層到計算機的世界,Class所體現的正是人類在觀察世界中所經常進行的一項工作,分類.
我們可能會經常整理屋子,然后將整理后的東西分門別類的放好.我們根據什么分類?電器,食物,書籍,這些屬性是我們分類的依據,這些不同類別的用品都有什么功能?食物可以吃,可以加工,書籍可以看,電器可以使用.抽象到計算機的世界,于是就有了class,property,method,類,屬性,方法.
所以我們日常中所進行的活動,很多都是分類的活動,你在工作中生活中所做的分類活動就是一種面向對象的設計行為,相對于類的概念,對象更具體,粒度更小,我們可以將具有一類共性的對象稱為類.
當一個對象很具體,我們就稱其為一個實例.
示例
var personInstance = { name:'jacky', age:60, six:'meal' }
當一個對象很抽象,我們可以稱其為類.
示例
var PersonClass = { name:'', age:'', six:'' }
這里我們沒有使用任何JavaScript模擬類的技術,也沒有用上ES6的class,但實際上這就是一個實例,一個類,從中我們可以看到,面向對象的設計其實是語言無關的,只要你能夠表達出其中的含義,如何實現,并不是重點.
JavaScript類設計
拋開各種模擬類的語法糖,讓我們直面本質來看看如何設計一個JavaScript類.
1.合理封裝
封裝的目的是找出代碼中的變化點,將其包裹起來,通過統一的具有語義的API接口來輸出結果,隱藏其中的實現和具體算法.
示例
function MenuTree () { this.dataSource = [], this.template = '<div id="menu-tree"><div>', this.element = null, this.init = function(domString){ var scope = this; scope.element = document.querySelector(domString); scope.render = function () { scope.element.innerHTML = scope.template; }; return scope; } } //for example new MenuTree().init(domString).render();
這里我們封裝了一個MenuTree的類,這是一個用來初始化樹形菜單的類,我們這么寫問題不大,當然假設需求也很簡單,模板是固定的,于是我們只要傳入一個domString來將一個dom節點變成一個MenuTree就完成了,對使用者來說,他看代碼就知道嗯,這個類使用init方法初始化,然后返回一個包含了render方法的對象,這個對象保存了對dom節點的引用,從而通過render方法來將模板寫入節點.不需要過多的文檔注釋,代碼能夠表達這個類的含義.
封裝也是我們對舊代碼不斷重構的利器,不斷的抽象出接口來推動軟件系統的升級,基于封裝的例子我們再來看看如何進行演化和重構.
隨著軟件的擴大,我們需要用到很多其他的類于是我們又設計了一個List類:
示例
function List () { this.dataSource = [], this.template = '<div id="list"><div>', this.element = null, this.init = function(domString){ var scope = this; scope.element = document.querySelector(domString); scope.onBeforeRender(); scope.render = function () { scope.element.innerHTML = scope.template; scope.onAfterRender(); }; return scope; }, this.onBeforeRender = function(){ code... }, this.onAfterRender = function(){ code... } }
然后你會發現List類和MenuTree類有相似的地方,但是需求上更復雜,需要在render前后做些邏輯處理,于是我們將其重構,對List和MenuTree進行一次抽象,從而得到一個UI類.
UI類示例
function UI(constructor) { this.dataSource = []; this.template = constructor.template || ''; this.element = null; this.onBeforeRender = constructor.onBeforeRender || function () { }; this.onAfterRender = constructor.onAfterRender || function () { }; } UI.prototype = { init: function (domString) { var scope = this; scope.element = document.querySelector(domString); scope.onBeforeRender(); scope.render = function () { scope.element.innerHTML = scope.template; scope.onAfterRender(); }; return scope; } };
List重構示例
function List() { UI.apply(this, arguments) } temp = List.prototype.constructor; List.prototype = UI.prototype; List.prototype.constructor = temp; var myList = new List({ template: '<h1>hello world</h1>', onBeforeRender: function () { //code... }, onAfterRender: function () { //code... } }); myList.init('ui-list').render();
MenuTree重構示例
function MenuTree(){ UI.apply(this, arguments); } temp = List.prottype.constructor; .... var myMenu = new Menu() ....
JavaScript重構的核心就是抽象出對象的共享屬性和方法,至于如何實現繼承,方法很多,ES6也提供了class的語法糖,大家可參考相關的文章和資料,但是核心在于如何設計抽象基類.
2.基于職責設計
類的職責值得是類實現的功能和承擔的數據處理任務
OOD的設計原則:
- 每個類的職責必須明確
- 每個類只承擔一項職責
避免過大的類,指那些能干一切事的類,他們就像上帝一樣,我們稱他們為God Object這里羅列一些GRASP(General Responsibility Assignment Software Pattern)原則:
- Creater
- Controller
- Pure Fabrication
- Information Expert
- High Cohesion
- Indirection
- Low Coupling
- Polymorphism
- Protected Variations
Expert/Information Expert原則:
某些功能需要某些必要的信息,這些信息在哪個類中,就讓這個類承擔這項職責
在一些開發場景中,我們可能需要構造一個購物車用來存放用戶選擇的商品,我們可以稱為ShoppingCart類
對于用戶選擇的商品數量,商品價格等信息,均應該歸屬ShoppingCart類而不是前置的Customer類.
Creator原則
JavaScript中類本身也是對象,不過函數對象與普通的實例對象并不相同.關于這個原則,我們只要確保代碼中構造對象的一致性即可
Conroller原則
前端對于MVC以及其各種變種應該是耳熟能詳了,尤其在web開發領域,基于時間驅動模型的MVC非常適合用來開發各種web應用,但我們可能很熟悉View和Model的概念,但是對于Controller可能就很難去定義了.
一般我們對于Controller代碼的編寫基于這幾點,所謂Controller僅僅是控制者,調度者的概念,對于調度物體的內部實現不應該關心也不應該去干預,Controller僅僅應當包含從View到Model的訪問代碼以及Model返回給View的數據代碼,不包含具體的業務邏輯,所謂業務邏輯,就是你的軟件功能.
示例
//view var indexView = app.createView({ template:'<div>{name}</div>', onBeforeRender:function(data){ //code.... return data; } }) //model var indexModel = app.createModel({ getData:$.post(url,function(){}) }) //controller app.createController('indexController',function(){ indexModel.getData().then(function(data){ indexView.setData(data) }) })
對于返回data的處理應當在indexView中的onBeforeRender中處理,而不應當去controller中ajax的回調中編寫.這就是controller原則.
High Cohesion/Low Coupling
高內聚低耦合,含義很深啊,自己體會吧,騷年們,不過建議還是有的,記住組合優先于繼承,那你的類就會高內聚低耦合.
Indirection(間接性)原則
對象之間相互關聯,會形成復雜的交互網絡,為減少耦合,通過創建一個中間對象將多對多的關系拆散為一對多關聯.
Pure Fabrication原則
當某些功能很難找到特定歸屬的業務邏輯相關的類時,就創建一個新類,這是一個折衷的辦法,折衷類我們稱為Pure Fabrication,就是生造出來的類,慣例我們稱為XXXXXService類.這也是實現低耦合高內聚的一種方法
Protected Variations原則
這個原則稱為隔離變化,簡單的說,當我們設計一個JavaScript類時應該考慮,如果上下文環境變換,這個類還能正常工作么?.簡單的例子,如果你在一個UI類中對dom結構強要求,那么就很容掛了.
Polymorphism 多態性原則
如果一個類中對于不同狀態的實現依賴于大量的switch和if/else if判斷,那就應該考慮通過繼承的方式,讓不同的子類去處理.
寫著寫著就下班了,祝大家新年快樂!