在面向对象编程中,类B可以继承自另外一个类A。我们将A称为父类(superclass),将B称为子类(subclass)。B的实例从A继承了所有的实例方法。类B可以定义自己的实例方法,有些方法可以重载类A中的同名方法,如果B的方法重载了A中的方法,B中的重载方法可能会调用A中的重载方法,这种做法称为“方法链”(method chaining)。同样,子类的构造函数B()有时需要调用父类的构造函数A(),这种做法称为“构造函数链”(constructor chaining)。子类还可以有子类,当涉及类的层次结构时,往往需要定义抽象类(abstract class)。抽象类中定义的方法没有实现。抽象类中的抽象方法是在抽象类的具体子类中实现的。

在JavaScript中创建子类的关键之处在于,采用合适的方法对原型对象进行初始化。如果类B继承自类A,B.prototype必须是A.prototype的后代。B的实例继承自B.prototype,后者同样也继承自A.prototype。此外还会介绍类继承的替代方案:“组合”(composition)。

  • 定义子类

JavaScript的对象可以从类的原型对象中继承属性(通常继承的是方法)。如果O是类B的实例,B是A的子类,那么O也一定从A中继承了属性。为此,首先要确保B的原型对象继承自A的原型对象。通过inherit()函数,可以这样来实现:

B.prototype=inherit(A.prototype);//子类派生自父类
B.prototype.constructor=B;//重载继承来的constructor属性

这两行代码是在JavaScript中创建子类的关键。如果不这样做,原型对象仅仅是一个普通对象,它只继承自Object.prototype,这意味着你的类和所有的类一样是Object的子类。如果将这两行代码添加至defineClass()函数中,可以将它变成例9-11中的defineSubclass()函数和Function.prototype.extend()方法:

例9-11:定义子类

//用一个简单的函数创建简单的子类
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;
}
//也可以通过父类构造函数的方法来做到这一点
Function.prototype.extend=function(constructor,methods,statics){return defineSubclass(this,constructor,methods,statics);
};

例9-12展示了不使用defineSubclass()函数如何“手动”实现子类。这里定义了Set的子类SingletonSet。SingletonSet是一个特殊的集合,它是只读的,而且含有单独的常量成员。

例9-12:SingletonSet:一个简单的子类

//构造函数
function SingletonSet(member){this.member=member;//记住集合中这个唯一的成员
}
//创建一个原型对象,这个原型对象继承自Set的原型
SingletonSet.prototype=inherit(Set.prototype);//给原型添加属性
//如果有同名的属性就覆盖Set.prototype中的同名属性
extend(SingletonSet.prototype,{//设置合适的constructor属性constructor:SingletonSet,//这个集合是只读的:调用add()和remove()都会报错add:function(){throw"read-only set";},remove:function(){throw"read-only set";},//SingletonSet的实例中永远只有一个元素size:function(){return 1;},//这个方法只调用一次,传入这个集合的唯一成员foreach:function(f,context){f.call(context,this.member);},//contains()方法非常简单:只须检查传入的值是否匹配这个集合唯一的成员即可contains:function(x){return x===this.member;}
});

这里的SingletonSet类是一个比较简单的实现,它包含5个简单的方法定义。它实现了5个核心的Set方法,但从它的父类中继承了toString()、toArray()和equals()方法。定义子类就是为了继承这些方法。比如,Set类的equals()方法用来对Set实例进行比较,只要Set的实例包含size()和foreach()方法,就可以通过equals()比较。因为SingletonSet是Set的子类,所以它自动继承了equals()的实现,不用再实现一次。当然,如果想要最简单的实现方式,那么给SingletonSet类定义它自己的equals()版本会更高效一些:

SingletonSet.prototype.equals=function(that){
return that instanceof Set&&that.size()==1&&that.contains(this.member);
};

需要注意的是,SingletonSet不是将Set中的方法列表静态地借用过来,而是动态地从Set类继承方法。如果给Set.prototype添加新的方法,Set和SingletonSet的所有实例就会立即拥有这个方法(假定SingletonSet没有定义与之同名的方法)。

  • 构造函数和方法链

最后一节的SingletonSet类定义了全新的集合实现,而且将它继承自其父类的核心方法全部替换。然而定义子类时,我们往往希望对父类的行为进行修改或扩充,而不是完全替换掉它们。为了做到这一点,构造函数和子类的方法需要调用或链接到父类构造函数和父类方法。

