Clipper Tutorial: a Guide to Open Source Clipper(s)/Object Oriented Programming
Object Oriented Programming
Clipper had very limited support for OOP (Object Oriented Programming, from now on we’ll call it by its nickname). His successor CA-Visual Objects was much more advanced in this respect. However Visual Objects never had a great success, and third-party producers provided OOP libraries for Clipper, among which the most famous were Class(y), TopClass, Fivewin and Clip4Win.
Looking at Object-Oriented Programming from a Safety Distance
Let us suppose we're dealing with the distance function given the Cartesian coordinates of the points (http://mathinsight.org/cartesian_coordinates). The formulas we'll apply are:
for (Euclidean) distance on a real line, Euclidean plane and Euclidean space respectively.
In procedural programming our functions would look like this:
? distance1d(4,-3) ? distance2d(2,-3,-1,-2) ? distance3d(1,1,1,4,4,4) FUNCTION distance1d( x1, x2 ) RETURN sqrt((x2-x1)^2) FUNCTION distance2d( x1,y1,x2,y2 ) RETURN sqrt((x2-x1)^2+(y2-y1)^2) FUNCTION distance3d( x1,y1,z1,x2,y2,z2 ) RETURN sqrt((x1-x2)^2+(y1-y2)^2+(z1-z2)^2)
We defined three functions, with different names, which take as arguments the coordinates. But doing so we need to pass six arguments for the distance in a three-dimensional space.
If we're doing it with object oriented programming we may get something like this:
#include "hbclass.ch" CREATE CLASS Point1D VAR Abscissa // the abscissa of our point METHOD New( Abscissa ) // Constructor METHOD Distance( Point ) ENDCLASS CREATE CLASS Point2D INHERIT Point1D VAR Ordinate // the ordinate of our point METHOD New( Abscissa, Ordinate ) // Constructor METHOD Distance( Point ) ENDCLASS CREATE CLASS Point3D INHERIT Point2D VAR Zcoord // the Z-coordinate of our point METHOD New( Zcoord ) // Constructor METHOD Distance( Point ) ENDCLASS && Constructors Zone METHOD New( Abscissa ) CLASS Point1D ::Abscissa := Abscissa RETURN Self METHOD New( Abscissa, Ordinate ) CLASS Point2D ::Abscissa := Abscissa ::Ordinate := Ordinate RETURN Self METHOD New( Abscissa, Ordinate, Zcoord ) CLASS Point3D ::Abscissa := Abscissa ::Ordinate := Ordinate ::Zcoord := Zcoord RETURN Self &&Distances Methods METHOD Distance( Point ) CLASS Point1D RETURN Sqrt( ( Self:Abscissa - Point:Abscissa ) ^ 2 ) METHOD Distance( Point ) CLASS Point2D RETURN Sqrt( ( Self:Abscissa - Point:Abscissa ) ^ 2 + ( Self:Ordinate - Point:Ordinate ) ^ 2 ) METHOD Distance( Point ) CLASS Point3D RETURN Sqrt( ( Self:Abscissa - Point:Abscissa ) ^ 2 + ( Self:Ordinate - Point:Ordinate ) ^ 2 + ( Self:Zcoord - Point:Zcoord ) ^ 2 ) PROCEDURE Main() FirstPoint := Point1D():New( 3 ) SecondPoint := Point1D():New( - 3 ) ? FirstPoint:Abscissa ? FirstPoint:Distance( SecondPoint ) ThirdPoint := Point2D():New( 2, - 3 ) FourthPoint := Point2D():New( - 1, - 2 ) ? ThirdPoint:Distance( FourthPoint ) FifthPoint := Point3D():New( 1, 1, 1 ) SixthPoint := Point3D():New( 4, 4, 4 ) ? FifthPoint:Distance( SixthPoint ) RETURN
Here we've defined three classes, their constructors, and a distance method for each of them, and showed how to use them. It is also a simple example of how inheritance works. Other concepts are encapsulation (information hiding or data hiding), abstraction, polymorphism, overloading and overriding of methods. Inheritance plays a central role in a key concept: reuse. A class can be reused in a software project if it is exactly what was needed; or if it is not exactly what was needed it can be extended by defining a subclass. Much like the design of a database, the design of object oriented class is an art with its principles, see for example http://www.oodesign.com/, http://www.codeproject.com/articles/567768/object-oriented-design-principles and pages about UML like http://www.uml-diagrams.org/uml-object-oriented-concepts.html.
The first thing to note is that we start by including the clipper header file hbclass.ch, the header file for Class commands.
Access to variables and methods of an object is done via the colon operator. A prepended double colon refers to variables with a larger scope (such as those passed to a method).
In the code above we defined three classes, each one implementing a Point. Point2D for example was defined as a class extending Point1D, that is a generalization of the concept. A method Distance was given for each of the classes.
A line such as
? FifthPoint:Distance( SixthPoint )
contain the output command ?, the reference to an object (FifthPoint in this case), an invocation of the Distance method :Distance, to which another point was passed ( SixthPoint ).
It is also possible to write a Distance function which takes two arguments of a Point class, that may look like this:
FUNCTION Distance ( Point1, Point2 ) RETURN Sqrt( ( Point1:Abscissa - Point2:Abscissa ) ^ 2 + ( Point1:Ordinate - Point2:Ordinate ) ^ 2 + ( Point1:Zcoord - Point2:Zcoord ) ^ 2 ) ? Distance( FifthPoint, SixthPoint )
This is, however, not object-oriented programming, as we could have written the same function with a not object-oriented language such as Pascal or C, passing it two structs, or records as Pascal calls them, named Point1 and Point2.
It is important the fact that some data internal to the object (set by the real programmer of the thing) can't be changed by the object user. As a real life example, we can consider a car engine. The provider of the object set a number of cylinders, and we have not many chances of changing that: we've got to regard it as a constant. There is naturally a number of interesting formulas about engines that engineers use (some to be seen at http://www.thecartech.com/subjects/engine/engine_formulas.htm). For example, the one for computing the Engine Volumetric Efficiency given the volume of air taken into a cylinder and the cylinder swept volume. Here comes the importance of data hiding: nobody needs to know those informations to get his car going. Also, when someone designes an engine they probably don't expect the user to change the volumetric efficiency by operating on the engine. The same thing is obtained in object oriented programming using visibility modifiers, or access modifiers.
[CREATE] CLASS <cClassName> [ FROM | INHERIT <cSuperClass1> [, ... ,<cSuperClassN>] ] [ MODULE FRIENDLY ] [ STATIC ] [ FUNCTION <cFuncName> ] [HIDDEN:] [ CLASSDATA | CLASSVAR | CLASS VAR <DataName1>] [ DATA | VAR <DataName1> [,<DataNameN>] [ AS <type> ] [ INIT <uValue> ] [[EXPORTED | VISIBLE] | [PROTECTED] | [HIDDEN]] [READONLY | RO] ] ... [ METHOD <MethodName>( [<params,...>] ) [CONSTRUCTOR] ] [ METHOD <MethodName>( [<params,...>] ) INLINE <Code,...> ] [ METHOD <MethodName>( [<params,...>] ) BLOCK <CodeBlock> ] [ METHOD <MethodName>( [<params,...>] ) EXTERN <funcName>([<args,...>]) ] [ METHOD <MethodName>( [<params,...>] ) SETGET ] [ METHOD <MethodName>( [<params,...>] ) VIRTUAL ] [ METHOD <MethodName>( [<params,...>] ) OPERATOR <op> ] [ ERROR HANDLER <MethodName>( [<params,...>] ) ] [ ON ERROR <MethodName>( [<params,...>] ) ] ... [PROTECTED:] ... [VISIBLE:] [EXPORTED:] ... [FRIEND CLASS <ClassName,...>] [FRIEND FUNCTION <FuncName,...>] [SYNC METHOD <cSyncMethod>] ENDCLASS [ LOCK | LOCKED ]
Copied verbatim from w:Harbour (software)
#include "hbclass.ch" PROCEDURE Main() LOCAL oPerson CLS oPerson := Person():New( "Dave" ) oPerson:Eyes := "Invalid" oPerson:Eyes := "Blue" Alert( oPerson:Describe() ) RETURN CREATE CLASS Person VAR Name INIT "" METHOD New( cName ) METHOD Describe() ACCESS Eyes INLINE ::pvtEyes ASSIGN Eyes( x ) INLINE iif( HB_ISSTRING( x ) .AND. x $ "Blue,Brown,Green", ::pvtEyes := x, Alert( "Invalid value" ) ) PROTECTED: VAR pvtEyes ENDCLASS // Sample of normal Method definition METHOD New( cName ) CLASS Person ::Name := cName RETURN Self METHOD Describe() CLASS Person LOCAL cDescription IF Empty( ::Name ) cDescription := "I have no name yet." ELSE cDescription := "My name is: " + ::Name + ";" ENDIF IF ! Empty( ::Eyes ) cDescription += "my eyes' color is: " + ::Eyes ENDIF RETURN cDescription