JavaScript 設計模式 :用組合模式寫出複雜元件

莊文達發表於2017-10-17

組合模式

  • 什麼是組合模式
  • 生活中的組合模式
  • 組合模式的實際運用
  • 為什麼使用js繼承

js繼承文獻

官方: 組合模式,將物件組合成樹形結構以表示“部分-整體”的層次結構,組合模式使得使用者對單個物件和組合物件的使用具有一致性。掌握組合模式的重點是要理解清楚 “部分/整體” 還有 ”單個物件“ 與 "組合物件" 的含義。

  • 好了,你可以忽略我上面說的廢話,下面聽我BB。

傳說中的23中設計模式的命名者已經告訴大家這大概是一個什麼樣的套路,但我們以後要討論的不僅僅要了解,還可以找機會使用。

生活中的組合模式

生活中還是有很多的組合模式的:麥當勞套餐飯店美團X人套餐聯通流量包等等,他們把多個‘個體’組合成了一個‘整體’,這是生活中的例子,讓我用最近的實際小專案來說話。

舉個栗子:

最近在公司有一個小模組叫‘甘特圖’,github基本都是這樣的:

JavaScript 設計模式 :用組合模式寫出複雜元件

但是公司怎能要這種傳統的東西呢,必須要創新!!!具體有啥呢,傳統只有一條線,老闆:給我加兩條,一條預期,一條實際,要有mileStone(里程碑),加個總進度,加個....云云,信心滿滿的你是否被嚇到了呢,我個人實現了一份,不過可能使用組合模式會更好,我們下面簡單嘗試一下使用組合模式吧 ?。

首先,我們確定我們做一個元件名字就叫Gantt(肯德基)吧,元件包括什麼呢:分為這幾個小元件(套餐):畫圖套餐(包括:進度條,背景網格,內容補充),格式化套餐,加上一些簡單的工具類:獲得最長時間,日期格式化,只是給大家舉個栗子。

做好之後大概這個樣子 :

JavaScript 設計模式 :用組合模式寫出複雜元件
github開源地址

這裡做一下規範,下文中,我將把Gantt叫做(商店)小元件叫做(套餐),元件內原型方法叫做(成員)。

  • 先造一個繼承器輪子↓
 //建立一個屬於我們自己的Jquery,裡面只有inheritObject、inheritPrototype兩個方法,
    (function () {
    var util = {
        inheritObject: function (o) {//物件繼承封裝
            var F = function () {
            };
            F.prototype = o;
            return new F();
        },
        inheritPrototype: function (subclass, supperclass) {//原型繼承封裝
            var obj = this.inheritObject(supperclass.prototype);
            obj.constructor = subclass;
            subclass.prototype = obj;
        }
    };
    window.$ = window.util = util;
})(window);//把閉包變數弄到全域性

var Gantt = function (data) {
    this.ganttData = data;
    this.children = [];
    this.element = null;
}

Gantt.prototype = {
    init: function () {
        throw new Error('此方法必須子類重寫')
    },
    build: function () {
        throw new Error('此方法必須子類重寫')
    },

}

/**
 * 建立 Gantt外層容器
 * @param name
 * @param parent
 * @constructor
 */
var Container = function (name, parent) {
    Gantt.call(this);
    this.name = name;
    this.parent = parent;
    this.init();//構建子容器的基本點(id,dom,name)
}
$.inheritPrototype(Container, Gantt); //


Container.prototype = {
    /**
     *重寫父類init
     */
    init: function () {
        this.element = document.createElement('div');//建立一個div元素
        this.element.name = this.name;
        this.element.id = 'ganttView';
        this.parent.append(this.element);
    },
    /**
     *重寫父類build
     */
    build: function (child, text) {
        child.append(document.createTextNode(text)); //新增測試描述
        this.children.push(child);
        this.element.appendChild(child);
        return this;
    },
    getElement: function () {
        return this.element;
    },
    draw: function () {
        this.parent.appendChild(this.element)
    }
}
//呼叫方法
var ganttView = new Container('GanttView', document.body);
ganttView.build(document.createElement("div"), '左側詳情專案1')
    .build(document.createElement("div"), '左側詳情專案2').build(document.createElement("div"), '右側畫圖')
      
   
    ```
    
  
節約時間css就不寫了,
複製程式碼
float:left;
height,width,
color,backbround,
text:center.....腦補中...
```
複製程式碼

