Brief
從Mix-In模式到Mixin模式,中文常用翻譯為“混入/織入模式”。單純從名字上看不到多少端倪,而通過采用Mixin模式的jQuery.extend我們是否可以認為Mixin模式就是深拷貝的代名詞呢?
本文試圖從繼承機制入手對Mixin模式進行剖析,若有紕漏請大家指正,謝謝。
The Fact of Inheritance
首先讓我們一起探討一下繼承機制吧。作為OOP的三個重要特性(Inheritance,Polymorphism,and Encapsulation)之一,繼承應該是和Encapsulation關系最緊密。
試想一下,現在我們得到需求分析后要對問題做概念模型設計,過程大概就是從具象化->抽象化->再具象化,而在抽象化時自然而然需要提取具象的共同點得到簡單直接的識別模型(如:廣東人啥都吃,外國教育就是好),而再具象化時則需要構建更為明確的含義更豐富的認知模型(廣東人有的吃貓肉,有的不吃),但它始終是識別模型的具象,也就始終依賴識別模型。
認知模型 多對多 識別模型,如 廣東人有的會做生意,有的喜歡打工(廣東人有的吃貓肉,有的不吃) 對 廣東人會做生意(廣東人啥都吃)
PS:認知模型、識別模型均為本人老作出來,識別模型就如文章title,略看后大概知道文章方向;認知模型如文章的content,細看才了解文章的含義。
The Diamond Problem from Multiple Inheritance
從上文了解到認知模型可對應多個識別模型,那么自然而然就需要多繼承了,而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] } }))
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
文章列表