4.5 Constructor Functions (Take 2) (构造函数 第二部分)
我们几乎已经完成了Box类的例子。Box类已经有了可以调用的方法,并且可以检查和改变属性。现在让我们回到Box的构造函数,这个问题我们在本章前面的“构造函数 第一部分(Constructor Functions (Take 1))”中介绍过。
假设我们想使我们创建的每个Box实例的初始宽度width和高度height都是1。在实例创建过程中,我们需要某种事物可以设置每个新的Box实例的width和height属性。这种事物就是我们所说的构造函数constructor function。
构造函数不负责创建新实例,而是用于初始化每一个新实例。当我们使用new操作符创建一个类的新实例时,此时该类的构造函数开始运行。在构造函数中,我们可以通过设置其属性或调用方法来制定最新创建的实例。
在一个类体中,我们使用function语句来定义一个构造函数,准确地说我们是定义一个方法。然而,一个构造函数定义必须遵守以下规则:
? 构造函数名必须与其类名严格匹配(大小写敏感)。
? 构造函数名定义不可以指定返回值类型(即使Void也不可以)。
? 构造函数不能有返回值(return语句只用于退出函数之用,不可以指定任何返回值)。
? 构造函数定义不能包含static特性,但是可以使用public或private。
这里我们再来看一下我们早期创建的Box类的构造函数。第二行和第三行是(空)构造函数——构造函数体以及类的其余部分都被忽略了:
class Box {
public function Box ( ) {
}
}
在
ActionScript 2.0中,构造函数只用于初始化实例。构造函数不是强制性的。如以前我们所知,当一个类没有明确定义一个构造函数时,
ActionScript会自动提供一个缺省的构造函数,在新建类的实例时该构造函数不接受任何参数也不执行任何初始化。尽管这么做很方便,但作为一个最佳实践,总应该包含一个构造函数,即使它仅仅是一个空函数。使用空构造函数明确表明,该类设计不需要构造函数,而且应该附上一个注视会更有效。例如:
class Box {
// 空构造函数。该类不需要初始化。
public function Box ( ) {
}
}
注意,构造函数可以被声明为
public或者
private,就像普通方法一样。大多数构造函数都是公共的
public,但是有一些特别的类需要一个私有
private构造函数(相关的一例子,见第十七章)。声明为
private的构造函数不能直接初始化。例如,如果我们为我们的
Box类提供一个
private构造函数:
class Box {
private function Box ( ) {
}
}
当我们试图创建一个Box实例时:
编译器将产生以下错误:
不可以访问私有成员。
为了能够创建实例,具有私有构造函数的类必须提供一个创建以及返回实例的类方法。例如:
class Box {
// 私有构造函数
private function Box ( ) {
}
// 返回新实例的类方法
public static function getBox ( ):Box {
return new Box( );
}
}
// 使用:
var b:Box = Box.getBox( );
如果一个具有私有构造函数的类没有提供一个用于调用内部私有构造函数的公共类方法,你就不能初始化该类的对象。在下列情况中,你可以使用私有构造函数:
? 为了创建一个等价于Java风格抽象类的草稿(即,一个不能实例化的类,但是必须经过扩展才能使用,如第八章中所讨论的)
? 为了放置一些关于何时以及如何创建一个类实例的限制(例如,阻止一个程序创建多于一个类对象)
让我们来充实基本的Box构造函数,以便将1赋给每个新建的Box实例的width和height属性。为了清楚,我们同样展示width和height属性定义。按照惯例(但不是必需的),构造函数被放置在属性定义之后、方法定义之前。注意,构造函数声明不能包括返回值类型:
class Box {
private var width:Number;
private var height:Number;
public function Box ( ) {
// 初始化width和height
width = 1;
height = 1;
}
// 从此之后是典型的方法定义...
}
现在,我们每次创建一个新的
Box实例,其宽度和高度都被初始化为
1(否则,它们的默认值是
undefined)。这虽然很便利,但是却过于僵化。为了允许为每个
Box实例指定
width和
height属性,因此我们要为构造函数定义添加参数:
public function Box (w:Number, h:Number) {
// 使用传给参数w和h的值初始化width和height
width = w;
height = h;
}
在使用
new操作符创建对象时,构造函数的参数值被传入,如下所示:
new SomeClass(value1, value2,...valuen);
其中
SomeClass是被初始化的类名,
value1, value2, ...valuen是传给构造函数的值。例如,我们使用初始值
width为
2和
height为
3来创建一个新的
Box实例:
new Box(2, 3);
构造函数通常使用参数值来设置属性值,但是参数同样用来指定当一个实例被创建时应该发生什么。例如,对于
Chat类的构造函数可以包含一个参数
doConnect,该参数表明
Chat实例是否应该自动连接刚刚创建的聊天服务器。
class Chat {
public function Chat (server:String, port:Number, doConnect:Boolean) {
if (doConnect) {
connect(server, port);
}
}
}
4.5.1 Simulating Multiple Constructor Functions (模拟多构造函数)
不同于Java,对于一个单独的类,ActionScript不支持多构造函数(在Java中称为overloaded constructors)。在Java中,一个类根据不同的参数数目和类型来初始化实例。在ActionScript中,模拟该功能必需手动实现。例4-5,在我们Box类的基础上,展示了一种在ActionScript中模拟多构造函数的可能方式。
在例4-5中,Box构造函数将其工作委派给三个伪构造器方法,分别命名为boxNoArgs( )、boxString( )、boxNumberNumber( )。每个伪构造器的名字表明了参数的数目和数据类型(例如,boxNumberNumber( )定义了两个Number型的参数)。注意,这个例子中的伪构造器没有为它们的参数定义数据类型;在代码注释中会讨论这个异常。
例4-5 模拟重载构造函数
class Box {
public var width:Number;
public var height:Number;
/**
* Box构造器。将初始化委派给boxNoArgs( ), boxString( ), or boxNumberNumber( ).
*/
public function Box (a1:Object, a2:Object) {
// 正如我们前面学到的,arguments可以存储传给该函数的参数值
// 如果无参数传入构造函数,则调用boxNoArgs( )
// 如果传入一个字符串参数,则调用boxString( )
// 如果传入两个数字参数,则调用boxNumberNumber( )
if (arguments.length == 0) {
boxNoArgs( );
} else if (typeof a1 == "string")
// 在以下代码中,我们通常需要将a1强制类型转换为
// boxString( )方法第一个参数所需的类型(即,String)。
// 然而,ActionScript 2.0类型转换操作符不能用于String和Number数据类型,
// 很不幸,因此对于boxString( )和boxNumberNumber( )的参数
// 我们无法进行类型转换。
// 更多关于类型转换的细节请见第三章。
boxString(a1);
} else if (typeof a1 == "number" && typeof a2 == "number") {
// 没有强制类型转换为Number,见前面注释
boxNumberNumber(a1, a2);
} else {
// 显示方法使用不当的警告
trace("Unexpected number of arguments passed to Box constructor.");
}
}
/**
* 无参数构造器
*/
private function boxNoArgs ( ):Void {
// arguments.caller是对于调用该函数的函数的引用
// 如果Box构造器没有调用该方法,则退出
if (arguments.caller != Box) {
return;
}
// 为width和height提供缺省值
width = 1;
height = 1;
}
/**
* 字符串构造器
*/
private function boxString (size):Void {
// 如果Box构造器没有调用该方法,则退出
if (arguments.caller != Box) {
return;
}
// 根据描述字符串设置width和height
if (size == "large") {
width = 100;
height = 100;
} else if (size == "small") {
width = 10;
height = 10;
} else {
trace("Invalid box size specified");
}
}
/**
* 数字构造器
*/
private function boxNumberNumber (w, h):Void {
// 如果Box构造器没有调用该方法,则退出
if (arguments.caller != Box) {
return;
}
// 设置width和height的数字值
width = w;
height = h;
}
}
// 用法:
var b1:Box = new Box( );
trace(b1.width); // 显示: 1
var b2:Box = new Box("large");
trace(b2.width); // 显示: 100
var b3:Box = new Box(25, 35);
trace(b3.width); //显示: 25
4.5.2 Using this in Constructor Functions (在构造函数中使用this)
在构造函数体中,
this关键字引用新建的实例。只有从新建实例中引用方法时,我们才会在构造函数中使用
this。例如,以下代码使用
this解决了一个参数与属性的命名冲突:
public function Box (width:Number, height:Number) {
// 设置width属性(this.width)为width参数的值(width)
this.width = width;
//设置height属性(this. height)为height参数的值(height)
this.height = height;
}
4.5.3 Constructor Functions Versus Default Property Values (构造函数vs.缺省属性值)
在本章前面我们学过了,一个实例属性可以被赋予一个缺省值,这个值必须是一个编译时常量表达式,如10或“hello world”。例如:
private var x:Number = 10;
private var msg:String = "hello world";
尽管通过为一个实例属性赋予一个缺省值来对其进行初始化是合法的,但最佳实践是,在一个构造函数中完成所有实例属性的初始化。构造函数不受编译时常量规则的限制,因此它们可以通过任意代码,例如方法调用、条件、循环等,安全地计算属性值。此外,通过在构造器中进行属性初始化,使得我们的类初始化代码更易查找和维护。