强!作者:Lich_Ray,转载请注明来源:http://lichray.javaeye.com/blog/89554
本文以 JavaScript 语言为例,介绍了无类面向对象语言中实现各种面向对象概念的方法。值得注意的是,下面所说的并非“奇技淫巧”,其中的大部分都是计算机科学家们在设计无类语言时就已经确立了的模式,少部分是我借鉴其它语言的经验已经对前辈们思想的理解给出了完备化技术。
阅读本文至少需要对 JavaScript 语言“特别”的对象机制以及函数的运行上下文有所了解。如果您还对 JavaScript 对象知之甚少,可以查看附件中我翻译的 ECMA262 v3 中 4.2.1 Object 这一节;如果对 Lambda 演算不了解,建议去看 SICP。
一. 基础:
建立类。只需声明一个函数作为类的构造函数即可。
2 // 填充对象属性
3 this .light = light ? light : 0
4 this .state = false
5
6 // 对象方法。
7 // 放心,JavaScript 没傻到给每个对象都真去分配一个函数的地步
8 this .turnOn = function () {
9 this .state = true
10 }
11 }
创建实例。通过下面的代码创建一个新的电灯:
2 js > true
这个新的电灯现在是匿名的,接下来可以在任何表达式中使用它。当然,最常用的做法是把一个名字绑定上这个对象。
访问实例属性。
2 new Light( 100 ).light
3 js > 100
4 anOnLight = new Light()
5 // 调整属性
6 anOnLight.state = true
匿名类。顾名思义,这个类没有名字(精确的说是构造函数没有名字)。就像这样:
2 this .light = light ? light : 0
3 this .state = false
4 )( 90 )
类属性;类函数。顾名思义,一个类自身绑定的属性、函数,被所有类的实例可见,但不可直接使用。
2 Light.SIZE = 5
3 // 类函数
4 Light.newInstence = function (arg) {
5 // 这么简单的 Factory 模式
6 // this 指向函数运行所在名字空间的上级
7 return new this (arg)
8 }
想利用实例使用类的属性用下面的办法。函数调用类似:
2 js > 5
类方法。真正意义上的“方法”
2 this .state = false
3 }
4 anOnLight.turnOff()
5 anOnLight.state
6 js > false
二. 进阶
单继承。一个类扩展另一个类的所有能力。
2 this .price = price
3 }
4 // 事实上是建立了一个匿名的 Light 实例,然后将其能力反映给 PhilipLight
5 // 飞利浦灯泡的亮度默认为100。这种继承模式很有意思。
6 PhilipLight.prototype = new Light( 100 )
7 myLight = new PhilipLight( 12 )
8 myLight.price
9 js > 12
10 // 类方法照用。对象方法也照用。
11 myLight.turnOn()
12 myLight.state
13 js > true
可以把单继承作为一个 Object 类的能力保留下来,如果不强求默认值的话:
2 Object.prototype.extend = function (aClass) {
3 this .prototype = new aClass
4 }
5 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() 方法对该对象应用来自其它类的函数:
2 function Product (price) {
3 this .price = price
4 // 买 num 件商品需要的钱
5 }
6 Product.prototype.buySetOf = function (num) {
7 return this .price * num
8 }
9 // 那么对于同样有 price 属性的飞利浦灯泡,我们可以这样计算买10个灯泡要多少钱:
10 Product.prototype.buySetOf.call(myLight, 10 )
11 js > 120
12 // apply 的第二个参数是被 call 的参数列表
13 Product.prototype.buySetOf.apply(myLight, [ 10 ])
14 js > 120
类的半自动混合。
2 // 这里用到的技术下文中讲解
3 this .prototype.app = function (func, args) {
4 // func 是消息字符串
5 if ( this [func] != undefined)
6 return ( this [func].apply( this , args))
7 return (aClass.prototype[func].apply( this , args))
8 }
9 }
10 PhilipLight.mixin(Product)
11 myLight = new PhilipLight( 12 )
12 myLight.app('buySetOf', [ 10 ])
13 js > 120
对象的半自动混合。对象当成另一个对象使用,类似的方法:
2 this .app = function (func, args) {
3 // func 是消息字符串
4 if ( this [func] != undefined)
5 return ( this [func].apply( this , args))
6 return (anObject[func].apply( this , args))
7 }
8 }
9 // 这个用法弱智了点,但确实能说明问题
10 myLight.able( new Product)
11 myLight.app('buySetOf', [ 10 ])
12 js > 120
三. 补完
这一章讲解 4P 的实现。
包(package)没什么好说的,通读一遍 Prototype.js,看看作者是如何使用 JavaScript 对象描述程序结构的,就什么都知道了。这可比什么 interface 强多了。
公有(public)权限。Pass.
受保护的(protected)权限。如果你使用了 JavaScript 对象来描述程序结构,那么,其中每个类中的函数会自然获得 protected 权限——因为,使用它们都需要包名或者 with 语句。
私有(private)权限。不像 Python 等等语言,它们事实上是不存在的私有权限;JavaScript 使用 Lambda 演算中的逃逸变量原理实现私有权限。换个例子:
2 // 对于一个符合标准的实现,这里的 var 关键字可以省略
3 var height = height ? height : 0
4 var weight = 0
5 // 下面的东西对于 Java 程序员来说很熟悉<img src="/images/forum/smiles/icon_smile.gif"/>
6 this .getHeight = function () {
7 return height
8 }
9 this .setHeight = function (num) {
10 height = num
11 }
12 }
13 deak = new Desk( 34 )
14 deak.getHeight()
15 34
16 deak.setHeight( 45 )
17 deak.getHeight()
18 45
19 desk.height
20 ReferenceError line 1 :desk.height is not defined
此时的 height 就是逃逸变量,从 Desk 函数中以作为对象上绑定的函数的环境上绑定的变量“逃”了出来(这句话有些拗口,不过的确如此)。对于直接由构造函数参数引入的变量,也可以作为私有属性。类似的,还可以有私有函数——直接将函数定义写入构造函数即可。
四. 小结
以 Self、JavaScript 为代表的无类语言在用函数式风格解释面向对象思想方面作出了巨大进步,无论是灵活性还是强大程度都不是那些关键字一大堆的语言可与之相媲美的。如果我有空,可能还会来介绍一点 E 语言方面的思想,那才是真正无敌的无类语言啊。