Design Pattern: Not Just Mixin Pattern

Brief                              javascript

  從Mix-In模式到Mixin模式,中文經常使用翻譯爲「混入/織入模式」。單純從名字上看不到多少端倪,而經過採用Mixin模式的jQuery.extend咱們是否能夠認爲Mixin模式就是深拷貝的代名詞呢?  html

  本文試圖從繼承機制入手對Mixin模式進行剖析,如有紕漏請你們指正,謝謝。java

 

The Fact of Inheritance                      node

  首先讓咱們一塊兒探討一下繼承機制吧。做爲OOP的三個重要特性(Inheritance,Polymorphism,and Encapsulation)之一,繼承應該是和Encapsulation關係最緊密。linux

  試想一下,如今咱們獲得需求分析後要對問題作概念模型設計,過程大概就是從具象化->抽象化->再具象化,而在抽象化時天然而然須要提取具象的共同點獲得簡單直接的識別模型(如:廣東人啥都吃,外國教育就是好),而再具象化時則須要構建更爲明確的含義更豐富的認知模型(廣東人有的吃貓肉,有的不吃),但它始終是識別模型的具象,也就始終依賴識別模型。es6

  認知模型 多對多 識別模型,如 廣東人有的會作生意,有的喜歡打工(廣東人有的吃貓肉,有的不吃)廣東人會作生意(廣東人啥都吃)ruby

  PS:認知模型、識別模型均爲本人老做出來,識別模型就如文章title,略看後大概知道文章方向;認知模型如文章的content,細看才瞭解文章的含義。app

 

The Diamond Problem from Multiple Inheritance      ide

  從上文了解到認知模型可對應多個識別模型,那麼天然而然就須要多繼承了,而C++和Python均支持這一語言特性。函數

  示例:

  

  D類爲B、C的派生類,A類有方法M,若C重寫方法M,若如今經過D類的實例調用方法M,那麼究竟是調用A類中的方法實現,仍是C類中的方法實現呢?這個就是著名的Diamond Problem。

  本質上是,對象在同一時間擁有多條繼承鏈,而且相同的成員簽名出如今>1條繼承鏈上,在無優先級或其餘約束條件的狀況下天然是找不到北的哦。

  Cons:1. 隨着項目規模發展,類間繼承關係愈發複雜,繼承鏈增多,容易發生Diamond Problem。

 

Single Inheritance Plus Multiple Interfaces         

  鑑於多繼承引發的問題,Java和C#、Ruby、Scala等後來者均 採用單繼承+多接口 的繼承機制。

  單繼承,致使對象沒法在同一時間擁有多條繼承鏈,從而防止Diamond Problem的發生。

  多接口,定義行爲契約和狀態(嚴格的接口僅含行爲契約),不含具體的行爲實現,表達like-a語義。

  但問題又隨之產生,在擼ASP.NET MVC時項目組通常都會在ConcreteController和Controller間加>=1層的BaseController,而後各類Logger、Authentication、Authorization和Utils均塞到BaseController裏面,而後美其名爲基(雞)類(肋)。這時你會發現BaseController中的成員(方法、字段)是無機集合,要靠#region...#endregion來劃分功能區間,而後隨着項目規模的擴大,各類狗死垃圾都往BaseController猛塞。那爲何不用Interface來作真抽象呢?那是由於Interface只能包含方法定義,具體實現則由派生類提供。BaseController的做用倒是讓派生類共享有血有肉的行爲能力,難道還有每一個ConcreteController去實現代碼徹底同樣的Logger嗎?

  Cons:1. 在須要行爲能力組合的狀況下顯得乏力。

  因爲上述問題,因此咱們在開發時建議 組合優於繼承,但若是組合是在BaseController上實現,那跟採用#region...#endregion劃分代碼片斷是無異的。咱們但願的是在ConcreteController直接組合Logger、Authentication等橫切面功能。爲何呢?由於不是全部橫切面功能都被ConcreteController所須要的,加入在BaseController中則增長冗餘甚至留下隱患。

 

