博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Design Pattern: Not Just Mixin Pattern
阅读量:6412 次
发布时间:2019-06-23

本文共 8785 字,大约阅读时间需要 29 分钟。

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() // 返回 fsjohnhuangjs.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} [zuper|mixins|members] - super class, mixin classes array or members of class     * @param {Array.|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://ywura.baihongyu.com/

你可能感兴趣的文章
利用Python进行数据分析(15) pandas基础: 字符串操作
查看>>
busybox inetd tftpd
查看>>
函数可重入性及编写规范
查看>>
Scribe应用实例
查看>>
一个通过BackgroundWorker实现WinForm异步操作的例子
查看>>
net中System.Diagnostics.Process.Start用法
查看>>
Ural_1090. In the Army Now (数状数组)
查看>>
Gridview中生成的属性rules="all",在Firefox出现内线框解决办法
查看>>
10容易实现基于Flash的MP3播放器为您的网站
查看>>
轻松实现QQ用户接入
查看>>
ToString精确到毫秒
查看>>
关于Android横竖屏切换的解决方法
查看>>
POJ_2184 Cow Exhibition (0-1背包)
查看>>
一段扫flash跨站的脚本
查看>>
算法洗脑系列(8篇)——第五篇 分治思想
查看>>
C++基本数据类型
查看>>
win7 64位下装office报1402的错误的解决方法
查看>>
iPhone开发资源汇总(更新中)
查看>>
PHP+七牛云存储上传图片代码片段
查看>>
【LeetCode】23. Merge k Sorted Lists
查看>>