load help; @[name] dao.class @[name] @[title] 面向对象编程的类 @[title] @[text] 类是一用户定义的数据结构,此数据结构支持数据抽象,封装,多态和继承等。 一个类的定义主要由数据成员和方法成员构成。 这些数据成员和方法成员定义了这个类的实力对象的状态和行为。 它通常被用来作面向对象的编程。 @[text] @[name] dao.class.definition @[name] @[title] 定义 @[title] @[text] 道语言里类由关键词@[code]class@[code]定义。 这个关键词后要跟类名,然后是可能的基类名等,最后是类体。 下面是一个最简单的类定义: @[code] class SimplestClass { } @[code] 这个类没有基类,也没有成员数据和方法。 要定义一个由用的类型,成员数据或成员方法必不可少。 @[subsection] 成员数据和方法 @[subsection] 道语言类支持以下几种数据成员: @[list] --@[green]常量@[green]: 以@[green]const@[green]关键词声明; --@[green]静态变量@[green]: 以@[green]static@[green]关键词声明; --@[green]实例变量@[green]: 以@[green]var@[green]关键词声明; --@[green]实例定变量@[green]: 以@[green]invar@[green]关键词声明; @[list] 这些数据成员的声明可以有也可以没有类型标注,还可以有也可以没有初始化值。 这些类型标注和初始值声明跟函数的类型标注和缺省参数声明完全一样。 例如,下面的方式都可用来声明实例变量成员: @[code] var 变量; var 变量 = 初始值; var 变量: 类型名; var 变量: 类型名 = 初始值; @[code] 道类支持多种类型的成员方法,如构造方法,静态方法,实例方法 和操作符重载方法等。 操作符重载方法必须使用关键词@[code]operator@[code]定义, 其他方法须使用关键词@[code]routine@[code]。 类成员的可访问性可由三个权限关键词声明: @[list] --@[green]public@[green]: 公共,无限制访问; --@[green]protected@[green]: 保护,仅可从本类和子类访问; --@[green]private@[green]: 私有,仅可从本类访问; @[list] @[subsection] 类实例 @[subsection] 下面是一个没有构造方法的简单类: @[code] class Contact { const description = 'This is a class for contact information' var name = 'Nobody' var address = 'Nowhere' routine Show(){ io.writeln( name, 'lives at', address ) } } @[code] 这种简单的类支持两种实例构造方法: @[list] -- 调用其缺省构造函数以成员数据的缺省值初始化实例的成员数据: @[code] obj = Contact() @[code] -- 枚举类成员数据的初始值: @[code] obj = Contact.{ 'Mike', '123 Main Street' } @[code] 成员数据的名称可被指定,这样成员值的枚举可不按其定义顺序: @[code] obj = Contact.{ address = '123 Main Street', name = 'Mike' } @[code] 值得注意的是,仅不含基类和显式构造方法的类可以按这种方式构造实例。 @[list] @[subsection] 类构造方法 @[subsection] 类里方法名跟类名一样的成员方法将作构造方法处理。 当类以函数的方式被调用时,类的实例将被创建, 该实例将调用适当的构造方法作初始化。 下面的例子同上,但增加了个构造方法: @[code] class Contact { const description = 'This is a class for contact information' var name = 'Nobody' var address = 'Nowhere' routine Contact( name : string, address = 'Unknown' ){ self.name = name self.address = address } routine Show(){ io.writeln( name, 'lives at', address ) } } @[code] 现在调用该类的构造方法是生产该类实例的唯一方法: @[code] var obj = Contact( 'Mike', '123 Main Street' ) @[code] 类的构造方法也跟普通函数和方法一样可以根据参数类型重载: @[code] class Contact { const description = 'This is a class for contact information' var name = 'Nobody' var address = 'Nowhere' routine Contact( name : string, address = 'Unknown' ){ self.name = name self.address = address } routine Contact( another : Contact ){ name = another.name address = another.address } routine Show(){ io.writeln( name, 'lives at', address ) } } @[code] @[subsection] 静态方法 @[subsection] 静态方法可由在@[code]routine@[code]关键词前增加@[code]static@[code]关键词来实现。 这种方法可由类直接调用而不需要类实例,因此这种方法不能访问类的实例变量。 @[code] class Klass { var id = 123 static info = 'Klass' static routine StaticMethod(){ id = 1 } # Wrong; static routine StaticMethod(){ io.writeln( info ) } # Correct; } var obj = Klass::StaticMethod() @[code] @[text] @[name] dao.class.inheritance @[name] @[title] 类的继承 @[title] @[text] 道语言里,新类可由已存在的类(或C数据类型)作为基类派生出来, 新类将作为该基类的子类继承基类的一些数据和方法成员。 基类有时也被称作父类。 定义派生类时,基类的类名必须用冒号分割放在派生类名后面。 这里是个简单的例子: @[code] class Base { var id = 0 routine Base( i = 0 ){ id = i } } class Sub : Base { var value = 'abc' } var obj = Sub() @[code] 这个例子中,派生类的一个实例由该类的隐含的缺省构造方法产生。 该构造函数将自动调用基类的构造方法(隐含的或显式定义的)以初始化基类的成员数据。 当显式定义派生类的构造方法时,基类的构造方法可被显式地调用。 这种调用方式跟C++里的类似,就是用冒号分割,把基类构造方法的调用 放在派生类的构造方法的原型后面。 基类构造方法的调用可使用也仅可使用派生类构造方法里的参数, 和自动定义的@[code]self@[code]变量。 @[code] class Sub : Base { var value = 'abc' routine Sub( v = 'ABC', i = 3 ) : Base( i ) { value = v } } @[code] @[code] load meta class Base { var id = 0 routine VirtMeth(){ io.writeln( 'Base::VirtMeth()' ) } routine NormMeth(){ meta.self( self ).VirtMeth() # emulate virtual method call; } } class Sub : Base { var value = 'abc' routine VirtMeth(){ io.writeln( 'Sub::VirtMeth()' ) } } o : Base = Sub() o.NormMeth() # prints: Sub::VirtMeth() @[code] @[text] @[name] dao.class.operator @[name] @[title] 操作符重载 @[title] @[text] 如果一个用户定义的类重载了相关的运算符,那么它的实例将跟内置类型那样 直接使用操作符进行运算。 道类支持基本的算数运算符的重载。例如加法就可以通过定义一个@[green]operator+@[green] 方法来实现: @[code] class Integer { var value = 0 routine Integer( val = 0 ){ value = val } static operator +( A : Integer, B : Integer ){ io.writeln( 'Integer + Integer' ); return Integer( A.value + B.value ); } } var I1 = Integer( 123 ) var I2 = Integer( 456 ) var I3 = I1 + I2 @[code] 对类成员的访问也可以被定义。这种定义需要实现@[green]operator .field_name@[green] 和@[green]operator .field_name=@[green]方法: @[code] class Integer { private var value = 0 public routine Integer( val = 0 ){ value = val } operator .value(){ io.writeln( 'get value' ) } operator .value=( v : int ){ value = v; io.writeln( 'get value' ) } } var I = Integer( 123 ) var v = I.value I.value = 456 @[code] 有些类的用途类似容器类,它的数据需要以下标的方式访问。 这可由实现@[green][]@[green]和@[green][]=@[green]来实现。 @[code] class IntList { var ints = {} routine Append( value : int ){ ints.append( value ) } operator []( index : int ){ return ints[index] } operator []=( value :int, index : int ){ ints[index] = value } } var ilist = IntList() ilist.Append( 123 ) ilist.Append( 456 ) ilist[0] = 789 io.writeln( ilist[0] ) @[code] TODO: cast @[text] #{ In order to avoid creating new class instance whenever such addition operation is performed, one can define another addition method that can be used when the instance created by this operation is a temporary object and can be reused. This method will take the reusable temporary object as the first parameter, @[code] class Integer { var value = 0 routine Integer( val = 0 ){ value = val } static operator +( A : Integer, B : Integer ){ io.writeln( 'Integer + Integer' ); return Integer( A.value + B.value ); } static operator +( C : Integer, A : Integer, B : Integer ){ io.writeln( 'Integer = Integer + Integer' ); C.value = A.value + B.value; return C; } } var I1 = Integer( 123 ) var I2 = Integer( 456 ) for(var i = 1 : 3 ){ var I3 = I1 + I2 } @[code] #} @[name] dao.class.mixin @[name] @[title] 组件类 @[title] @[text] 新类的定义也可包含其它类,这些类将作为组件融入(嵌入)到新类中。 在类定义里,组件类可放在括号里列在新类名后面。 只有没有基类的类才可用作组件类。 @[code] class Base { var value = 456 routine Meth2(){ io.writeln( self, value ) } } class Mixin ( Base ) { var index = 123 routine Meth(){ io.writeln( self, index, value ) } routine Meth2( a : string ){ io.writeln( self, index, value, a ) } } # # The "Base" class will be presented only once in "Klass": # class Klass ( Base, Mixin ) { var index = 123456 routine Meth2( a : int ){ io.writeln( self, index, value, a ) } } var k = Klass() io.writeln( k.index ) k.Meth() k.Meth2() k.Meth2( 'abc' ) k.Meth2( 789 ) @[code] @[text] @[name] dao.interface @[name] @[title] 抽象接口 @[title] @[text] 抽象接口(Interface)是一种通过申明成员方法和操作符重载的方式 来抽象的定义一个对象支持的使用方式。 一个对象要与一个接口兼容,它必须包含该接口所定义的所有方法和操作符。 接口是一种抽象类型,因为它没法创建实例对象,而且它的所有成员方法 都是没有实现的抽象方法。 这里是个简单的可检查对象大小的借口: @[code] interface HasSize { routine size()=>int } @[code] 我们可以定义一个以该接口为参数的函数,这样我们就可以对任意支持 @[code]size()@[code]方法的对象调用该方法检查它的大小: @[code] routine PrintSize( object: HasSize ) { io.writeln( object.size() ) } @[code] 这个函数可以@[code]string@[code],@[code]list@[code] 或 @[code]map@[code]等为参数: @[code] PrintSize( 'hello world' ) PrintSize( { 1, 2, 3 } ); @[code] 接口也支持继承(包括多重继承): @[code] interface Resizable : HasSize { routine resize( size :int ) } @[code] 类似的: @[code] routine Resize( object: Resizable, size: int ) { io.writeln( 'old size:', object.size() ) io.writeln( 'new size:', size ) object.resize( size ) } var ls = {} Resize( ls, 5 ) io.writeln( ls ) @[code] 接口也支持操作符重载。但是,内置的类型和操作符不能通过接口检查, 因为它们不以方法的形式实现。 因此接口最主要还是用于道类类型和封装的C/C++类型。 定义相同集合的成员方法的借口可互换, @[code] interface HasSize2 { routine size()=>int } routine PrintSize2( object: HasSize2 ) { o :HasSize = object; # assign an object of "HasSize2" to a variable of "HasSize"; io.writeln( object.size() ) } PrintSize2( {} ); @[code] @[text]