例9-13对此做了展示。它定义了Set的子类NonNullSet,它不允许null和undefined作为它的成员。为了使用这种方式对成员做限制,NonNullSet需要在其add()方法中对null和undefined值做检测。但它需要完全重新实现一个add()方法,因此它调用了父类中的这个方法。注意,NonNullSet()构造函数同样不需要重新实现,它只须将它的参数传入父类构造函数(作为函数来调用它,而不是通过构造函数来调用),通过父类的构造函数来初始化新创建的对象。

例9-13:在子类中调用父类的构造函数和方法

/*
*NonNullSet是Set的子类,它的成员不能是null和undefined
*/
function NonNullSet(){//仅链接到父类
//作为普通函数调用父类的构造函数来初始化通过该构造函数调用创建的对象Set.apply(this,arguments);
}
//将NonNullSet设置为Set的子类
NonNullSet.prototype=inherit(Set.prototype);
NonNullSet.prototype.constructor=NonNullSet;//为了将null和undefined排除在外,只须重写add()方法
NonNullSet.prototype.add=function(){//检查参数是不是null或undefinedfor(var i=0;i<arguments.length;i++)if(arguments[i]==null)throw new Error("Can't add null or undefined to a NonNullSet");//调用父类的add()方法以执行实际插入操作return Set.prototype.add.apply(this,arguments);
};

让我们将这个非null集合的概念推而广之,称为“过滤后的集合”,这个集合中的成员必须首先传入一个过滤函数再执行添加操作。为此,定义一个类工厂函数(类似例9-7中的enumeration()函数),传入一个过滤函数,返回一个新的Set子类。实际上,可以对此做进一步的通用化的处理,定义一个可以接收两个参数的类工厂:子类和用于add()方法的过滤函数。这个工厂方法称为filteredsetSubclass(),并通过这样的代码来使用它:

//定义一个只能保存字符串的"集合"类
var StringSet=filteredSetSubclass(Set,function(x){return typeof x==="string";});//这个集合
类的成员不能是null、undefined或函数
var MySet=filteredSetSubclass(NonNullSet,function(x){return typeof x!=="function";});

例9-14是这个类工厂函数的实现代码。注意,这个例子中的方法链和构造函数链和NonNullset中的实现是一样的。

例9-14:类工厂和方法链