Make Mixin Pattern Clear                    

  因爲Multiple Inheritance容易誘發Diamond Problem,而Single Inheritance Plus Multiple Interfaces則表達乏力,那麼能夠引入其餘方式完善上述問題呢?Mixin Pattern則是其中一種。

  首先找個實現了Mixin Pattern的而咱們又熟悉的實例,以便好好分析學習。很天然地我把目光投到了jQuery.extend函數,$.extend(target/*, ...args*/)會將args的成員(方法+字段)都拷貝到target中,而後target就擁有args的特性,後續處理過程當中外界就能夠將target當作args中的某個對象來用了。(Duck Type)

  好了如今咱們能夠提取一下Mixin Pattern的特色:

  1. Roles:Mixin原料(args)、Mixin對象(target);

  2. 將Mixin原料的成員(方法+字段)複製到Mixin對象中,而後Mixin對象就擁有Mixin原料的特性。

  是否是這樣就將Mixin Pattern描述完整了呢?固然不是啦,上面兩條僅能做爲初識時的印象而已。

  Mixin Pattern的真實面目應該是這樣的:

  1. Roles:Mixin Source & Mixin Target;

  2. Mixin Source將織入自身的全部成員(方法和字段)到Mixin Target;

  3. Mixin Source織入的方法必須具有實現,而不只僅是簽名而已; 

  4. Mixin Source 和 Mixin Target相互獨立。就是Mixin Target與Mixin Source互不依賴,Target即便移除Source的成員依然有效,而Source也不針對Target來設計和編碼;

  5. 若存在簽名相同的成員,後來者覆蓋前者仍是保留,仍是以其餘規則處理都是正常的;(對象的繼承鏈依然只有一條,所以若存在簽名相同的成員,其實仍是好辦的^_^)

  另外Mixin Pattern還細分爲 對類進行Mixin(Mixin Classes)對對象進行Mixin(Mixin Objects) 兩種實現形式

  Mixin Class

// 引入defc.js庫
/* 定義mixin source */
var mixins1 = {
  name: 'fsjohnhuang',
  getName: function(){return this.name}
}
var mixins2 = {
  author: 'Branden Eich', 
  getAuthor: function(){return this.author}
}

/*** 類定義時織入 ***/
var JS = defc('JS', [mixins1], {
  ctor: function(){},
  version: 1,
  getVersion: function(){return this.version}
})
// 實例化
var js = new JS()
js.getName() // 返回 fsjohnhuang
js.getVersion() // 返回1

/*** 類定義後織入 ***/
JS._mixin(mixins2 )
js.getAuthor() // 返回Branden Eich

  Mixin Class對類織入字段和方法,所以會影響到全部類實例 和 繼承鏈上的後續節點(既是其派生類)。

Mixin Object

// 引入defc.js庫
/* 定義mixin source */
var mixins1 = {
  name: 'fsjohnhuang',
  getName: function(){return this.name}
}
var JS = defc('JS')
/*** 對Object進行Mixin ***/
var js = new JS()
defc.mixin(js, mixins1)
js.getName() //返回fsjohnhunag

   Mixin Object對實例自己織入字段和方法,所以僅僅影響實例自己而已。

注意:Mixin Source實質爲字段和方法的集合,而類、對象或模塊等均僅僅是集合的形式而已。

上述代碼片斷使用的類繼承實現庫defc.js源碼(處於實驗階段)以下:

/*!
 * defc
 * author: fsjohnhuang
 * version: 0.1.0
 * blog: fsjohnhuang.cnblogs.com
 * description: define class with single inheritance, multiple mixins
 * sample:
 *   defc('omg.JS', {
 *     ctor: function(version){
 *         this.ver = verison    
 *     },
 *     author: 'Brendan Eich',
 *     getAuthor: function(){ return this.author }
 *   })
 *   var ES5 = defc('omg.ES5', 'omg.JS', {
 *        ctor: function(version){}
 *   })
 *   var mixins = [{isGreat: true, hasModule: function(){return true}}]
 *   var ES6 = defc('omg.ES6', ES5, mixins, {
 *        ctor: function(version){},
 *      getAuthor: function(){
 *            var author = zuper() // invoke method of super class which is the same signature
 *            return [author, 'JSers']
 *      }
 *   })
 *   var es6 = new ES6('2015')
 *   var es6_copy = new ES6('2015')
 *   assert.deepEquals(['Branden Eich', 'JSers'], es6.getAuthor())
 *   assert.equals(true, es6.isGreat)
 *   ES6._mixin({isGreat: false})
 *   assert.equals(false, es6_copy.isGreat)
 *   
 *   defc.mixin(es6, {isGreat: true})
 *   assert.equals(true, es6.isGreat)
 *   assert.equals(false, es6_copy.isGreat)
 */
