[NAME] ALL.dao.tutorial.class [TITLE] Class, Mixin, OOP and AOP [DESCRIPTION] Object-Oriented Programming (OOP) is supported in Dao by offering class-based features such as data abstraction, encapsulation, polymorphism and inheritance etc. And such support for OOP is further enhanced by interface. 0.1 Class Definition A class is a user-defined data structure consisting data fields and member methods, which define the states and behaviours for the instances of the class. Class supports three types of fields: * constant: declared with keyword const; * static variable: declared with keyword static; * instance variable: declared with keyword var; Such fields can be declared with or without explicit types, and with or without default or initialization values, in the same way as specifying types and/or default values for function parameters. For example, the following can be used for instance variables, 1 var variable; 2 var variable = init_value; 3 var variable : typename; 4 var variable : typename = init_value; Class methods must be declared with keyword routine (or its alias keywords function or sub) for constructors and normal methods, or keyword operator for operator overloading. The access of class fields and methods can be restricted by three permission keywords: * public: publically accessible without restriction; * protected: accessible from the class and its derived classes; * private: only accessible from the class; Here is a simple class, 1 class ClassOne 2 { 3 var index = 0; 4 var name : string 5 var words : list<string> = {} 6 7 routine ClassOne( name :string, index = 0 ){ 8 self.name = name; 9 self.index = index; 10 } 11 } Within class methods, the special variable self represents the current class instance. Class methods may be declared inside class body and defined outside in the same way as in C++, but in Dao, one should make sure that, the parameter list must be exactly the same in the places for declaration and definition. 0.2 Class Instance Class constructors are the methods that have name the same as the class name. A class instance can be created by invoking a constructor of the class in the same way as a function call, 1 object = ClassOne( 'abc' ) Like in Python, the constructors are not used to create class instances, instead, an instance is created before, and then the constructor is called after to initialize the instance. For a class without parent classes and without constructors, its instances may also be created by enumerating the members of the class, 1 class Point3D 2 { 3 var x = 0D; 4 var y = 0D; 5 var z = 0D; 6 } 7 point = Point3D::{ 1, 2, 3 }; The names of instance variables may also be specified in enumeration, 1 point = Point3D::{ 2 y => 2, 3 x => 1, 4 z => 3, 5 }; When you create a class instance using enumeration, the instance is created, and filled with the values in the enumeration. Instance creation by enumeration is much faster than creation by invoking class constructor, since no class constructor is called and there is no overhead associated with function call (parameter passing, running time context preparation for the call etc.). So such instance creation is very desirable for creating many instances for simple classes, in which there are no complicated initialization operations. 0.3 Member Variable As mentioned above, instance variables are declared in class constructor using var keyword. Class constant can be declared using const keyword, and static member can be declared using static keyword as in C++: 1 class Klass 2 { 3 const aClassConst = "KlassConst"; 4 static aClassStatic; 5 } Here aClassConst will be constant belonging to a Klass. While aClassStatic will be a static variable in the class scope. 0.4 Setters, Getters and Overloadable Operators Instead of defining setXyz() methods, one can define .Xyz=() method as setter operator, so that modifying class member Xyz by obj.Xyz=abc will be allowed; similarly, if .Xyz() is defined, get the value by obj.Xyz will also be allowed: 1 class MyNumber 2 { 3 private 4 5 var value = 0; 6 7 public 8 9 routine MyNumber( 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 = MyNumber( 123 ) 18 num.value = 456 19 io.writeln( num.value ) As you may guess, accessing instance variable through getters and setters are more expensive than using them as public variables! They should be used only when they make things more convenient (for example, when you want them to do extra work when a variable is accessed). Other supported operators for overloaing include: 1. [operator ()(...)] for function call; 2. [operator [](...)] for getting item(s); 3. [operator []=(...)] for setting item(s); Basic arithmetic operators are also supported for overloading. 0.5 Method Overloading Class methods can be overloaded in the same way as normal functions. Class constructor may also be overloaded by simply adding a method with the same name as the class. For example, class MyNumber can be modified to hold numeric value only: 1 class MyNumber 2 { 3 private 4 5 var value : int = 0; 6 7 public 8 9 routine MyNumber( value = 0 ){ # accept integer as parameter 10 self.value = value; 11 } 12 13 # overloaded constructor to accept MyNumber as parameter: 14 routine MyNumber( value : MyNumber ){ self.value = value.value } 15 16 operator .value=( v : int ){ value = v } 17 operator .value=( v : MyNumber ){ value = v.value } 18 operator .value(){ return value } 19 } 20 21 num1 = MyNumber( 123 ) 22 num1.value = 456 23 io.writeln( num1.value ) 24 25 num2 = MyNumber( num1 ) 26 io.writeln( num2.value ) 27 28 num2.value = 789 29 io.writeln( num2.value ) 30 31 num2.value = num1 32 io.writeln( num2.value ) 0.6 Inheritance 1 class ColorRBG 2 { 3 var Red = 0.0; 4 var Green = 0.0; 5 var Blue = 0.0; 6 7 routine ColorRBG( r = 0.0, g = 0.0, b = 0.0 ){ 8 Red = r; 9 Green = g; 10 Blue = b; 11 } 12 routine ColorRGB( name : enum<white,black,red,green,blue,yellow,magenta,cyan> ){ 13 switch( name ){ 14 case $white: Red = Green = Blue = 1.0 15 case $black: 16 case $red: Red = 1.0 17 case $green: Green = 1.0 18 case $blue: Blue = 1.0 19 case $yellow: Red = Green = 1.0 20 case $magenta: Red = Blue = 1.0 21 case $cyan: Green = Blue = 1.0 22 } 23 } 24 25 routine setRed( r ){ Red = r; } 26 routine setGreen( g ){ Green = g; } 27 routine setBlue( b ){ Blue = b; } 28 29 routine getRed(){ return Red; } 30 routine getGreen(){ return Green; } 31 routine getBlue(){ return Blue; } 32 } 33 34 yellow = ColorRBG( 1, 1, 0 ); # create an instance. The following will define a derived class of ColorRBG, 1 class ColorRGBA : ColorRBG 2 { 3 var alpha = 0.0; # alpha component for tranparency. 4 5 routine ColorRGBA( r = 0.0, g = 0.0, b = 0.0, a = 0.0 ) : ColorRBG( r, g, b ){ 6 alpha = a; 7 } 8 # Inherit the constructor from ColorRGB that accepts color names: 9 use routine ColorRGB( name : enum<white,black,red,green,blue,yellow,magenta,cyan> ); 10 } 11 12 yellow2 = ColorRGBA( 1, 1, 0, 0 ); # not tranparent. 13 yellow2.alpha = 0.5; # change to half tranparency. 14 15 magenta = ColorRGBA( $magenta ) In the definition of derived class, the parent class ColorRBG should be put after the derived class and be separated with :.(Since 2013-10-20, classic multiple inheritance is no longer support. Now mixins are the preferred way to do similar things.) The constructor parameters for derived class can be passed to parent classes in the way as shown in the example. Constructors from the parent class can be inherited by using the use statement, in which the full function signature of the constructor should be specified. 0.7 Mixin Features introduced in the this and the remaining sections are available only in the latest devel release and the online version. Classes to be used as mixins can be specified in a pair of brackets following the class name. Only classes without parent classes can be used as mixins. 1 class Base 2 { 3 var value = 456 4 routine Meth2(){ io.writeln( self, value ) } 5 } 6 7 class Mixin ( Base ) 8 { 9 var index = 123 10 11 routine Meth(){ io.writeln( self, index, value ) } 12 routine Meth2( a : string ){ io.writeln( self, index, value, a ) } 13 } 14 15 # 16 # The "Base" class will be presented only once in "Klass": 17 # 18 class Klass ( Base, Mixin ) 19 { 20 var index = 123456 21 routine Meth2( a : int ){ io.writeln( self, index, value, a ) } 22 } 23 24 k = Klass() 25 26 io.writeln( k.index ) 27 28 k.Meth() 29 k.Meth2() 30 k.Meth2( 'abc' ) 31 k.Meth2( 789 ) 0.8 Class Decorator (New in the latest devel release and online version) Class decorators are classes that can be used modify other classes. The modification is done by using such class as a mixin base class to inject (combine) its members into the modified class, and by automatically applying its method decorators to the methods of the modified class. For such auto decorator application, only decorators with explicitly specified decoration targets are automatically applied. Such targets are expressed as prefix and suffix rules which can be expressed in the following ways: 1. Prefix~ : a prefix pattern. The decorator will be auto applied to methods that have names with such prefix; 2. ~Suffix : a suffix pattern. The decorator will be auto applied to methods that have names with such suffix; 3. Prefix~Suffix : a prefix and suffix pattern. The decorator will be auto applied to methods that have names with such prefix and suffix; 4. ~ : an empty prefix and suffix pattern. The decorator will be auto applied to any methods. When multiple mixins are used in a host class, the decorators of the first mixin are applied the last. And the first decorator of the same decorator is also applied the last as well. 1 class @Header 2 { 3 static routine @Delimiter( meth(args) : routine ) for ~ { 4 io.writeln( '=======================' ) 5 return meth( args, ... ) 6 } 7 routine @Delimiter( meth(args) : routine ) for ~ { 8 io.writeln( '-----------------------' ) 9 return meth( args, ... ) 10 } 11 } 12 class @Decorator 13 { 14 var value = 654321 15 16 routine @Test( meth(args) :routine<self:@Decorator> ) for Test { 17 io.writeln( 'Decorator::Test()', value ) 18 meth( args, ... ); 19 } 20 routine @Prefix( meth(args) :routine<self:@Decorator> ) for Prefix~ { 21 io.writeln( 'Decorator::Prefix()' ) 22 meth( args, ... ); 23 } 24 routine @Suffix( meth(args) :routine<self:@Decorator> ) for ~Suffix { 25 io.writeln( 'Decorator::Suffix()' ) 26 meth( args, ... ); 27 } 28 routine @Prefix_Suffix( meth(args) :routine<self:@Decorator> ) for Prefix~Suffix { 29 io.writeln( 'Decorator::Prefix_Suffix()' ) 30 meth( args, ... ); 31 } 32 } 33 34 class MyMixin ( @Header, @Decorator ) 35 { 36 routine Test(){ 37 io.writeln( 'MyMixin::Test()' ) 38 } 39 routine PrefixTest(){ 40 io.writeln( 'MyMixin::PrefixTest()' ) 41 } 42 routine TestSuffix(){ 43 io.writeln( 'MyMixin::TestSuffix()' ) 44 } 45 routine PrefixTestSuffix(){ 46 io.writeln( 'MyMixin::PrefixTestSuffix()' ) 47 } 48 } 49 50 obj = MyMixin() 51 obj.Test() 52 obj.PrefixTest() 53 obj.TestSuffix() 54 obj.PrefixTestSuffix() 0.9 Aspect Class (New in the latest devel release and online version) In Dao, a class decorator can be effectly used as an aspect for AOP, if decoration target patterns are specified for auto application. The target patterns are can be specified in the same way as the target patterns for class method decorators. The fields of such class will be automatically injected to normal classes selected according to the affix rules, and the decorators defined in such aspect class are automatically applied to the methods (selected according to the same affix rules) of the normal classes. 1 class @AspectForAnyClass for ~ # To be applied to any classes; 2 { 3 var injected : list<int> = {} 4 5 # This is not a decorator! 6 routine @AspectForAnyClass(){ 7 io.writeln( 'In @AspectForAnyClass():' ); 8 injected = { 1, 2, 3 } 9 } 10 11 # This decorator will also be applied to the default constructors: 12 routine @DecoratorForAnyMethod( meth(args) : routine ) for ~ { 13 io.writeln( 'In @DecoratorForAnyMethod():', std.about(meth) ) 14 io.writeln( injected ) 15 return meth( args, ... ) 16 } 17 } 18 19 # For classes with names prefixed with My: 20 class @AspectForMyClasses for My~ 21 { 22 routine @Method( meth(args) : routine ) for Method~ { 23 io.writeln( 'In @AspectForMyClasses::@Method():', std.about(meth) ) 24 return meth( args, ... ) 25 } 26 } 27 28 class MyClass 29 { 30 routine Method(){ io.writeln( 'MyClass::Method()' ) } 31 } 32 33 k = MyClass() # Invoke the default constructor of Klass; 34 k.Method()