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的設計原則:

  1. 每個類的職責必須明確
  2. 每個類只承擔一項職責

避免過大的類,指那些能干一切事的類,他們就像上帝一樣,我們稱他們為God Object這里羅列一些GRASP(General Responsibility Assignment Software Pattern)原則:

  1. Creater
  2. Controller
  3. Pure Fabrication
  4. Information Expert
  5. High Cohesion
  6. Indirection
  7. Low Coupling
  8. Polymorphism
  9. 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判斷,那就應該考慮通過繼承的方式,讓不同的子類去處理.

寫著寫著就下班了,祝大家新年快樂!

 本文由用戶 tiantts 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!