使用 Dojo 模拟基于类的 OOP
在深入讨论 Dojo 的基于类的模拟之前,重要的是要注意到,到目前为止,Dojo 仍然是一个 JavaScript 库。Java 代码和 JavaScript 不是一回事;事实上,它们的差别很大。Dojo 并不试图迫使 JavaScript 像 Java 代码那样操作,相反,它允许 Java(和其他基于类的 OOP 语言)开发人员以一种他们熟悉的方式使用 JavaScript OOP,而底层结构仍然以一种原型方式工作。
使用 dojo.declare
创建类
要使用 Dojo 创建类,可以使用 dojo.declare
函数。现在,我们使用这个函数来创建一个 Car
类(见清单 9)。
清单 9. 使用 dojo.declare 创建一个 Car
类
dojo.declare("Car", null, {
});
var myCar = new Car();
console.log(myCar);
这是创建一个类并实例化该类的一个对象的基本 shell。dojo.declare
函数接受 3 个参数:
- 类名
- 类继承的超类
- 包含该类的所有属性和方法的一个对象
如您所见,在 Dojo 中创建一个类将默认向从该类生成的任何对象赋予一些属性和方法。您现在的类不怎么有趣,因此,我们来添加一些属性和方法,以及一个构造器,就像上一小节中演示面向原型的方法时所做的那样(见清单 10)。
清单 10. 一个更完整的 Car
类
dojo.declare("Car", null, {
reg_no: "",
current_speed: 0,
current_gear: 0,
constructor: function(reg_no) {
this.reg_no = reg_no;
},
accelerate: function(increment) {
this.current_speed += increment;
},
decelerate: function(decrement) {
this.current_speed -= decrement;
},
increaseGear: function() {
this.current_gear++;
},
decreaseGear: function() {
this.current_gear--;
}
});
继承性和多继承性
清单 12. 使用 Dojo 创建 ATCar
子类
dojo.declare("ATCar", Car, {
accelerate: function(increment) {
this.inherited(arguments);
if(increment >= 10) this.increaseGear();
},
decelerate: function(decrement) {
this.inherited(arguments);
if(decrement >= 10) this.decreaseGear();
}
});
如清单 12 所示,子类中只提供所有被覆盖的或新的属性和方法(在本例中,您只是覆盖加速和减速方法以自动换挡)。Dojo 负责自动调用超类中的构造器。如果您需要添加一个构造器函数,可以向子类添加一个构造器函数,但您不必担心调用超类构造器,因为那将自动进行。您将注意到,在两个被覆盖的方法中,行
this.inherited(arguments)
都被调用,这将调用超类中的相同方法。这将使您避免重新编写代码来执行实际加速,只需像自动挡汽车那样方便换挡即可。
Dojo 还支持多继承性。多继承性允许一个子类从多个父类派生,从每个父类继承属性和方法。严格说来,只有一个父类被认为是超类(数组中的第一个),但每个父类的构造器都将被调用,调用顺序与这些父类在数组中的顺序一致。
为演示多继承性,我们以一个 Smartphone 为例,除了接打电话和收发文本消息外,它还有很多功能(见清单 14)。通常,它应该还有播放音乐、观看视频等功能。为简单起见,我们假设一个 Phone 能打电话,一个 MediaPlayer 能播放视频,而一个 Smartphone 具有上述两个功能。
清单 14. Dojo 中的多继承性
dojo.declare("Phone", null, {
phone_number: "",
minutes_remaining: 0,
constructor: function(properties) {
this.phone_number = properties.phone_number;
this.minutes_remaining = properties.minutes_remaining;
console.log("Phone "+this.phone_number+" powered on. You have
"+this.minutes_remaining+" minute(s) remaining.");
}
});
dojo.declare("MediaPlayer", null, {
disk_space: 0,
songs:[],
constructor: function(properties) {
this.disk_space = properties.disk_space;
this.songs = properties.songs;
console.log("Media Player powered on. You have "+this.songs.length+" songs,
with "+this.disk_space+" GB free space left.");
}
});
dojo.declare("Smartphone", [Phone, MediaPlayer], {
phone_id: "",
constructor: function(properties) {
this.phone_id = properties.phone_id;
console.log("Smartphone ID "+this.phone_id+" boot up complete.");
}
});
var songs = [
{artist:"U2",title:"Vertigo"},
{artist:"Coldplay",title:"Yellow"}
];
var myPhone = new Smartphone({
<span style="color:#33CC00;">phone_number:"(555) 123-4567",
minutes_remaining: 60,
disk_space: 2.5,
songs: songs,
phone_id: "4345FDFD7JAPO76"</span>
});
console.log(myPhone);
这里值得指出的第一点是:
dojo.declare
是如何实现多继承性的。如您所见,一组类被传递,而不只是将父类作为第二个参数传递。这些父类的构造器将以它们在数组中的顺序自动被调用。重要的是要注意,如果每个父类构造器都接受不同的参数,那么 Dojo 将不能区分应该传递给每个构造器函数的参数。因此,如果您需要将不同的参数传递给不同的构造器,您应该在简单 JavaScript 中以
“键/值” 对的形式添加参数并在构造器中以那种方式使用它们。
使用 dojo.mixin
来改进多继承性示例
Dojo 提供了一个不错的工具函数 dojo.mixin
,它允许您通过从左到右合并对象属性来混合对象(见清单 16)。
清单 16. 一个基本 dojo.mixin 示例
var objA = { a: 1, b: 2 }; var objB = { b: 3, c: 4 }; dojo.mixin(objA, objB); console.log(objA);最初在
objA
中被设置为
2
的
b
属性已经被来自
objB
的值
3
所覆盖。而且,
c
属性已经被添加。这个基本示例完成后,我们来看看如何在您的多继承性示例中使用
dojo.mixin
。
在上一个示例中创建 Phone
类时,您可能会回想起清单 17 中那个类的构造器中的两行。
清单 17. Phone
类构造器中的行
this.phone_number = properties.phone_number;
this.minutes_remaining = properties.minutes_remaining;
由于只有两行,这还不太麻烦,但如果行比较多该怎么办呢?必须以这种方式分配属性难道不是一件很令人痛苦的事吗?而这正是 dojo.mixin
函数真正有用的地方!使用下面的行替换这两行(以及MediaPlayer
和 Smartphone
类中类似的行):dojo.mixin(this, properties);
。
结果与以前完全相同,但已经被传递到构造器的各个属性不会出现混乱。这很简洁,不是吗?
Dojo 中的打包和模块化开发
如果您来自一个 Java 开发环境,您可能更愿意遵循以下理念:不同的类应该驻留在不同的文件中,按照包进行分组。然后,当继承或其他目的需要时,再 “导入” 类,以确保它们仅在必要时才被加载。使用 JavaScript 时,没有这样的开箱即用打包和模块系统,但幸运的是,Dojo 提供了一个解决方案。
Dojo 通过 dojo.provide
和 dojo.require
函数提供了一个类似的打包系统。
清单 20. 在 Dojo 中打包类
dojo.provide("com.ibm.developerworks.dojoseries.Car");
dojo.declare("com.ibm.developerworks.dojoseries.Car", null, {
//Car class code goes here
});
清单 21. 在 Dojo 中导入类
dojo.provide("com.ibm.developerworks.dojoseries.ATCar");
dojo.require("com.ibm.developerworks.dojoseries.Car");
dojo.declare("com.ibm.developerworks.dojoseries.ATCar",
com.ibm.developerworks.dojoseries.Car, {
//ATCar class code goes here
});
尽管从技术上讲类名可以与
dojo.provide
语句中提供的路径不同(注意,使用
dojo.require
进行的任何类加载必须使用
dojo.provide
中设置的完全限定路径),但我们强烈建议不要这样做,因为这样只会导致混乱。