论坛首页 综合技术论坛

无类语言的OOP(JavaScript描述)

浏览 28112 次
该帖已经被评为良好帖
作者 正文
   发表时间:2007-06-12  
FP
本文以 JavaScript 语言为例,介绍了无类面向对象语言中实现各种面向对象概念的方法。值得注意的是,下面所说的并非“奇技淫巧”,其中的大部分都是计算机科学家们在设计无类语言时就已经确立了的模式,少部分是我借鉴其它语言的经验已经对前辈们思想的理解给出了完备化技术。
阅读本文至少需要对 JavaScript 语言“特别”的对象机制以及函数的运行上下文有所了解。如果您还对 JavaScript 对象知之甚少,可以查看附件中我翻译的 ECMA262 v3 中 4.2.1 Object 这一节;如果对 Lambda 演算不了解,建议去看 SICP

一. 基础:
建立类。只需声明一个函数作为类的构造函数即可。
function Light (light) {
	//填充对象属性
	this.light = light ? light : 0
	this.state = false
	
	//对象方法。
	//放心,JavaScript 没傻到给每个对象都真去分配一个函数的地步
	this.turnOn = function () {
		this.state = true
	}
}

创建实例。通过下面的代码创建一个新的电灯:
new Light(100) instanceof Light
js> true

这个新的电灯现在是匿名的,接下来可以在任何表达式中使用它。当然,最常用的做法是把一个名字绑定上这个对象。
访问实例属性。
//访问属性
new Light(100).light
js> 100
anOnLight = new Light()
//调整属性
anOnLight.state = true

