`
driftcloudy
  • 浏览: 132065 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

DOJO中的面向对象__第三章 Dojo中的多继承

    博客分类:
  • Dojo
阅读更多

(一) 定义多继承

  Dojo在基于类的面向对象系统方面增强了JS的表现力,在第二章中已经提到Dojo还允许用户使用多继承,本章将主要探讨关于多继承的内容。利用dojo.declare声明多继承的类是很方便的,用户只需要传递一个数组(superclass )进去,superclass数组包含了所有的父类。

dojo.declare("A", null, {
	constructor: function() { console.log ("A"); }
});
dojo.declare("B", null, {
	constructor: function() { console.log("B"); },
	text: "text B"
});
dojo.declare("C", null, {
	getText: function(){ return "text C" }
});
dojo.declare("D",[A,B,C],{
	constructor: function() { 
		console.log(this.text + " and " + this.getText()); 
	}
});
var d = new D();
// A
// B
// text B and text C

 

  该例声明了类型A、B、C、D,注意在声明类D的时候传入了superclass数组——[A, B, C],这使得D成为A、B、C的子类。运行上面代码会首先打印A,在执行A的构造器之后,其他基类的构造器也会按传入顺序被执行,D的构造器会被最终调用。同时,D也继承了A、B、C三个父类中其他域。

 

  事实上,A是D的唯一一个真正的父类,这是由于Dojo在实现多继承的时候,仅仅将A采纳为D的父类,其他的‘父类’仅仅会被mixin进A(具体细节可以参考第三章第四小节)。但抛开实现的细节来看,用户真正需要得到的结果是D是A、B、C这三个类的子类。因此,这时候采用JS中的instanceof运算符并不能很好的判断类型。拿上例来说:

d instanceof A	//true
d instanceof B	//false
d instanceof C	//false
d instanceof D	//true
 

  很显然,这不是用户想要的结果。为了应付在多继承环境下的类型判断,Dojo提供了类似的函数——isInstanceOf,方便用户进行类型判断。

d.isInstanceOf(A)		//true
d.isInstanceOf(B)		//true
d.isInstanceOf(C)		//true
d.isInstanceOf(D)		//true

 

(二) MRO与C3算法

  面向对象语言如果支持了多继承的话,都会遇到著名的菱形问题(Diamond Problem)。假设存在一个如左图所示的继承关系,O中有一个方法foo,被A类和B类覆写,但是没有被C类覆写。那么C在调用foo方法的时候,究竟是调用A中的foo,还是调用B中的foo?


  不同语言对这个问题的处理方式有所不同。例如C++中采用虚继承,而Python中采用MRO的方式来解决。MRO又称作方法解析顺序(Method Resolution Order),即查找被调用的方法所在类时的搜索顺序。在Python中一共出现过三种MRO,Dojo中采纳了Python2.3版本之后的MRO算法,该算法简称C3算法。

 

  C3算法简单来说是将一个类型以及它的所有父类进行线性化排列。之所以进行线性排列,其实是想让这些类按照某种重要程度排序,然后实际调用方法的时候,在这个线性序列中从前向后依次寻找,最靠前的方法才会被调用到。比如上面图片中的这个例子,在Python中可以描述为:

>>> O = object
>>> class A(O): pass
>>> class B(O): pass
>>> class C(A,B): pass
 

  对C进行C3算法,得到的结果表示为L(C)=CABO.这个结果看起来很像是广度优先搜索的结果,事实上它们之间是有点类似,但不总是相同。得到的线性化序列CABO保证了Python在调用方法的时候,C是第一个被搜索的,A总是优先于B被先搜索到。

写道
定义:
C(B1 ... BN) :C类的父类是B1…BN类
L(C(A,B)) :C以及C的所有父类的线性化排列
merge(A,B) :A和B的合并
CABO的头部:C
CABO的尾部:ABO
C3算法:
如果一个类没有父类,那么该类的线性化为:
L(C)=C
如果一个类有一个或多个父类,那么该类的线性化为:
L[C(B1 ... BN)] = C + merge(L[B1] ... L[BN], B1 ... BN)
merge的方法:
1. 取出merge中参数列表的第一个元素,取第一个元素的头部,如果该头部不在其他元素的尾部,则该头部合格,在merge列表中排除它,并把它当做结果的头部。
2. 如果该元素的头部在其他元素的尾部,则跳到该元素的下一个元素。取出该元素的头部,判断它是否合格。合格则在merge列表排除它并放入结果中,不合格则重复该步骤。
3. 最终直到所有的类都被删除,则merge成功,或者无法找到合格的头部,如果无法找到合格的头部,则merge失败并报出异常。
 

  上面细述了C3算法,注意我们在定义一个类的时候,传入这个类的父类的顺序直接决定了最后线性化结果的顺序。下面来看一个复杂一些的例子。

>>> O = object
>>> class F(O): pass
>>> class E(O): pass
>>> class D(O): pass
>>> class C(D,F): pass
>>> class B(E,D): pass
>>> class A(B,C): pass

 

这里有四层继承结构。我们从上到下逐层计算线性化序列:

写道
L[O] = O
L[E] = E+merge(L[O],O)
  = E+ merge(O,O)
  = EO
L[D] = DO
L[F] = FO
L[B] = B+merge(L(E),L(D),ED)
  = B+merge(EO,DO,ED)
  = BE+merge(O,DO,D)
  = BED+merge(O,O)
  = BEDO
L[C] = CDFO
L[A] = A+merge(L(B),L(C),BC)
  = A+merge(BEDO,CDFO,BC)
  = AB+merge(EDO,CDFO,C)
  = ABE+merge(DO,CDFO,C)
  = ABEC+merge(DO,DFO)
  = ABECD+merge(O,FO)
  = ABECDF+merge(O,O)
  = ABECDFO

  从L(A)=ABECDFO来看,最终A类对象调用方法时是按照ABECDFO的优先顺序来搜索的。利用C3算法计算的时候需要注意并不是所有的继承结构最后都能导出线性化的序列。C3算法的第三步骤允许我们失败。假设有下面这样的继承结构: 

>>> O = object
>>> class A(O): pass
>>> class B(O): pass
>>> class C(A,B): pass
>>> class D(B,A): pass
>>> class E(C,D): pass

 

对该继承结构计算线性化序列:

L[O] = O
L[A] = AO		
L[B] = BO
L[C] = CABO
L[D] = DBAO
L[E] = E+merge(L(C),L(D),CD)
       = E+merge(CABO,DBAO,CD)
      = EC+merge(ABO, DBAO,D)
      = ECD+merge(ABO, BAO)

 

  当进行到L[E] = ECD+merge(ABO, BAO)这一步时已经无法再进行下一步merge计算。所以对E利用C3算法失败。得到失败的结果也是合情合理的,从直观上讲,如果E的对象调用从A或B中继承来的方法,无法判断究竟该调用A中的还是B中的。由于是利用C(A,B)和D(B,A)这样来构建,所以没法得知A和B谁对E来说更加“重要” 。 

 

(三) Dojo中的MRO

  dojo中MRO的处理方式与Python有一点点小区别。Python在构建对象的时候传入父类列表,越靠前的类越容易被搜索到,代表着对新建的类越重要。反之,如果一个父类处在越高的继承层次上,则越不容易被优先搜索到。dojo中的MRO大体上可以参考上节中的描述。但是略有区别,描述如下:

写道
Dojo中的C3算法:
如果一个类没有父类,那么该类的线性化为:
L(C)=C
如果一个类有一个或多个父类,那么该类的线性化为:
L[C(B1 ... BN)] = C + merge(L[BN] ... L[B1])
//python中:
// L[C(B1 ... BN)] = C + merge(L[B1] ... L[BN], B1 ... BN)

 

  具体的区别已经在上面的算法描述中被标识出,可以看出,merge的参数不大一样,少了B1 ... BN序列,而且传入参数的顺序发生了变动。不过具体的merge做法与Python中一样。正是因为传入的参数顺序与Python中完全相反,造成了Dojo中有一种越是靠后的类越是被优先搜索到的趋势。

 

 

  下面举例来具体说明Dojo与Python中MRO的区别。假设有如左图所示的继承,分别计算MRO顺序:

 

 

  通过上述的例子可以发现,由于merge中传入参数的顺序不同,导致最终得出的MRO顺序不同。整体上Python倾向于一种类似广度优先搜索的顺序,而Dojo中的结果呈现出一种深度优先的搜索顺序,不过实际上并不是很准确。

 

  除了在整体上反映出不同的优先顺序,Dojo中的MRO做法实际上避免了许多MRO失败。在上一小节已经描述过一种情况,由于父类均是从同样的类型继承而来,但是继承的顺序不同,导致子类无法确定优先级关系,因此merge步骤失败。还有一种情况是,如果父类之间彼此也存在继承关系,那么同样会容易导致MRO失败,比如说下面所示的继承。

 

  如上图所示,C类的两个父类A和B之间发生了继承关系。在Python的MRO中,右边的一个继承关系是失败的。利用C3算法可以很快的推导出来。

写道
L[A] = A
L[B] = BA
L[C] = C+merge(L(A),L(B),AB)
  = C+merge(A,BA, AB ) // Python中的MRO失败了

 

  类似,左边的继承关系在Dojo中也应该是失败的。因为Dojo和Python中继承结构的线性化大体上是左右相反的。但实际上,无论是左边还是右边的继承关系,在Dojo中都是成功的。在Dojo中,分别针对左边和右边的继承进行MRO计算:

 

 

  在Dojo中左边的继承能够MRO成功,主要原因是merge时传入的参数比Python中少了父类型序列。如下所示:

写道
L[C(B1 ... BN)] = C + merge(L[BN] ... L[B1])       //除了L(BN)…L[B1]的顺序不同
L[C(B1 ... BN)] = C + merge(L[B1] ... L[BN], B1 ... BN )   //Python中还需传入父类型的序列

  如果B1 ... BN之间(即父类型之间)彼此不存在继承关系,那么是否传入父类型序列对merge的结果是不造成影响的。但是如果B1 ... BN之间存在了继承关系,那么merge的时候,B1 ... BN将会结果造成直接影响。不传入父类型的序列,这正是Dojo中能够成功避免一些MRO失败的原因,也可以说,Dojo中的MRO并不像Python中那么严格。


  Dojo中的MRO计算是通过c3mro函数来进行的,传入的参数是dojo.declare声明时的superclass数组。如果想知道c3mro实现的细节,可以参考第四章。

 

(四) mixin与多继承

  JS中的原型继承方式并不能支持多继承,因为每个构造器仅仅能指定一个原型对象,这是一种单继承形式,所以在Dojo中也仅仅是尽量去模拟多继承,而并非真正的多继承。故本章标题中采用的多继承字样是不准确的,准确的说,在Dojo中使用的是mixin与单继承结合的方式。只有一个类充当真正的父类,其余的类会被用于mixin。

 

  mixin是指将属性添加到指定的对象中,这是一种很常用的扩展对象的手段。mixin行为发生在两个对象之间,源对象(source)和目标对象(target)。大体来说,mixin会遍历source中的属性,并且添加到target中去。,如果在target中已经存在了同名的属性,那么需要在mixin中进一步判断,是否需要将这些同名属性覆盖。一个简单的mixin实现如下:

function mixin(target, source){
	for(name in source){
		target[name] = source[name];
	}
}

实际上Dojo中mixin的也类似于这样来实现,只是加了一些判断条件。

 

  在上一节中已经描述过Dojo中的MRO计算。在Dojo.declare进行处理的时候,首先对superclass进行MRO计算,并返回一个由构造器组成的数组。紧接着需要根据这个数组(序列),构建出原型链。该原型链中包含了所有数组中出现的构造器,包括在superclass中的和不在superclass中的。只有当这条原型链被构建好,关于继承所做的工作才真正完成。在构建原型链的过程中,Dojo不断的利用mixin与匿名函数的组合,模拟出多继承的实现。举例来说:

dojo.declare('A',null,{
	funA:function(){}
});
dojo.declare('B',null,{
	funB:function(){}
});
dojo.declare('C',null,{
	funC:function(){}
});
dojo.declare("D",[A,B,C],{}); 
new D();

 

  对于上述例子中的D类,传入的superclass为[A,B,C],计算出的MRO序列为:[C,B,A]。构造器A作为整个继承结构的最顶端,可以看做是D类的真正父类。至于B类、C类,都在构造原型链的过程中,被mixin进了某个匿名对象中。下面是构建后的继承图:

 

  利用dojo.declare声明的时候,只有一个类被当作父类,其余所有传入的类仅仅做mixin用。通常是superclass中的第一个类会被当做父类,即对于继承C(B1 ... BN),B1会被当做C的父类,不过这是有前提的,即L(C)的末尾完全由L(B1)构成。大部分情况下,这个前提都是可以满足的,但是也有不满足的情况,这时候所选取的父类就是L(C)中的最后一个类。举例来说:

 

  可以用JS提供的instanceof来判断是否是父类型的实例。而针对其他mixin的类型使用,则会失败,这时候可以用第二章中描述过的isInstanceOf函数。例如,对于上面的例一:

var f = new F();
console.log(f instanceof A );		//false
console.log(f.isInstanceOf(A)); 	//true
console.log(f instanceof B ); 		//false
console.log(f instanceof C ); 		//false
console.log(f instanceof E ); 		//false
console.log(f instanceof F ); 		//true
console.log(f instanceof D ); 		//true
 

  根据L(F)= FECBAD可以看出,类型F处于继承结构的最底端,而类型D是F的父类,处于最顶端。这两个类型都能够直接被instanceof识别,其余的ABCE都只能利用Dojo提供的isInstanceOf才能返回true。

 

 

 

 

 

 

 

 

0
0
分享到:
评论

相关推荐

    使用_Dojo_的_Ajax_应用开发进阶教程

    JavaScript使用原型链实现面向对象编程中的继承机制。 - **prototype**: 每个函数都有一个 `prototype` 属性,用于指定实例化对象的默认属性和方法。 - **__proto__**: 每个对象都有一个 `__proto__` 属性,指向它...

    dojo类机制实现原理分析

    此外,Dojo还支持多重继承,但需要注意的是,在多重继承中,只有父类列表中的第一个被视为真正的父类,其他父类则通过`mixin`的方式合并进子类的原型链。 #### 四、C3父类线性化算法 在多重继承中,不同父类间可能...

    geting started with dojo

    通过两天的学习,参与者不仅能够熟悉 JavaScript 的基础语法和面向对象编程,还能够深入了解并掌握 Dojo 框架的核心概念和技术细节。此外,课程中还包括了实用的工具介绍和丰富的实践环节,旨在帮助学员能够迅速上手...

    codingdojo:编码 Dojo 代码

    3. **面向对象编程**:Python是面向对象的语言,类和对象的概念是其核心部分。在Dojo中,可能会通过创建类来解决复杂问题,理解继承、封装和多态性。 4. **模块和包**:Python的模块和包系统允许组织和重用代码。...

    coding_dojo_group

    2. **面向对象编程**:Python是面向对象的语言,涵盖类的定义、对象的创建、继承、封装和多态等概念。 3. **标准库**:Python的标准库非常丰富,如os模块用于操作系统交互,sys模块处理系统依赖,math模块提供数学...

    dojo_ninjas:使用Python创建的Dojo和Ninjas

    8. **第三方库**:Python生态系统中有大量优秀的第三方库,如pygame用于游戏开发,pandas用于数据分析,numpy用于数值计算等,这些库可能会被用来增强"dojo_ninjas"的功能。 9. **测试与调试**:Python的unittest或...

    coding_dojo_assignment

    2. **面向对象编程**:类定义、继承、封装、多态等概念。 3. **错误和异常处理**:使用 try-except 来捕获和处理程序运行时可能出现的问题。 4. **标准库使用**:如 `os`、`sys`、`re`(正则表达式)等,以及特定...

    python-DOJO-:编码dojo python作业

    2. **Python进阶**: 进阶主题涵盖类和对象(面向对象编程基础,继承、封装、多态),异常处理(try-except语句),文件操作,模块化编程,以及标准库的使用,如os、sys、datetime等。 3. **Python标准库**: Python...

    麻雀虽小五脏俱全 Dojo自定义控件应用

    这是Dojo的一个亮点,它允许第三方开发者按照标准方式创建自定义控件,确保了控件的高度内聚性和面向对象的特性。每个Dojo控件都是一个JavaScript类,通常继承自_Widget或其子类,这些类提供了控件的生命周期管理...

    Python_March_2021:我的编码Dojo Python作业回购

    - **继承与多态**:子类继承父类的属性和方法,以及多态性在代码重用和扩展中的应用。 - **封装**:通过访问修饰符(public, private)实现数据的隐藏和保护。 3. **文件操作**: - **读写文件**:学习如何打开...

    CSharpDojo:C#个人编码Dojo

    C#是一种多范式、面向对象的编程语言,由微软公司开发,主要用于构建Windows应用程序、游戏、移动应用以及Web服务。在这个Dojo中,开发者或学习者可以通过一系列的编码挑战来提升自己的C#技能。 1. **基础语法与...

    first-java-project:我练习的progate.com Java Dojo中的第一个Java项目

    2. **面向对象编程**:类的定义、对象的创建、封装、继承、多态等核心概念。 3. **异常处理**:学习如何使用try-catch块来捕获和处理程序运行时可能出现的错误。 4. **输入/输出**:可能涉及到文件操作,如读取或...

    third-java-project:我的第三个Java项目来自progate.com Java Dojo II练习

    在本项目"third-java-...通过这个项目,开发者不仅可以提升Java编程技能,还能深入理解和应用面向对象编程、异常处理、文件操作、集合框架、多线程以及项目构建工具。这是一个绝佳的学习和提升个人Java技术的机会。

    Daily-Coding-Problems:每日编码挑战,以提高我的编程技能问题来自Cs Dojo NewsLetter。我受到图形艺术家Beeple的启发,在过去的12年中,他每天都在创作艺术。

    2. **面向对象编程**:理解类与对象的概念,如何定义类,以及类的方法、属性,继承、封装和多态性等面向对象的基本原则。 3. **异常处理**:使用try/except语句来捕获并处理程序运行时可能出现的错误,确保程序的...

    ProjectsAndAlgos:训练营项目和算法部分的编码Dojo类分配存储库!

    类提供了一种更面向对象的语法,但底层仍然是基于函数和原型的。 6. 算法:在"ProjectsAndAlgos"中,学员可能会接触到排序算法(如冒泡排序、快速排序、归并排序)、查找算法(如二分查找、哈希查找)、图算法(如...

    Ajax基础教程(扫描版)

    第3章 与服务器通信:发送请求和处理响应 37 3.1 处理服务器响应 37 3.1.1 使用innerhtml属性创建动态内容 37 3.1.2 将响应解析为xml 40 3.1.3 使用w3c dom动态编辑页面 45 3.2 发送请求参数 52 3.2.1 请求...

    SSH笔记_Struts2配置

    - Struts2有许多官方和第三方插件,如i18n、json、freemarker等,它们的配置在`struts-plugin.xml`中完成。 8. **Action与Service层整合** - Spring管理Service层对象,Struts2通过Spring插件注入Service,实现...

    EXT中文教程121212

    EXT采用面向对象的编程风格,大量使用了类(Class)和继承。 揭示源代码: EXT的源码阅读可以从主要的基类开始,如Ext.Element和Ext.Component,理解它们的生命周期和方法。此外,EXT的事件管理、布局管理和数据...

    Ajax.in.Practice.pdf

    - **面向对象编程(OOP)**:介绍如何使用JavaScript实现面向对象编程,包括类、继承等概念。 - **Prototype框架**:这是一个流行的JavaScript库,简化了DOM操作和Ajax交互,提供了丰富的功能集。 ### 开源Ajax工具包...

Global site tag (gtag.js) - Google Analytics