在JavaScript中有一个编程原则是“组合优于继承”,真的是这样吗?

作者:admin     字体:[增加 减小]    类型:原创
在很多JavaScript书中都说,编程的原则“组合优于继承”,实际情况真的是这样吗?灵活的JavaScript语言只有处理具体问题的灵活方法,绝没有包治百病的灵丹妙药,这就是JavaScript语言的个性。

在JavaScript中,有一个编程原则是“组合优于继承”。利用组合的原理定义一个新的类,它“包装”了另一个类对象。下面我们以前面讲的集合(请参考《超级好用的JavaScript集合类Set》)为例,来讲一下如何实现组合。

Set集合的类定义:

//集合类
//集合(set)是一种数据结构,用以表示非重复值的无序集合
function Set( ){
    this.values = {};
    this.n = 0;
    this.add.apply(this,arguments);
}

//将每个参数都添加至集合中
Set.prototype.add = function() {
    for ( var i=0;i<arguments.length;i++ ) {    //遍历每个参数
        var val = arguments[i];     //待添加到集合的值
        var str = Set._v2s(val);    //把它转换为字符串
        if ( !this.values.hasOwnProperty(str) ) {   //如果不在集合中
            this.values[str] = val; //将字符串和值对应起来
            this.n++;   //集合中值的计数加一
        }
    }
    return this;    //支持链式方法调用
}

//从集合删除元素,这些元素由参数指定
Set.prototype.remove = function() {
    var self = this;
    Array.prototype.map.call(arguments,function(e){ //遍历每个参数
        var str = Set._v2s(e);  //将字符串和值对应起来    
        if( self.values.hasOwnProperty(str) ) { //如果它在集合中
            delete self.values[str];        //删除它
            this.n--;   //集合中值的计数减一
        }
    });
    return this;    //支持链式方法调用
}


//如果集合包含这个值,则返回true,否则,返回false
Set.prototype.contains = function(value) {
    return this.values.hasOwnProperty(Set._v2s(value));
}

//返回集合的大小
Set.prototype.size = function() {
    return this.n;
}

//遍历集合中的所有幸免于难,在指定的上下文中调用f
Set.prototype.foreach = function(f,context) {
    for( var s in this.values ) {   //遍历集合中的所有键值
        if (this.values.hasOwnProperty(s))  //忽略继承的属性
            f.call(context,this.values[s]); //调用f,传入value
    }
}

//这是一个内部函数,用以将任意JavaScript值和唯一的字符串对应起来
Set._v2s = function( val ) {
    var self = this;
    switch( val ) {
        case undefined : return 'u';    //特殊的原始值
        case null:  return 'n';         //值只有一个字母
        case true:  return 't';
        case false: return 'f';
        default: switch( typeof val ) {
            case 'number': return '#' + val;    //数字都带有#前缀
            case 'string': return '"' + val;    //字符串都带有"前缀
            default: return '@' + objectId(val);    //Objs and funcs get @
        }   
    }

    function objectId(o) {
        var prop = "|**objectid**|";    //私有属性,用以存放id
        if (!o.hasOwnProperty(prop) ) { //如果对象没有id
            o[prop] = self._v2s.next++; //将下一个值赋给它
        }
        return o[prop];     //返回这个id
    }
}

Set._v2s.next = 1000;   //设置初始id的值

在Function.prototype上我们封装一个继承函数,该函数在《利用函数实现JavaScript的多层继承》中有也有用到。

//以构造函数为父类,在其基础上扩展子类
Function.prototype.extend = function(constructor,methods,statics){
    //用一个简单的函数创建简单的子类
    function defineSubclass ( 
        superclass,     //父类的构造函数
        constructor,    //新的子类的构造函数
        methods,        //实例方法:复制至原型中
        statics)        //类属性:复制至构造函数中
    {
        //建立子类的原型对象
        constructor.prototype = inherit(superclass.prototype);
        constructor.prototype.constructor = constructor;
        //像对常规类一样复制方法和类属性
        if ( methods ) extend(constructor.prototype,methods);
        if (statics) extend(constructor,statics);

        //返回这个类
        return constructor;
    }

    return defineSubclass(this,constructor,methods,statics);
}

使用组合代替继承的集合的实现

var FilteredSet = Set.extend(
    function FilteredSet(set,filter) {
        this.set = set;
        this.filter = filter;
    },
    {
        add: function() {
            if (this.filter){
                for(var i=0;i<arguments.length;i++){
                    var v = arguments(i);
                    if ( !this.filter(v) ){
                        throw new Error("FilteredSet: value " + v + " rejected by filter");
                    }                   
                }
            }
            this.set.add.apply(this.set,arguments);
            return this;            
        },
        remove: function(){
            this.set.remove.apply(this.set,arguments);
            return this;
        },
        contains: function(v) { return this.set.contains(v); },
        size: function() {return this.set.size();},
        foreach: function(f,c) { this.set.foreach(f,c);}    
    }
);

在这个例子中使用组合的一个好处是,只须创建一个单独的FilteredSet子类即可。可以利用这个类的实例来创建任意带有成员限制的集合实例。甚至还可以对已经过滤后的集合进行过滤。

var s = new FilteredSet(new Set(),function(x){return x!==null;});
var t = new FilteredSet(s,function(x){return !(x instanceof Set);});

////不能添加元素null,添加报错
//s.add(1,2,3,null);    //Uncaught Error: FilteredSet: value null rejected by filter
s.add(1,2,3);       

//不能添加集合类型的元素
//t.add(s);//Uncaught Error: FilteredSet: value [object Object] rejected by filter
t.add('测试');
console.log(s,t);

在JavaScript中只有更适合某种情况的解决方案,组合相较于继承,缺点也是很明显的。

在JavaScript中组合的缺点

从上图可以看出,我们创建的两个实例s与t其实指向的是同一个对象。