[NAME] ALL.dao.tutorial.class [TITLE] Dao Class and Object-Oriented Programming [DESCRIPTION] 道语言对面向对象编程(object-oriented programming, OOP)有良好的支持。 道语言类使用关键字 class定义。 一个类就是一组变量与函数的集合的抽象表示,这些变量和函数称为类的成员; 成员 变量的值将决定该类的特性,成员函数决定了该类的行为。 类本身是程序员定义的抽象类型,它的 值即实例就是一个具体的数据集合,此集合 包含了类定义的成员变量。 类实例可以以函数调用的方 式调用类的构造函数创建,也可通过枚举类成员变量来创建。 类成员的访问权限可由修饰词 private(私有), protected(保护) 和 public(公开)来限定,缺省权限为公开。 这些修饰词 后可跟也可不跟冒号。 类与类之间可存在继承和包含关系。一个类(子类)可定义为另一个类(基类)的引申。 子类可从 基类继承某些特性,在基类的基础上扩充或专门化某些功能。类也可以包含其他类 的实例为成员。 0.1 基类定义 类的定义由关键字class开始申明类的名称, 然后在由花括号所包围的类体里,申明类的成员常量, 变量 和方法等。 1 class MyNumber 2 { 3 private 4 5 var value = 0; 6 var name : string; 7 8 public 9 10 routine MyNumber( value = 0, s = "NoName" ){ 11 value = value; 12 name = s; 13 } 14 15 routine setValue( v ){ value = v } 16 routine getValue(){ return value } 17 18 routine setValue( v : float ); 19 } 20 21 routine MyNumber::setValue( v : float ) 22 { 23 value = v; 24 } 类定义体里使用关键字 var申明类的成员变量。 同普通变量类似,类成员变量也可按下面方式申明 为有固定类型, 1 var variable : type; 这里 type必须是一类型名,这样 variable的类型将固定为 type的类型; 或者 variable必须是 type的实例,如果 type也是类的话。 类的成员变量还可拥有缺省值。在类创建时,有缺省值的成员变量将被首先以缺省值填充。 类的成 员变量的缺省值可按以下方式指定, 1 var variable = init_value; 2 var variable : type = init_value; 这里 init_value也必须是一常量。 在道语言里,类的定义体也是类的构造函数。和其他某些脚本语言里的类构造函数类似, 道语言里 的类构造函数并不是真正用来构造类实例,而是用来初始化类的成员数据。 道语言里,类可拥有多 个重载的构造函数,用来根据不同的构造函数调用参数以不同的方式初始化类实例。 例子, 1 class MyNumber( value = 0, name = "NoName" ) 2 { 3 private 4 my Value = 0; # value类型为整形,缺省值为零 5 my Name : string; # name必须是字符串 6 7 Value = value; 8 self.Name = name; 9 10 public 11 routine setValue( value ){ Value = value } 12 routine getValue(){ return Value } 13 14 routine setValue( value : int ); 15 } 16 17 routine MyNumber::setValue( value : int ) 18 { 19 Value = value; 20 } 在类的构造函数或成员函数里,可使用一特殊变量 self,它表示类的当前实例。 与C++里的类成员函数定义类似,类的成员函数可在类定义体内申明,然后在类体外定义。 不过在道 语言里,类体外定义的成员函数的参数表必须和申明时的完全一致。 Dao里,虚拟函数也可用关键词 virtual来申明。 如果一个类成员函数里没使用类成员变量,且没调用其他使用了类成员变量的成员函数, 那么此成 员函数可按如下方式调用, 1 classname.method(...) 0.2 类实例 新的类实例可以由调用该类的构造函数得到,构造函数的调用方式跟普通函数调用完全一样。 1 obj1 = MyNumber(1); 类实例也可通过枚举类的成员变量创建,这种实例创建方式主要适合于比较简单的类,因为这种不需 要 复杂操作进行初始化。 1 obj2 = MyNumber{ 2, "enumerated" }; 在枚举成员变量时,可指定成员的名称, 1 obj3 = MyNumber{ 2 Name => "enumerated"; 3 Value => 3; 4 }; 当类实例由枚举创建时,类实例先被成员变量的缺省值填充,然后使用枚举中的数据初始化相应的成 员变量。通过枚举创建类实例比通过调用类构造函数创建类实例要快得多,调用类构造函数有一系列 额外开销,如参数传递,函数运行时数据的分配等。 枚举创建实例很适合于创建大量简单类的实例 。 0.3 类成员数据 正如上面提到,类的成员变量由关键字 var在类体内申明。 类的常量和静态成员可分别由 const和 static来申明。 1 class Klass 2 { 3 const aClassConst = "KlassConst"; 4 static aClassStatic; 5 } 0.4 Setters, Getters 和运算符重载 相对于给私有或保护的成员变量定义 setXyz()或 getXyz()方法, 更好的方式是定义所谓的Setters 和Getters,定义这样的方法使得从外部环境可以直接 对私有或保护的成员变量进行访问,就像访问 公开成员一样。 对于成员变量 Xyz其Setters应被定义为 .Xyz=(), 而其Getters应被定义为 .Xyz()。 当Setters被定义后 Xyz可由 obj.Xyz=abc设定; 而当Getters被定义后 Xyz可由 obj.Xyz 获取。 1 class MyNumber0 2 { 3 private 4 5 var value = 0; 6 7 public 8 9 routine MyNumber0( v = 0 ){ 10 value = v; 11 } 12 13 operator .value=( v ){ value = v; io.writeln( "value is set" ) } 14 operator .value(){ return value } 15 } 16 17 num = MyNumber0( 123 ) 18 num.value = 456 19 io.writeln( num.value ) 你也许已经猜到,通过Setters和Getters对类成员变量的访问会有比较大的运算开销。 因此为了效 率起见,需要从外部访问的成员变量应被设为公开。 只有在必要时才用Setters和Getters,比如当 你需要在那些成员变量被访问时作些额外工作时。 被支持重载的运算符还包括: 1. [operator =(...)] 用作赋值运算; 2. [operator ()(...)] 用作函数调用; 3. [operator [](...)] 用作取元素; 4. [operator []=(...)] 用作设定元素; 其运算符的重载将载后续版本中支持。 0.5 重载成员方法 类的成员方法可以象普通函数那样被重载。类的构造函数也可被重载,给类定义于类同名的 成员方 法即可,不过在重载的类构造函数中不可使用关键词 my来定义成员变量。 如类 MyNumber可作如下 修改使得它只可从数字或该类的实例构造和赋值, 1 class MyNumber 2 { 3 private 4 var value : int; 5 6 public 7 routine MyNumber( value = 0 ){ # accept builtin number as parameter 8 self.value = value; 9 } 10 # 接受MyNumber实例为参数的构造函数,类似于C++里的复制构造函数: 11 routine MyNumber( value : MyNumber ){ self.value = value.value } 12 13 operator .value=( v : int ){ value = v } 14 operator .value=( v : MyNumber ){ value = v.value } 15 operator .value(){ return value } 16 } 17 18 num1 = MyNumber( 123 ) 19 num1.value = 456 20 io.writeln( num1.value ) 21 22 num2 = MyNumber( num1 ) 23 io.writeln( num2.value ) 24 25 num2.value = 789 26 io.writeln( num2.value ) 27 28 num2.value = num1 29 io.writeln( num2.value ) 0.6 类继承 1 class ColorRBG 2 { 3 var Red = 0; 4 var Green = 0; 5 var Blue = 0; 6 7 routine ColorRBG( r, g, b ){ 8 Red = r; 9 Green = g; 10 Blue = b; 11 } 12 13 routine setRed( r ){ Red = r; } 14 routine setGreen( g ){ Green = g; } 15 routine setBlue( b ){ Blue = b; } 16 17 routine getRed(){ return Red; } 18 routine getGreen(){ return Green; } 19 routine getBlue(){ return Blue; } 20 } 21 22 yellow = ColorRBG( 255, 255, 0 ); # create an instance. 下面将定义 ColorRBG的一个派生类, 1 class ColorQuad : ColorRBG 2 { 3 var alpha = 0; # alpha component for tranparency. 4 5 routine ColorQuad( r, g, b, a ) : ColorRBG( r, g, b ){ 6 alpha = a; 7 } 8 } 9 10 yellow2 = ColorQuad( 255, 255, 0, 0 ); # not tranparent. 11 yellow2.alpha = 127; # change to half tranparency. 当从已有类派生新类时,基类必须放在派生类的参数列表后,并由 :隔开。 如果有多个基类,这基 类都应被放在 :后并被 ,隔开。 派生类构造函数的参数可按例子所显示的方式传递给基类的构造函 数。 定义派生类时,如果只有一个父类,派生类将同时继承父类的构造函数。