;(function(factory){
        var require = function(module){ return require[module] }
        require.utils = {
            isArray: function(obj){
                return /Array/.test(Object.prototype.toString.call(obj))
            },
            isFn: function(obj){
                return typeof obj === 'function'
            },
            isObj: function(obj){
                return /Object/.test(Object.prototype.toString.call(obj))
            },
            isStr: function(obj){
                return '' + obj === obj
            },
            noop: function(){}
        }

        factory(require, this)
}(function(require, exports){
    var VERSION = '0.1.0'
    var utils = require('utils')

    var clazzes = {}

    /**
     * @method defc
     * @public 
     * @param {DOMString} clzName - the full qualified name of class, i.e. com.fsjohnhuang.Post
     * @param {Function|DOMString|Array.<Object>|Object} [zuper|mixins|members] - super class, mixin classes array or members of class
     * @param {Array.<Object>|Object} [mixins|members] - mixin classes array or members of class
     * @param {Object} [members] - members of class. ps: property "ctor" is the contructor of class
     * @returns {Object}
     */
    var defc = exports.defc = function(clzName, zuper, mixins, members){
        if (clazzes[clzName]) return clazzes[clzName].ctor
        var args = arguments, argCount = args.length

        members = utils.isObj(args[argCount-1]) && args[argCount-1] || {}
        mixins = utils.isArray(mixins) && mixins || utils.isArray(zuper) && zuper || []
        zuper = utils.isFn(zuper) && zuper || utils.isStr(zuper) && clazzes[zuper] && clazzes[zuper].ctor || 0 

        var clz = clazzes[clzName] = {}
        var ctor = clz.ctor = function(){
            // execute constructor of super class
            if (zuper) zuper.apply(this, arguments)
            // execute constructor
            members.ctor && members.ctor.apply(this, arguments)
            // contruct public fields
            for(var m in members) 
                if(utils.isFn(this[m] = members[m])) delete this[m]
        }
        ctor.toString = function(){ return (members.ctor || utils.noop).toString() }

        // extends super class
        if (zuper){
            var M = function(){}
            M.prototype = zuper.prototype
            ctor.prototype = new M()    
            ctor.prototype.contructor = members.ctor || utils.noop
        }

        // construct public methods 
        for(var m in members)
            if(m === 'ctor' || !utils.isFn(members[m])) continue
            else if(!(zuper.prototype || zuper.constructor.prototype)[m]) ctor.prototype[m] = members[m]
            else (function(m){
                // operate the memebers of child within the methods of super class
                var _super = function(self){ return function(){ return (zuper.prototype || zuper.constructor.prototype)[m].apply(self, arguments)} }
                var fnStr = members[m].toString()
                    , idx = fnStr.indexOf('{') + 1
                    , nFnStr = fnStr.substring(0, idx) + ';var zuper = _super(this);' + fnStr.substring(idx)
                
                eval('ctor.prototype[m] = ' + nFnStr)
            }(m))

        // do shallow mixins
        for(var mixin in mixins)
            for(var m in mixins[mixin]) ctor.prototype[m] = mixins[mixin][m]

        // additional methods
        ctor._mixin = function(/*...mixins*/){
            var mixins = arguments
            for(var mixin in mixins)
                for(var m in mixins[mixin]) this.prototype[m] = mixins[mixin][m]
        }

        return ctor
    }

    /**
     * @method defc.mixin
     * @public
     * @param {Any} obj - mixin target
     * @param {...Object} mixins - mixin source
     */
    defc.mixin = function(obj/*, ...mixins*/){
        var mixins = Array.prototype.slice.call(arguments, 1)
        for(var mixin in mixins)
            for(var m in mixins[mixin]) obj[m] = mixins[mixin][m]
    }
}))
View Code

 

Conclusion                          

  後續咱們將繼續探討C#和Java實現Mixin Pattern的方式,敬請期待,哈哈!

  尊重原創,轉載請註明來自:http://www.cnblogs.com/fsjohnhuang/p/4634039.html ^_^肥子John

 

Thanks                            

http://hax.iteye.com/blog/182339

http://cxyclub.cn/n/34324/

http://wiki.jikexueyuan.com/project/javascript-design-patterns/mixin.html

http://www.zhihu.com/question/20778853

https://en.wikipedia.org/wiki/Mixin

https://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem

http://codecrafter.blogspot.com/2011/03/c-mixins-with-state.html

http://codecrafter.blogspot.com/2010/02/c-quasi-mixins-example.html

http://stackoverflow.com/questions/6644668/mixins-with-c-sharp-4-0

http://www.sitepoint.com/ruby-mixins-2/

http://www.tutorialspoint.com/ruby/ruby_object_oriented.htm

http://www.ibm.com/developerworks/cn/java/j-diag1203/

http://www.linuxjournal.com/node/4540/print

相關文章
相關標籤/搜索