匿名类。顾名思义,这个类没有名字(精确的说是构造函数没有名字)。就像这样:
aLight = new (function (light){
	this.light = light ? light : 0
	this.state = false
)(90)

类属性;类函数。顾名思义,一个类自身绑定的属性、函数,被所有类的实例可见,但不可直接使用。
//类属性
Light.SIZE = 5
//类函数
Light.newInstence = function (arg) {
	//这么简单的 Factory 模式
	//this 指向函数运行所在名字空间的上级
	return new this(arg)
}

想利用实例使用类的属性用下面的办法。函数调用类似:
anOnLight.constructor.SIZE
js> 5

类方法。真正意义上的“方法”
Light.prototype.turnOff = function () {
	this.state = false
}
anOnLight.turnOff()
anOnLight.state
js> false


二. 进阶
单继承。一个类扩展另一个类的所有能力。
function PhilipLight (price) {
	this.price = price
}
//事实上是建立了一个匿名的 Light 实例,然后将其能力反映给 PhilipLight
//飞利浦灯泡的亮度默认为100。这种继承模式很有意思。
PhilipLight.prototype = new Light(100)
myLight = new PhilipLight(12)
myLight.price
js> 12
//类方法照用。对象方法也照用。
myLight.turnOn()
myLight.state
js> true

可以把单继承作为一个 Object 类的能力保留下来,如果不强求默认值的话:
//把那些垃圾的库抛在脑后,让它们见识见识什么叫优雅。
Object.prototype.extend = function (aClass) {
	this.prototype = new aClass
}
PhilipLight.extend(Light) //No problem

多继承。我可以很明白的说,JavaScript 办不到。因为想在单继承链上实现多继承是不可能的。不过,这并不是说 JavaScript 面向对象机制不能达到多继承那样的表现力:装饰模式、Mixin 这些更强大的机制都是能办到的。
Mixin。漂亮地实现 Mixin 的前提是访问拦截器(getter 和 setter)。JavaScript 1.6 之前没有这种东西,需要修改编程习惯——这不是我们想要的。JavaScript 1.7 中加入的只是对特定消息的访问拦截器(现已在出现在 1.5 C 实现中)支持所以我们只能稍微改变一下编程风格。先说明一下如何对某个对象应用其它类的函数。
泛型。JavaScript 1.5 中,我们可以用函数对象的 call() 方法或 apply() 方法对该对象应用来自其它类的函数:
//Light 也是一种商品
function Product (price) {
	this.price = price
	//买 num 件商品需要的钱
}
Product.prototype.buySetOf = function (num) {
	return this.price * num
}
//那么对于同样有 price 属性的飞利浦灯泡,我们可以这样计算买10个灯泡要多少钱:
Product.prototype.buySetOf.call(myLight, 10)
js> 120
//apply 的第二个参数是被 call 的参数列表
Product.prototype.buySetOf.apply(myLight, [10])
js> 120

类的半自动混合。
Object.prototype.mixin = function (aClass) {
	//这里用到的技术下文中讲解
	this.prototype.app = function (func, args) {
		//func 是消息字符串
		if (this[func] != undefined)
			return (this[func].apply(this, args))
		return (aClass.prototype[func].apply(this, args))
	}
}
PhilipLight.mixin(Product)
myLight = new PhilipLight(12)
myLight.app('buySetOf', [10])
js> 120

对象的半自动混合。对象当成另一个对象使用,类似的方法:
Object.prototype.able = function (anObject) {
	this.app = function (func, args) {
		//func 是消息字符串
		if (this[func] != undefined)
			return (this[func].apply(this, args))
		return (anObject[func].apply(this, args))
	}
}
//这个用法弱智了点,但确实能说明问题
myLight.able(new Product)
myLight.app('buySetOf', [10])
js> 120


三. 补完
这一章讲解 4P 的实现。
包(package)没什么好说的,通读一遍 Prototype.js,看看作者是如何使用 JavaScript 对象描述程序结构的,就什么都知道了。这可比什么 interface 强多了。
公有(public)权限。Pass.
受保护的(protected)权限。如果你使用了 JavaScript 对象来描述程序结构,那么,其中每个类中的函数会自然获得 protected 权限——因为,使用它们都需要包名或者 with 语句。
私有(private)权限。不像 Python 等等语言,它们事实上是不存在的私有权限;JavaScript 使用 Lambda 演算中的逃逸变量原理实现私有权限。换个例子:
function Desk (height) {
	//对于一个符合标准的实现,这里的 var 关键字可以省略
	var height = height ? height : 0
	var weight = 0
	//下面的东西对于 Java 程序员来说很熟悉 :)
	this.getHeight = function () {
		return height
	}
	this.setHeight = function (num) {
		height = num
	}
}
deak = new Desk(34)
deak.getHeight()
34
deak.setHeight(45)
deak.getHeight()
45
desk.height
ReferenceError line 1:desk.height is not defined

此时的 height 就是逃逸变量,从 Desk 函数中以作为对象上绑定的函数的环境上绑定的变量“逃”了出来(这句话有些拗口,不过的确如此)。对于直接由构造函数参数引入的变量,也可以作为私有属性。类似的,还可以有私有函数——直接将函数定义写入构造函数即可。

四. 小结
以 Self、JavaScript 为代表的无类语言在用函数式风格解释面向对象思想方面作出了巨大进步,无论是灵活性还是强大程度都不是那些关键字一大堆的语言可与之相媲美的。如果我有空,可能还会来介绍一点 E 语言方面的思想,那才是真正无敌的无类语言啊。
  • 4.2.1.zip (9.9 KB)
  • 描述: ECMA-262 翻译片段。
  • 下载次数: 614
   发表时间:2007-06-12  
lz好样的
E语言sounds good
0 请登录后投票
   发表时间:2007-06-13  
貌似很多开源包和很多人都实现了自己的extends/mixins或者类似的方法.有些在里面还干了一些非常magic的事情。

这个,大概是用基于prototype的语言来实现这些东西的困惑巴。因为没有在语言级别提供直接的支持,于是大家都百花齐放,或者说的难听一点就是各奔东西了。直接的结果是某些开源包互相之间有冲突,不能拿来一齐使用(我有近半年没用过javascript了,或者现状已经改变了?社区有了大一统的做法?)

另外,这里还差一个多态的实现,虽然简单,但称不上优雅。也一齐补全了吧
0 请登录后投票
   发表时间:2007-06-13  
第一个例子中在构造函数中创建的函数,很多javascript的书都说这样创建的每个对象都有自己的函数版本,比如这里的Light的每个对象都有自己的turnOn,楼主说“JavaScript 没傻到给每个对象都真去分配一个函数的地步“,这一点如何证明?
0 请登录后投票
   发表时间:2007-06-13  
charon 写道
貌似很多开源包和很多人都实现了自己的extends/mixins或者类似的方法.有些在里面还干了一些非常magic的事情。

这个,大概是用基于prototype的语言来实现这些东西的困惑巴。因为没有在语言级别提供直接的支持,于是大家都百花齐放,或者说的难听一点就是各奔东西了。直接的结果是某些开源包互相之间有冲突,不能拿来一齐使用(我有近半年没用过javascript了,或者现状已经改变了?社区有了大一统的做法?)

另外,这里还差一个多态的实现,虽然简单,但称不上优雅。也一齐补全了吧


javaeye有人做了个JSI,可以解决各开源js类库的冲突并共同使用
0 请登录后投票
   发表时间:2007-06-13  
well done,继续写一些好文章。

charon 写道
貌似很多开源包和很多人都实现了自己的extends/mixins或者类似的方法.有些在里面还干了一些非常magic的事情。

这个,大概是用基于prototype的语言来实现这些东西的困惑巴。因为没有在语言级别提供直接的支持,于是大家都百花齐放,或者说的难听一点就是各奔东西了。直接的结果是某些开源包互相之间有冲突,不能拿来一齐使用(我有近半年没用过javascript了,或者现状已经改变了?社区有了大一统的做法?)

另外,这里还差一个多态的实现,虽然简单,但称不上优雅。也一齐补全了吧


javascript应该算是小巧灵性。这些灵活就注定了他不太容易大规模工业生产。比如java。
为什么非要写的那么复杂呢?我在写javascript的时候,顶多就用到一些对象之类的,什么继承之类的考虑都不考虑。写java不也是一个接口,一个实现。连继承都很少有。(不要告诉我,我写的应用太小,哈哈)。所以,语言本身探讨可以,但不要过多吹毛求疵了。
0 请登录后投票
   发表时间:2007-06-13  
dogstar 写道

为什么非要写的那么复杂呢?我在写javascript的时候,顶多就用到一些对象之类的,什么继承之类的考虑都不考虑。写java不也是一个接口,一个实现。连继承都很少有。(不要告诉我,我写的应用太小,哈哈)。所以,语言本身探讨可以,但不要过多吹毛求疵了。


只能说你所做的应用或者说用来抽象和解决问题的方法需要的只是封装,比如说继承或者多态之类的OO特性本身就有点多余。
我看到过很多人用java写程序,只是因为项目组要求用java,而项目组要求用java,只是因为客户如此要求,或者公司策略如此,而客户或者公司的要求的原因,并非在于java是个OO的语言,而是因为java的跨平台部署特性。
写的应用的大小,用哪个语言,本身就和是不是OO没有关系。
0 请登录后投票
   发表时间:2007-06-13  
dennis_zane 写道
第一个例子中在构造函数中创建的函数,很多javascript的书都说这样创建的每个对象都有自己的函数版本,比如这里的Light的每个对象都有自己的turnOn,楼主说“JavaScript 没傻到给每个对象都真去分配一个函数的地步“,这一点如何证明?

没好好学编译原理或者没好好看 ECMA-262 吧?
看看下面这一段,摘自 ECMA-262 13 章的一段:
引用
Two uses of the FunctionBody grammar production are defined to be equated when one of the following is true:
    * Both uses obtained their FunctionBody from the same location in the source text of the same ECMAScript program. This source text consists of global code and any contained function codes according to the definitions in 10.1.2.
    * Both uses obtained their FunctionBody from the same location in the source text of the same call to eval (15.1.2.1). This source text consists of eval code and any contained function codes according to the definitions in 10.1.2

这一段确定符合这些条件的函数,必须被认为是同一个函数。下一段中的注释说明了这有什么用(前文在讲解创建函数的算法步骤):
引用
Step 1 allows an implementation to optimise the common case of a function A that has a nested function B where B is not dependent on A. In this case the implementation is allowed to reuse the same object for B instead of creating a new one every time A is called. Step 13 makes this optimisation optional; an implementation that chooses not to implement it will go to step 2.

两个字:优化。既然两个函数“相同”,那就保留一个呗!

PS: 多态对于 JavaScript 来说自动存在。看标准理解点运算符和访问运算符 [] 的行为(自动查找继承链);附件中也有一点浅显的介绍。从我写的 Mixin 代码中也能看出来:PhilipLight 不就是多个 price 属性吗?本来跟 Product 类要求的 price 并不相关,但照用不误。
0 请登录后投票
   发表时间:2007-06-13  
多谢,我确实没去读过ECMAScript的规范,对javascript的学习也仅限于纯粹介绍JS的书,被误导了!-_-
0 请登录后投票
   发表时间:2007-06-13  
Lich_Ray 写道

PS: 多态对于 JavaScript 来说自动存在。看标准理解点运算符和访问运算符 [] 的行为(自动查找继承链);附件中也有一点浅显的介绍。从我写的 Mixin 代码中也能看出来:PhilipLight 不就是多个 price 属性吗?本来跟 Product 类要求的 price 并不相关,但照用不误。


你这个例子中的price不算是多态,只能算是覆盖了。怎么说你的this.price只能有一个。

问题的关键是你需要找到父类的同名函数。比如父类中有methodA,子类中也复写了相同签名的methodA,通常的要求是子类的methodA在干自己的私活之前必须还能调用父类的methodA(最典型的是在构造函数中,但构造函数又是个特例,各种语言都会对它做一些特别的照顾).
当然,有很多种简单的方法可以做到这一点,但是都需要做额外(相对于基于Class的语言是不必要的)的工作。

而且,就继承而言,javascript中的分类也是五花八门,也许拿到一个js库,第一个任务就是理解它的继承模型http://ajaxpatterns.org/Javascript_Inheritance


0 请登录后投票
论坛首页 综合技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics