Design Pattern: Not Just Mixin Pattern

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

时间: 2024-08-12 01:48:41

Design Pattern: Not Just Mixin Pattern的相关文章

Head First 之 Design Pattern(二):Observer Pattern

观察者模式是最常用的设计模式之一,[对象之间多对一的依赖关系,当一个对象发生变化时,其会通知所有依赖它的对象].拿订阅报纸和发行报社打比方,报社采集到news制作新的报纸,派送给订阅的客户,以此把最新的消息告知客户.所以,出版社 + 订阅者 = 观察者模式. 这种一对多的关系,也即"一个"主题."多个"观察者能够使得观察者仅仅了解主题推送的消息但不知晓其中的细节,而主题握有观察者列表但不干涉到观察者的个人隐私.所以,它们之间相互有交互,但不紧密,不清楚对方的细节.改

Head First 之 Design Pattern(一):Strategy Pattern &amp;&amp; 初入设计模式殿堂

头一回阅读<Head First 设计模式>,感觉语言生动形象,把一个个抽象的模式描述的浅显易懂,就像白话文一样. 正如作者推荐如何使用这本书. 你懂Java吗(不太懂... 会C++不影响吧)? 你想学习.了解.记得并应用设计模式,以及其所基于的OO设计原则吗(是的,我正在深入学习C++呢)? 你是不是更喜欢一种轻松愉悦的学习环境(absolutely)? 这本书正是我需要的~   "Head First"就是那些不断学习的人们,像他们致敬,共勉加油~ 使用设计模式最好的

正则表达式中pattern.match(),re.match(),pattern.search(),re.search()方法的使用和区别

正则表达式(regular expression)是一个特殊的字符序列,描述了一种字符串匹配的模式,可以用来检查一个串是否含有某种子串. 将匹配的子串替换或者从某个串中取出符合某个条件的子串,或者是在指定的文章中抓取特定的字符串等.Python处理正则表达式的模块是re模块,它是Python语言中拥有全部的正则表达式功能的模块.正则表达式由一些普通字符和一些元字符组成.普通字符包括大小写的字母.数字和打印符号,而元字符是具有特殊含义 正则表达式大致的匹配过程是: 拿正则表达式依次和字符串或者文本

Learning JavaScript Design Patterns The Observer Pattern

The Observer Pattern The Observer is a design pattern where an object (known as a subject) maintains a list of objects depending on it (observers), automatically notifying them of any changes to state. When a subject needs to notify observers about s

Learning JavaScript Design Patterns The Module Pattern

The Module Pattern Modules Modules are an integral piece of any robust application's architecture and typically help in keeping the units of code for a project both cleanly separated and organized. In JavaScript, there are several options for impleme

Design Pattern: Observer Pattern

1. Brief 一直对Observer Pattern和Pub/Sub Pattern有所混淆,下面打算通过这两篇Blog来梳理这两种模式.若有纰漏请大家指正. 2. Use Case 首先我们来面对一个老到跌渣的故事,并以从未听说过Observer Pattern为前提. 假设要设计一个新闻订阅系统,新闻分为商业.体育和八卦3种,而查收终端有PC.移动终端等,后续还不断增加新闻种类和查收终端. 需求如上,下面我们根据OOD的方式来构建概念模型. 新闻 <- 分类新闻 终端 <- 分类终端

Decorator Design Pattern

Decorator pattern allows a user to add new functionality to an existing object without altering its structure. This type of design pattern comes under structural pattern. This pattern creates a decorator class which wraps the original class and provi

Prototype Design Pattern

Before we proceed to Prototype Design Pattern, We need to review on Clone() method in Java. The Object cloning is a way to create exact copy of an object. The clone() method of Object class is used to clone an object. The java.lang.Cloneable interfac

Flyweight Design Pattern

Flyweight pattern is primarily used to reduce the number of objects created decrease memory footprint increase performance. Flyweight pattern tries to reuse already existing similar kind objects by storing them and creates new object when no matching