那麼我們注意,Container是個次級容器,是根據一個parent也就是最原始的'body'元素逐漸逐漸向裡面畫的,裡面還有更加複雜的內容(計算最大時間範圍,畫背景,新增日曆等等)

下面還應該有item裡面增加描述詳情畫圖等 當然描述,詳情,畫圖都只是‘套餐’,還是需要最底層的‘成員’來做地基的,成員是最基層的,他不再擁有子類,但是他們繼承了父類。

那麼我們整體修改一下程式碼:

  var Gantt = function (data) {
        this.ganttData = data;
        this.children = [];
        this.element = null;
    }

    Gantt.prototype = {
        init: function () {
            throw new Error('此方法必須子類重寫')
        },
        build: function () {
            throw new Error('此方法必須子類重寫')
        },

    }

    /**
     * 建立 Gantt外層容器
     * @param name
     * @param parent
     * @constructor
     */
    var Container = function (name, parent) {
        Gantt.call(this);
        this.name = name;
        this.parent = parent;
        this.init();//構建子容器的基本點(id,dom,name)
    }
    $.inheritPrototype(Container, Gantt); //


    Container.prototype = {
        /**
         *重寫父類init
         */
        init: function () {
            this.element = document.createElement('div');//建立一個div元素
            this.element.id = 'ganttView';
            this.element.textContent= this.name;
            this.parent.append(this.element);
        },
        /**
         *重寫父類build
         */
        build: function (child) {
//            child.append(document.createTextNode(text)); //新增測試描述
            this.children.push(child);
            this.element.appendChild(child.element);
            return this;
        },
        getElement: function () {
            return this.element;
        },
        draw: function () {
            this.parent.appendChild(this.element)
        }
    }

    /**
     * 建立 Gantt基礎成員
     * @param name
     * @param parent
     * @constructor
     */
    var Item = function (name, parent) {
        Gantt.call(this);
        this.name = name;
        this.parent = parent;
        this.init();//構建子容器的基本點(id,dom,name)
    }
    $.inheritPrototype(Item, Container); //

    Item.prototype = {
        /**
         *重寫父類init
         */
        init: function () {
            this.element = document.createElement('div');//建立一個div元素
            this.element.id = 'ganttItem';
            this.element.textContent = this.name;
//            this.parent.append(this.element);
//            return this.element;
        },
        /**
         *重寫父類build
         */
        build: function (text) {
            //可以再畫進度條,以及為進度條繫結點選事件
        },
        getElement: function () {
            return this.element;
        },
        draw: function () {
            this.parent.appendChild(this.element)
        }
    }

    //呼叫方法
    var container = new Container('GanttView', document.body);
        container.build(new Item('左側詳情專案1'))
                .build(new Item('左側詳情專案2'))
                
                ```
 
 ![](https://user-gold-cdn.xitu.io/2017/10/18/997cfd2b701a539045c4dad52aa460f3)
    
- 為什麼要使用`繼承`,賊麻煩.我直接`new`不行麼?

 “行,但是記住一點,我們使用了組合模式就要保障介面的統一,這也是[物件導向程式設計](https://juejin.im/post/58ff6374570c350058f489b5)的思想,類似`java`的`interface`介面,這樣我們處理回撥、異常更加方便,簡化了複雜的整體,又通過子類豐富了整體。“
    
我原來寫的甘特圖,是一次性生成的,如果產品提出,若使用`長輪循`監聽到伺服器,如果伺服器增加了一個任務,以動畫的形式動態新增一條進度條,這對於我以前的元件來說改動比較大,但是這個就很簡單了,可能我們加一個add原型方法繼承就好了。
    
- 那麼組合模式有啥好處?
    “使得專案更加模組化,一個元件可以無限向下分成各個套餐,直到到達最底層的成員,以原子粒度書寫程式碼,對於程式碼維護十分有利。”
    
    
開源的[Gantt外掛](https://github.com/pkwenda/gantt.js)是基於`Jquery`的擴充套件外掛,沒有用到svg等笨重的元件,只依賴jquery,只有14k,還有一些亮點沒有提交,當時寫的比較匆忙,現在最近想想也許當時考慮用組合模式更加適合於以後需求的變更。極大節約開發時間。
    
### 最後希望大家能寫出更加風騷的程式碼
 
複製程式碼

相關文章