/*
*这个函数返回具体Set类的子类
*并重写该类的add()方法用以对添加的元素做特殊的过滤
*/
function filteredSetSubclass(superclass,filter){var constructor=function(){//子类构造函数superclass.apply(this,arguments);//调用父类构造函数};var proto=constructor.prototype=inherit(superclass.prototype);proto.constructor=constructor;proto.add=function(){//在添加任何成员之前首先使用过滤器将所有参数进行过滤for(var i=0;i<arguments.length;i++){var v=arguments[i];if(!filter(v))throw("value"+v+"rejected by filter");}
//调用父类的add()方法superclass.prototype.add.apply(this,arguments);};return constructor;
}

例9-14中一个比较有趣的事情是,用一个函数将创建子类的代码包装起来,这样就可以在构造函数和方法链中使用父类的参数,而不是通过写死某个父类的名字来使用它的参数。也就是说如果想修改父类,只须修改一处代码即可,而不必对每个用到父类类名的地方都做修改。已经有充足的理由证明这种技术的可行性,即使在不是定义类工厂的场景中,这种技术也是值得提倡使用的。比如,可以这样使用包装函数和例9-11的Function.prototype.extend()方法来重写NonNullSet:

var NonNullSet=(function(){//定义并立即调用这个函数var superclass=Set;//仅指定父类return superclass.extend(function(){superclass.apply(this,arguments);},//构造函数{//方法add:function(){//检查参数是否是null或undefinedfor(var i=0;i<arguments.length;i++)if(arguments[i]==null)throw new Error("Can't add null or undefined");//调用父类的add()方法以执行实际插入操作return superclass.prototype.add.apply(this,arguments);}});
}());

最后,值得强调的是,类似这种创建类工厂的能力是JavaScript语言动态特性的一个体现,类工厂是一种非常强大和有用的特性,这在Java和C++等语言中是没有的。

  • 组合vs子类

定义的集合可以根据特定的标准对集合成员做限制,而且使用了子类的技术来实现这种功能,所创建的自定义子类使用了特定的过滤函数来对集合中的成员做限制。父类和过滤函数的每个组合都需要创建一个新的类。

然而还有另一种更好的方法来完成这种需求,即面向对象编程中一条广为人知的设计原则:“组合优于继承”。这样,可以利用组合的原理定义一个新的集合实现,它“包装”了另外一个集合对象,在将受限制的成员过滤掉之后会用到这个(包装的)集合对象。例9-15展示了其工作原理:

例9-15:使用组合代替继承的集合的实现

/*
*实现一个FilteredSet,它包装某个指定的"集合"对象,
*并对传入add()方法的值应用了某种指定的过滤器
*"范围"类中其他所有的核心方法延续到包装后的实例中
*/
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");}}
//调用set中的add()方法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子类即可。可以利用这个类的实例来创建任意带有成员限制的集合实例。比如,不用上文中定义的NonNullSet类,可以这样做:

var s=new FilteredSet(new Set(),function(x){return x!==null;});
甚至还可以对已经过滤后的集合进行过滤:

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

  • 类的层次结构和抽象类

之前给出了“组合优于继承”的原则,但为了将这条原则阐述清楚,创建了Set的子类。这样做的原因是最终得到的类是Set的实例,它会从Set继承有用的辅助方法,比如toString()和equals()。尽管这是一个很实际的原因,但不用创建类似Set类这种具体类的子类也可以很好的用组合来实现“范围”。例9-12中的SingletonSet类可以有另外一种类似的实现,这个类还是继承自Set,因此它可以继承很多辅助方法,但它的实现和其父类的实现完全不一样。SingletonSet并不是Set类的专用版本,而是完全不同的另一种Set。在类层次结构中SingletonSet和Set应当是兄弟的关系,而非父子关系。

不管是在经典的面向对象编程语言中还是在JavaScript中,通行的解决办法是“从实现中抽离出接口”。假定定义了一个AbstractSet类,其中定义了一些辅助方法比如toString(),但并没有实现诸如foreach()的核心方法。这样,实现的Set、SingletonSet和FilteredSet都是这个抽象类的子类,FilteredSet和SingletonSet都不必再实现为某个不相关的类的子类了。

例9-16在这个思路上更进一步,定义了一个层次结构的抽象的集合类。AbstractSet只定义了一个抽象方法:contains()。任何类只要“声称”自己是一个表示范围的类,就必须至少定义这个contains()方法。然后,定义AbstractSet的子类AbstractEnumerableSet。这个类增加了抽象的size()和foreach()方法,而且定义了一些有用的非抽象方法(toString()、toArray()、equals()等),AbstractEnumerableSet并没有定义add()和remove()方法,它只代表只读集合。SingletonSet可以实现为非抽象子类。最后,定义了AbstractEnumerableSet的子类AbstractWritableSet。这个final抽象集合定义了抽象方法add()和remove(),并实现了诸如union()和intersection()等非具体方法,这两个方法调用了add()和remove()。AbstractWritableSet是Set和FilteredSet类相应的父类。但这个例子中并没有实现它,而是实现了一个新的名叫ArraySet的非抽象类。

例9-16中的代码很长,但还是应当完整地阅读一遍。注意这里用到了Function.prototype.extend()作为创建子类的快捷方式。

例9-16:抽象类和非抽象Set类的层次结构

//这个函数可以用做任何抽象方法,非常方便
function abstractmethod(){throw new Error("abstract method");}/*
*AbstractSet类定义了一个抽象方法:contains()
*/
function AbstractSet(){throw new Error("Can't instantiate abstract classes");}
AbstractSet.prototype.contains=abstractmethod;/*
*NotSet是AbstractSet的一个非抽象子类
*所有不在其他集合中的成员都在这个集合中
*因为它是在其他集合是不可写的条件下定义的
*同时由于它的成员是无限个,因此它是不可枚举的
*我们只能用它来检测元素成员的归属情况
*注意,我们使用了Function.prototype.extend()方法来定义这个子类
*/
var NotSet=AbstractSet.extend(function NotSet(set){this.set=set;},{contains:function(x){return!this.set.contains(x);},toString:function(x){return"~"+this.set.toString();},equals:function(that){return that instanceof NotSet&&this.set.equals(that.set);}}
);/*
*AbstractEnumerableSet是AbstractSet的一个抽象子类
*它定义了抽象方法size()和foreach()
*然后实现了非抽象方法isEmpty()、toArray()、to[Locale]String()和equals()方法
*子类实现了contains()、size()和foreach(),这三个方法可以很轻易地调用这5个非抽象方法
*/
var AbstractEnumerableSet=AbstractSet.extend(function(){throw new Error("Can't instantiate abstract classes");},{size:abstractmethod,foreach:abstractmethod,isEmpty:function(){return this.size()==0;},toString:function(){var s="{",i=0;this.foreach(function(v){if(i++>0)s+=",";s+=v;});return s+"}";},toLocaleString:function(){var s="{",i=0;this.foreach(function(v){if(i++>0)s+=",";if(v==null)s+=v;//null和undefinedelse s+=v.toLocaleString();//其他的情况});return s+"}";},toArray:function(){var a=[];this.foreach(function(v){a.push(v);});return a;},equals:function(that){if(!(that instanceof AbstractEnumerableSet))return false;//如果它们的大小不同,则它们不相等if(this.size()!=that.size())return false;//检查每一个元素是否也在that中try{this.foreach(function(v){if(!that.contains(v))throw false;});return true;//所有的元素都匹配:集合相等}catch(x){if(x===false)return false;//集合不相等throw x;//发生了其他的异常:重新抛出异常}}});/*
*SingletonSet是AbstractEnumerableSet的非抽象子类
*singleton集合是只读的,它只包含一个成员
*/
var SingletonSet=AbstractEnumerableSet.extend(function SingletonSet(member){this.member=member;},{contains:function(x){return x===this.member;},size:function(){return 1;},foreach:function(f,ctx){f.call(ctx,this.member);}}
);/*
*AbstractWritableSet是AbstractEnumerableSet的抽象子类
*它定义了抽象方法add()和remove()
*然后实现了非抽象方法union()、intersection()和difference()
*/
var AbstractWritableSet=AbstractEnumerableSet.extend(function(){throw new Error("Can't instantiate abstract classes");},{add:abstractmethod,remove:abstractmethod,union:function(that){var self=this;that.foreach(function(v){self.add(v);});return this;},intersection:function(that){var self=this;this.foreach(function(v){if(!that.contains(v))self.remove(v);});return this;},difference:function(that){var self=this;that.foreach(function(v){self.remove(v);});return this;}});/*
*ArraySet是AbstractWritableSet的非抽象子类
*它以数组的形式表示集合中的元素
*对于它的contains()方法使用了数组的线性查找
*因为contains()方法的算法复杂度是O(n)而不是O(1)
*它非常适用于相对小型的集合,注意,这里的实现用到了ES5的数组方法indexOf()和forEach()
*/
var ArraySet=AbstractWritableSet.extend(function ArraySet(){this.values=[];this.add.apply(this,arguments);},{contains:function(v){return this.values.indexOf(v)!=-1;},size:function(){return this.values.length;},foreach:function(f,c){this.values.forEach(f,c);},add:function(){for(var i=0;i<arguments.length;i++){var arg=arguments[i];if(!this.contains(arg))this.values.push(arg);}return this;},remove:function(){for(var i=0;i<arguments.length;i++){var p=this.values.indexOf(arguments[i]);if(p==-1)continue;this.values.splice(p,1);}return this;}}
);

子类的定义,与组合的比较相关推荐

  1. python定义一个类和子类_Python定义类、定义子类以及super()函数的使用

    代码实验展示: # 继承关系 object -> Person -> Student -> SchoolBoy # python2的语法: # class Person(object ...

  2. java 子类重定义变量_java子类对象和成员变量的隐写方法重写

    1.子类继承的方法只能操作子类继承和隐藏的成员变量名字类新定义的方法可以操作子类继承和子类新生命的成员变量,但是无法操作子类隐藏的成员变量(需要适用super关键字操作子类隐藏的成员变量.) publ ...

  3. java定义一个盒子类box_C++定义一个Box(盒子)类 看完你就知道了

    包括以下内容 1.私有成员(length,width,height); 2.构造函数:可设置length,width,height的初值: 3.成员函数volumn:计算并输出盒子的体积: 4:成员函 ...

  4. java定义一个盒子类box_定义一个Box(盒子)类,在该类定义中包括数据成员: length(长),width(宽)和height(...

    定义一个Box(盒子)类,在该类定义中包括数据成员: length(长),width(宽)和height( 定义一个Box(盒子)类,在该类定义中包括数据成员: length(长),width(宽)和 ...

  5. java定义一个盒子类box_定义一个Box(盒子)类,在该类定义中包括数据成员: length(长),width(宽)和height(...

    满意答案 szkcl 2014.05.28 采纳率:54%    等级:8 已帮助:964人 1234567891011121314151617181920212223242526272829#inc ...

  6. java定义子类_java定义类和子类中的方法

    展开全部 你好: 首先是62616964757a686964616fe78988e69d8331333335343337Parents类public class Parents { private S ...

  7. 4.(简答题,25.0分) (20分)(1)定义一个新冠病毒类Virus,定义一个德尔塔病毒类(Delta)和奥密克戎病毒类(Omicron),这两个类是新冠病毒类的子类;定义一个“可抵御地”接口,德

    这里写自定义目录标题 欢迎使用Markdown编辑器 新的改变 功能快捷键 合理的创建标题,有助于目录的生成 如何改变文本的样式 插入链接与图片 如何插入一段漂亮的代码片 生成一个适合你的列表 创建一 ...

  8. java父类静态 子类调用_在java 中 父类定义的静态方法 子类 调用时候 如何 知道 是哪个子类调用的...

    展开全部 静态方法 在定2113义的时候就已经5261定义好属于谁4102了,不可能发生改变.1653 子类 可以定义一个同名专方法,但不是重属载. 实例方法 可以获取到调用的是那一个类,比如下面例子 ...

  9. python基础——继承与派生、组合

    python基础--继承与派生 1 什么是继承: 继承是一种创建新的类的方式,在python中,新建的类可以继承自一个或者多个父类,原始类成为基类或超累,新建的类成为派生类或子类 1.1 继承分为:单 ...

最新文章

  1. 用C#创建Windows服务(Windows Services)
  2. Java8学习系列之匿名函数Lambda
  3. mysql 主从同步不一致_涨知识!MySQL 主从同步原理原来是这样的
  4. 给windows设置隐藏文件夹的方法
  5. MySQL删除s表命令_SQLServer数据库sql语句中----删除表数据drop、truncate和delete的用法...
  6. java多个页面爬取_java爬取html页面(简易通用版)
  7. python cli_click python cli 开发包
  8. android吸附菜单,Android仿微博、人人Feed详情页吸附导航栏
  9. mysql字段冲突报错
  10. android仿优酷菜单,Android编程实现仿优酷旋转菜单效果(附demo源码)
  11. 基于CAD二次开发的道路纵断面竖曲线计算原理与编程自动绘制方法(以C#为例)
  12. 苏州大学计算机考研复试机试,2014年苏州大学计算机考研经验
  13. 计算机内存一代,内存条一代二代三代的区别
  14. PING命令结果中的TTL是什么?
  15. Xbrowser远程登录Ubuntu闪退问题的解决方案
  16. 免费送5000多G之java,javaweb,python,大数据,区块链,安卓等的学习资源
  17. 阿里云服务器建站怎么上传文件?
  18. 算法基础题:木棍切割问题
  19. 面向对象基础任务训练分享
  20. c语言分蛋糕均匀正方形,分蛋糕(C - 二分查找)

热门文章

  1. MongoDB数据访问[C#]附源码下载(查询增删改) 转载
  2. python---面向对象,class参数、__init__方法、与函数区别
  3. 案例二:行走的小女孩
  4. 面对前端六年历史代码,如何接入并应用ES6解放开发效率
  5. Simulink 窄带陷波滤波器(Notch filter)仿真到代码生成
  6. [大话IT]~~~~闲话操作系统
  7. 1247. 交换字符使得字符串相同
  8. 虚拟主机升级成云服务器,虚拟主机升级成云服务器
  9. 《Adobe Illustrator CS5中文版经典教程》—第1课1.1节 概 述
  10. ros 框架解析-订阅部分