Firstly, some words about CA-Clipper built-in OO model. Its OO is based on ordinary array, and any call like
results in the situation when in obj array there is searching of an element whose first element coincides with attribute or method name, and such search is executed linearly and is practically analogues to
function, which greatly reduces the efficiency of OO model of pure CA-Clipper. This, of course, is rather a simplified explanation, but the sense is still that as described.
We believe, now it's clear for what purpose association arrays were made. OO model based on association arrays is faster by an order.
At the same time, there's no need in expressions like
and in TClass class itself, which raises OO model efficiency.
How could you make your own class? It's very simple:
function MyClassNew() obj:=map() // empty object clone(MyClass2New(),obj) // adoption of MyClass2 structure clone(MyClass3New(),obj) // adoption of MyClass3 structure // if there are coinciding attributes or // methods, elements of the last class // become main. obj:attribute1:=0 obj:attribute2:=date() obj:method1:=@func1() // method1 becomes a function reference obj:method2:=@func2() // these functions must be defined in // the same .prg file as static // if methods have been addopted from other classes, // they will be reassigned to indicated classes return obj // returning a ready object static function func1 ::attribute1++ return NIL static function func2(self) self:attribute1-- return self
Also, we'd like to add two simple rules:
The attribute is created when something, including NIL, is assigned to it.
At any moment in run-time,the method can assign or reassign any function announced in this module as static function, or can adopt this function from another object, as a usual assignment of values:
myObj1:method1 := myObj2:methodX
In what way can objects be used? As in CA-Clipper, or even more simply:
obj:=MyClassNew() obj:method1() ? obj:attribute1
In an object, destroy() method can be announced, but it isn't quite destructor, which is usual in languages of the third generation. There is a local obj variable, and this is just an object. Upon leaving the function body, this variable, along with all its data, is destroyed. Now, let's consider the case of an object having an attribute
When destroying obj, it's necessary to close hFile, but the compiler doesn't know this; the compiler (rather a virtual machine) only knows that in hFile there's a number and will destroy the number only, but the file will remain open. Just for such purposes destroy() is intended, and it'll called (if it exists ) before destroying obj variable.
static function my_destroy() fclose(::hFile) return
If it's necessary to control changes of object attributes, make modify() method and invoke
mapmodify(obj, .t. )
modify() method is called before changing value of any object attribute. Two parameters are passed to modify(): hash code of attribute to be changed and new value to be assigned to it. modify() should return the value to assign to the attribute. For example:
obj := MyObj() obj:attr1 := "bye" ? obj:attr1,obj:attr2 // hello world function MyObj() local obj := map() obj:attr1 := "" obj:attr2 := "world" obj:modify := @mymodify() mapmodify(obj,.t.) return obj static function mymodify(self,hash,value) if hash == hash_ATTR1 .and. value == "bye" return "hello" // don't be so pessimistic :) endif return value
CLIP is able to store the data of any type to MEMO fields, including objects. But there is no way to store object methods (methods can be changed).
Recovering is made with the following steps: data are decoded; if data are of object type and object have CLASSNAME attribute then called function _recover_&(var:CLASSNAME)(var). This function must assign this object methods.
This feature can be used to send object as string via e-mail or TCP :)
Here is an example of using this feature:
x:=asdfNew() /* Constructor */ ? "x:m1",x:m1() /* See wheter it works */ ? "x:m2",x:m2() y:=var2str(x) /* object -> string */ /* or field->memo_field:=x */ ? "y=",y z:=str2var(y) /* string -> object, _recover_asdf() is called automatically */ /* or z:=field->memo_field */ ? "z=",z ? "z:m1",z:m1() /* see wheter it works now */ ? "z:m2",z:m2() return function asdfNew() local o:=map() o:classname := "ASDF" o:a1 := "asdf" o:a2 := "qwer" _recover_asdf(o) return o function _recover_asdf(o) o:m1 :=@asdf_1() o:m2 :=@asdf_2() ? "recovering" return o static function asdf_1 ? "asdf_1",::a1 return ::a1 static function asdf_2 ? "asdf_2",::a2 return ::a1
CLIP supports overloading of the operations. Operations that can be overloaded and corresponding operator methods are listed in the table below.
Table 10-1. Overloaded operations
Here is an example of using operations overloading.
car1 := newBMW() car2 := newKAMAZ() ? car1 > car2 // .F. (weight of Kamaz is much more :) car := car1+car2 ? car:model // Scrap ? car:weight // 10000 // 10000 kilograms of the scarp-iron :) function newCar() local obj := map() obj:model := "" obj:weight := 0 obj:operator_gt := @car_gt() obj:operator_add := @car_add() return obj static function car_gt(car) return ::weight > car:weight static function car_add(car) local obj := newCar() obj:model := "Scrap" obj:weight := ::weight+car:weight return obj function newBMW() local obj := newCar() // adopt Car class obj:model := "BMW" obj:weight := 2000 return obj function newKAMAZ() local obj := newCar() // adopt Car class obj:model := "KAMAZ" obj:weight := 8000 return obj
Due to such OO model and compiling into C-program, there appears a possibility to write TBrowse and Get standard classes in Clipper itself. At the same time, the efficiency of these classes is not worse than those written in pure C in CA-Clipper.