CLASSY
CLASSY
CLASSY
Version 2.0
Programmer's Guide
AppSolutions, Inc.
www.appsolutions.com
Software and documentation copyright 1991-1994 by Anton van Straaten. All rights reserved.
This document, and the software described in this document, are furnished under a license agreement
or nondisclosure agreement. The software may be used or copied only in accordance with the terms of
the agreement. It is against the law to copy the software on any medium except as specifically allowed in
the license or nondisclosure agreement.
No part of this documentation may be reproduced or transmitted in any form or by any means, including
photocopying, recording, or information storage and retrieval systems, without written consent of the
publisher, AppSolutions, Inc.
Table of Contents i
1 Getting Started 1
Technical Support ...................................................................................................... 1
License Agreement ..................................................................................................... 1
Upgrades .................................................................................................................... 2
Major and Minor Upgrades.......................................................................... 2
Incremental Upgrades .................................................................................. 3
Installation .................................................................................................................. 3
Online Documentation ................................................................................. 3
Sample Programs ......................................................................................... 3
Upgrading from Class(y) v1.0x .................................................................... 4
Guide to the Manual ................................................................................................... 4
Getting Started ............................................................................... 4
Introduction and Tutorial ............................................................... 4
Reference ....................................................................................... 4
Other Reading ............................................................................................................ 5
Book List ..................................................................................................... 6
2 Introduction to Object Orientation 8
Object Orientation and Clipper 5 ............................................................................... 8
Overview of Object-Orientation ................................................................................. 8
Brief History of Object-Oriented Languages ............................................................. 9
What is an Object? ................................................................................................... 10
Classes, Instances and Instance Variables ................................................................ 10
Messages and Methods............................................................................................. 11
Inheritance - Superclasses and Subclasses ............................................................... 11
Polymorphism .......................................................................................................... 11
3 Using Class(y) 11
Creating a Class........................................................................................................ 12
The Class Specification ............................................................................................ 12
Using the Class ......................................................................................................... 13
Method Definitions .................................................................................................. 14
Inheritance ................................................................................................................ 16
Class Variables & Methods ...................................................................................... 18
4 Pull-Down Menu Tutorial 20
Introduction .............................................................................................................. 20
Designing the Classes ............................................................................................... 20
Pull-Down Menu Class Diagram .............................................................................. 21
Explanation of the Code ........................................................................................... 22
Creating a Class - MenuItem...................................................................... 22
Other MenuItem Methods .......................................................................... 23
The BaseMenu Class.................................................................................. 24
The MenuBar Class.................................................................................... 26
Overriding an Inherited Method................................................................. 27
The PopupMenu Class ............................................................................... 27
The PullDnMenu Class .............................................................................. 29
The Menu Demonstration .......................................................................... 29
Tracing Execution .................................................................................................... 30
5 Inheriting From TBrowse 31
The Code .................................................................................................................. 31
Cargo is a Kludge!.................................................................................................... 31
Overall Program Structure........................................................................................ 32
TBDemo() ................................................................................................................ 32
MyBrowse() ............................................................................................................. 32
Skipper() .................................................................................................................. 33
DoGet() .................................................................................................................... 33
Where Did the Code Blocks Go? ............................................................................. 33
How to Use the Class ............................................................................................... 33
What Next? .............................................................................................................. 33
Event Handling......................................................................................................... 34
Field Editing ............................................................................................................. 34
Table of Contents i
Summary .................................................................................................................. 35
6 Class Declaration Commands 36
CLASS MESSAGE...IS ........................................................................................... 36
CLASS MESSAGE...[IS...] TO ............................................................................... 36
CLASS MESSAGE...METHOD .............................................................................. 36
CLASS METHOD ................................................................................................... 37
CLASS VAR ............................................................................................................ 37
CLASS VAR...IS ..................................................................................................... 38
CLASS VAR...[IS...] TO ......................................................................................... 39
CREATE CLASS ..................................................................................................... 39
END CLASS ............................................................................................................ 40
EXPORT: ................................................................................................................. 41
HIDDEN: ................................................................................................................. 41
MESSAGE...IS......................................................................................................... 41
MESSAGE...[IS...] IN .............................................................................................. 42
MESSAGE...[IS...] TO............................................................................................. 44
MESSAGE...METHOD ........................................................................................... 45
METHOD (declaration) ........................................................................................... 46
PROTECTED: ......................................................................................................... 47
VAR ......................................................................................................................... 47
VAR...IS ................................................................................................................... 48
VAR...[IS...] IN ........................................................................................................ 49
VAR...[IS...] TO ....................................................................................................... 50
VISIBLE: ................................................................................................................. 51
7 Method Definition Commands, Etc. 52
METHOD (definition) ............................................................................................. 52
self ............................................................................................................................ 52
:: ............................................................................................................................... 53
@: ............................................................................................................................. 53
8 Usage Reference 55
Classes ...................................................................................................................... 55
Class Objects.............................................................................................. 55
Class Functions .......................................................................................... 55
Class Messages .......................................................................................... 55
Class Variables .......................................................................................... 55
Class Methods ............................................................................................ 56
Class Initialization ..................................................................................... 57
Uses of Class Objects................................................................................. 57
Creating New Objects .................................................................. 57
Comparing Classes ...................................................................... 58
Checking an Object's Class .......................................................... 58
Comparing Objects................................................................................................... 58
Constructors and Initializers ..................................................................................... 58
Constructor Messages ................................................................................ 58
Initializer Methods ..................................................................................... 59
Instantiation and Performance.................................................................... 59
Debugging ................................................................................................................ 60
Enhanced Object Inspector ........................................................................ 60
Standard Object Inspector .......................................................................... 60
Viewing class variables ............................................................... 60
Viewing superclass variables ....................................................... 60
Anomaly ...................................................................................... 60
Error Reporting .......................................................................................... 61
The Debugger's Command Window .......................................................... 61
Deferred and Null Methods ...................................................................................... 62
Deferred Methods ...................................................................................... 62
Null Methods ............................................................................................. 62
Inheriting from Clipper's Classes ............................................................................. 62
Inheriting from TBROWSE ....................................................................... 63
Inheriting From GET ................................................................................. 63
Length of Variable Names........................................................................................ 63
Linking ..................................................................................................................... 64
Sample Link Scripts ................................................................................... 64
Technical Support
If you have any questions about Class(y), please try to find the answers in this manual before
contacting us. In particular, if your program seems to be behaving erratically, please check the section
on Linking in the Usage Reference. Incorrect linking of Class(y) is a common cause of errors. Also, if
you are experiencing sluggish performance, please ensure that you are following the guidelines
suggested in the Usage Reference section on Performance. Read the section on Upgrades, later in this
chapter, for information on obtaining the latest release of Class(y).
If you cannot find the answers you need in the manual, then feel free to check our web site at
www.appsolutions.com. Support can be had via email to support@appsolutions.com, but there is an
hourly fee. For rates please check the web site. When emailing support questions, please include the
following information:
• The version number of the product. If you have applied patches to upgrade the product to a later
version, please have available the date of the file CLASSY.LIB.
• The version number of CA-Clipper which you are using, and the name and version of the linker and
any other third-party products which you are using.
• If an error message was displayed, the exact text which was displayed. We suggest that you print
the screen so that you can fax this information to us if necessary.
• The line of Clipper code on which the error occurred, and what the application was doing when the
error occurred.
• If you are using a protected-mode linker such as ExoSpace, and a general protection fault (GPF) is
occurring, determine the location of the fault by referring to the MAP file for the application, as
described in the linker’s documentation. Also determine which line of Clipper code was the last to
be executed prior to the GPF. Your protected-mode linker should be able to provide this
information via a call-stack trace.
• Any other information which you think may be pertinent to the problem.
License Agreement
CAREFULLY READ THE FOLLOWING TERMS AND CONDITIONS BEFORE OPENING THE ACCOMPANYING SEALED
DISKETTE PACKAGE. OPENING THE SEALED DISKETTE PACKAGE WILL INDICATE YOUR ACCEPTANCE. IF YOU DO NOT
ACCEPT THESE TERMS AND CONDITIONS, YOU SHOULD PROMPTLY RETURN THE UNOPENED DISK PACKAGE ALONG
WITH ALL DOCUMENTATION AND YOUR MONEY WILL BE REFUNDED.
License
The accompanying software and the separately copyrighted components included therewith, and any enhancements or
modifications thereto, whether delivered electronically or otherwise ("the Software") and the User Documentation accompanying
the Software ("the User Documentation"), are licensed to you for your personal use only. You may use the Software on a single
terminal connected to a single computer (i.e., with a single CPU). THE SOFTWARE MAY NOT BE USED BY MORE
THAN ONE PERSON NOR ON MORE THAN ONE COMPUTER SIMULTANEOUSLY.
Chapter 1, Getting Started 1
You may utilize the Software to prepare derivative works of other programs which you own or control with no further license from
or payment to AppSolutions, provided that the Software cannot be separated out, in whole or in part, from any such derivative
works. Except as otherwise provided herein, you may not copy, reproduce, or distribute the Software nor alter, modify, reverse
engineer, decompile, disassemble, or otherwise attempt to render source code from the Software.
You are permitted to make up to two (2) copies of the Software for archival or backup purposes only. You may not copy or
otherwise reproduce the User Documentation. You may transfer the Software and the User Documentation to another party only in
accordance with the applicable policies and procedures established by AppSolutions, which policies and procedure may be
changed from time to time at the sole and absolute discretion of AppSolutions. If you transfer the Software and the User
Documentation, you must at the same time either transfer all copies of the Software (regardless of form) to the same party or
destroy any copies not transferred. AppSolutions retains all rights not expressly granted.
Term
This license is effective until terminated. You may terminate it by destroying the program and documentation and all copies
thereof. This license will also terminate if you fail to comply with any term or condition of this agreement. You agree, upon such
termination, to destroy all copies of the program and documentation.
Limited Warranty
EXCEPT FOR THE LIMITED WARRANTY SET FORTH ABOVE, THE SOFTWARE AND THE USER DOCUMENTATION
ARE PROVIDED "AS IS." APPSOLUTIONS MAKES NO OTHER WARRANTY, EXPRESS OR IMPLIED, WITH
RESPECT TO THE SOFTWARE AND/OR USER DOCUMENTATION AND SPECIFICALLY DISCLAIMS THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. APPSOLUTIONS DOES NOT
WARRANT THAT THE SOFTWARE AND/OR THE USER DOCUMENTATION WILL MEET YOUR REQUIREMENTS OR
EXPECTATIONS OR THAT THE OPERATION OF THE SOFTWARE WILL BE UNINTERRUPTED AND/OR ERROR
FREE. YOU ARE SOLELY RESPONSIBLE FOR THE SELECTION OF THE SOFTWARE TO ACHIEVE YOUR
INTENDED RESULTS AND FOR THE RESULTS ACTUALLY OBTAINED.
Under no circumstances, and notwithstanding any failure of the essential purpose of any limited remedy provided for herein, shall
AppSolutions be liable to you for any damages, claims or losses whatsoever, including but not limited to any claims for lost profits,
lost savings or other special, incidental or consequential damages arising out of the use or inability to use the Software and/or the
User Documentation regardless of the circumstances.
General
The Software and User Documentation are licensed to the United States Government, its agencies and/or instrumentalities, with
RESTRICTED RIGHTS. Use, duplication or disclosure by the Government, its agencies and/or instrumentalities is subject to
restrictions as set forth in subparagraph (c) (1) (ii) of the Rights in Technical Data and Computer Software clause at DFARS
252.227-7013. Contractor/manufacturer is AppSolutions, Inc., 266 Harristown Road, Suite 108, Glen Rock, NJ 07452.
In the event it is determined that any provision contained in this license is unlawful, void or unenforceable, such determination
shall solely affect such unlawful, void or unenforceable provision and shall not affect the validity or enforceability of the remaining
provisions of this license. This agreement will be governed by the laws of the State of New Jersey.
Upgrades
We are constantly working on our products to improve their usefulness to you. From time to time, we
publish upgrades to Class(y). These upgrades fall into three broad categories: major upgrades, minor
upgrades, and incremental upgrades. The version number of Class(y) takes the form M.Nr where M is
the major version number, N is the minor version number, and r is an alphabetic character which refers
to the revision level. For example, the version number 2.0d represents major version 2, minor version 0,
and revision level d.
Incremental Upgrades
Changes to the revision level indicate an incremental upgrade. Incremental upgrades, sometimes known
as maintenance upgrades, are released to address such problems as incompatibilities with other third-
party products, new versions of Clipper, or bugs. At the time of this writing, a total of 12 incremental
upgrades have occurred during Class(y)’s lifespan. The average time between incremental upgrades has
been less than three months.
We notify users of incremental upgrades via notices on our web site, www.appsolutions.com. Patch
files for incremental upgrades are made available there. Where possible, they are also available on the
BBS systems of our international distributors. Typically, patch files for incremental upgrades are small
and can be downloaded in five minutes or less, even at 2400 baud.
If you encounter a problem with Class(y), whether a bug or an incompatibility with some other product,
please check our web site or one of the other sources mentioned above for a patch which addresses the
problem.
Installation
Class(y) does not have an automated installation program. Installation is very straightforward. We
suggest that you copy the contents of the distribution diskette to a directory on your hard disk called
\CLASSY2, as in the following example:
XCOPY A:\*.* C:\CLASSY2\ /S
Note the /S switch to XCOPY, which causes all subdirectories to be correctly copied.
Once the files have been copied correctly, you need to set up your DOS environment so that you can
compile and link Class(y) programs. Throughout this section, we will assume that you installed
Class(y) in a directory named CLASSY2, as in the above XCOPY example.
We suggest that you add the \CLASSY2\LIB directory to the library path via the LIB environment
variable, and add the \CLASSY2\INCLUDE directory to the include file path via the INCLUDE
environment variable, as in the following example:
SET LIB=C:\CLIPPER\LIB;C:\CLASSY2\LIB
SET INCLUDE=C:\CLIPPER\INCLUDE;C:\CLASSY2\INCLUDE
If you are short of environment space or already have very long LIB and INCLUDE paths, you may
wish instead to copy the two libraries (*.LIB) in \CLASSY2\LIB into your default library directory.
Similarly, the header files (*.CH) can be copied from the \CLASSY2\INCLUDE directory to a default
include file directory.
Finally, copy the file CLASS(Y).NG into your Norton Guide directory (Norton Guide is supplied with
CA-Clipper).
Online Documentation
An online command and usage reference is provided in the form of a Norton Guide file,
CLASS(Y).NG, found in the \CLASSY2\DOCS directory.
Sample Programs
Class(y) includes various sample classes and programs to demonstrate their use. Batch files are
provided to compile and link these samples. For an overview of the available sample programs,
examine the File List in the Class(y) READ.ME file.
Getting Started
Chapter 1, Getting Started, deals with technical support, the license agreement, upgrades, installation,
and this section. The remainder of the manual can be broadly divided into two sections: an introduction
and tutorial section, and a reference section.
Reference
Chapter 6, Class Declaration Commands, and Chapter 7, Method Definition Commands, is a
command reference. Each chapter is organized alphabetically. The command reference contains details
of the syntax and behavior of individual Class(y) commands. Chapter 6 covers commands used in the
declaration of classes (commands used in a CREATE CLASS...END CLASS block), while Chapter 7
contains commands used in the definition of methods (method definitions usually appear after the class
declaration in a .PRG file).
Chapter 8, Usage Reference, and Chapter 9, Advanced Topics, form a combined glossary and
reference which covers a variety of Class(y) terminology and features. Each chapter is organized
alphabetically by major topic name. The Advanced Topics chapter contains material which is not
required for normal use of Class(y), but it will be of interest to those doing more advanced work.
An alphabetical index can be found at the end of the manual.
Note that the reference chapters, 6 through 9, are also contained in the Class(y) Norton Guide.
Overview of Object-Orientation
"After years of relative obscurity, object orientation appears to be entering the mainstream of
commercial computing for both software developers and end users. A shift to object orientation
is occurring simultaneously across a wide range of software components, including languages,
user interfaces, databases, and operating systems. While object-oriented programming is no
panacea, it has already demonstrated that it can help manage the growing complexity and
increasing costs of software development".
- from Object-Oriented Software by
Winblad, Edwards & King
This quotation is an excellent summary of what is happening in the world of computing today.
Although exciting research and development is taking place on many fronts, no single software topic
currently enjoys as wide a scope or impact as object orientation. Some of the most advanced and
powerful software products available today incorporate object orientation as a central concept:
languages such as Smalltalk, C++, and Actor; leading edge minicomputer databases such as Ontos and
Servio Logic's Gemstone; expert system development tools such as Neuron Data's Nexpert Object and
Level 5 Object from Information Builders; and graphical user interfaces (GUIs) such as Microsoft
What is an Object?
One of the fundamental reasons that object orientation is enjoying such success as a programming
paradigm is very simple: the real world is made up of objects. An invoice is an object. A stock item is
an object. A balance sheet is an object. An entire company is an object. Objects can contain other
objects (this is called composition); and in this way complete systems can be constructed using objects.
But what is an object from a programming point of view? Simply put, it is a collection of related data,
which is associated with the procedures which can operate on that data.
By this definition, most well-structured programs could be said to contain objects. This is another
contributing factor to the confusion surrounding the definition of object orientation.
It is in fact possible to write programs in an object-oriented way in many traditional procedure-oriented
languages. However, without the support provided by an object orientated languages, many
compromises have to be made.
An object-oriented language formalizes the relationship of the data within an object to the program
code which can operate on that data, by requiring that the compiler or interpreter be informed which
procedures are allowed to operate on an object's data.
Before we can clarify our definition of an object further, we need to explore a few other concepts.
Polymorphism
The term polymorphism in this context refers to the fact that the same message, such as print, can
result in different behaviors when sent to different objects. Sending the print message to a graph
object has a different effect than it would on a balance sheet object. With a traditional procedural
approach, the programmer is forced to differentiate procedure names, using names like PrnBalSheet
and PrintGraph. In an object-oriented language, this differentiation is unnecessary, and in fact
unwise.
Polymorphism has benefits for the programmer in that the same name can be used for conceptually
similar operations in different classes, but its implications go deeper than that. It means that a message
can be sent to an object whose class is not known. In a procedural system, given a variable of unknown
type, a CASE statement would typically be required to test the type of the variable and pass it to the
correct procedure for the desired operation. In an object-oriented system, a message is sent to the object
with no testing, and the object responds accordingly.
This has important implications for inheritance, since it means that methods belonging to classes near
the root of the inheritance tree do not need to know details of the subclasses which may be inherited
from them. By sending messages with standardized names to the objects with which it deals, generic
methods can be written which can later be used with any class which supports the required messages.
3 Using Class(y)
Chapter 2, Introduction to Object Orientation 11
The concepts described in the previous chapter should become clearer when applied in an actual
program. To this end, we will now follow the process of creating a simple, real life class; and at the
same time familiarizing ourselves with Class(y), enabling us to move on to a more complex example in
the next section.
First, some detail about Class(y). It consists of a library, CLASSY.LIB, and a header file,
CLASS(Y).CH. Object-oriented Clipper programs can be written and compiled in conjunction with the
header file, then linked with the library.
Class(y) makes use of, and extends, Clipper's limited built-in object-oriented capabilities, specifically
the send operator, or colon. In standard Clipper, a message is sent to an object as follows:
object:message( <parameters,...> )
Class(y) does not change this syntax. In fact, program modules which use objects, without defining
classes themselves, can be compiled without any special header file, since they are standard Clipper
programs in all respects.
The include file, CLASS(Y).CH, must be included (using #include) when defining a new object
class. A user-defined class is usually defined in a separate program module. It can then be compiled
into an object module (.OBJ), and linked into an executable file, or added to a library file (.LIB).
The use of the preprocessor and user-defined commands for class creation means that if and when
Computer Associates release a version of Clipper with the ability to create user-defined classes, it
should only be necessary to change the Class(y) header file, rather than any of the code implementing
specific user-defined classes.
Using Class(y), it is possible to mix and match programming styles. On the one hand, object classes can
be developed and used in normal procedural programs, in the same way that the built-in TBrowse and
Get classes are used in standard Clipper.
At the other extreme, complete object-oriented systems can be developed, in which no stand alone
procedures exist, other than a startup procedure. The startup procedure initializes the system and from
then on, all program execution is effected by passing messages between objects.
Any desired mix of these two approaches may be used, allowing developers to gradually familiarize
themselves with the benefits of object-oriented programming.
Creating a Class
Perhaps the most fundamental feature required of an object-oriented programming language is the
ability to create new object classes. With Class(y), a class consists of a class specification, which
describes the overall structure of a class, and the method definitions, which contains the actual code for
the methods of that class. These two components usually appear in the same module (.PRG file).
#include "class(y).ch"
// eof testrect.prg
This program creates a new instance of the class Rectangle and assigns it to the local variable x. The
expression:
Rectangle():new( 5, 10, 15, 40 )
can be understood as sending the new() message to the Rectangle class, which has the effect of
creating an instance of the Rectangle class with the specified dimensions.
Terminology The Rectangle() function in the above example is referred to as a class function, since it returns an
note object (a class object) which refers to the entire Rectangle class, rather than to a single instance of
Rectangle.
The method named new is a predefined system method which creates a new object and initializes it.
The new method is a predefined system method. It causes the appropriate initialization to be performed
by sending an init() message to the newly created object, along with the parameters originally
received by the new method. Each class should have its own, specialized init method. The job of the
init method is to initialize the new object according to the specified parameters.
More The new method, which is responsible for creating and initializing new objects, is known as a
terminology constructor method. The init method, which new uses to initialize new objects, is known as an
initializer method.
Once we have created our object, we can use it just as we would any other Clipper object, by sending it
messages. The statement x:width, for example, sends the width message to the Rectangle x. In this
particular case, this has the effect of invoking the width method, which calculates and returns a result.
Running this program produces output similar to the following:
C:\>testrect
Uh-oh! We have an error. Looking at line 10 of TESTRECT.PRG, we see that we have tried to print the
value of x:top. What is wrong with that? Looking back to the specification of class Rectangle, we
notice that the instance variable top is declared before the EXPORT: statement. Because of this, top
is considered a hidden instance variable, which means it can only be accessed by methods belonging to
class Rectangle. Our program, TESTRECT.PRG, is not a Rectangle method, and so is prevented from
accessing this variable.
Hidden instance variables are an important benefit of object-oriented programming. A hidden instance
variable is only accessible within the methods of its class. Making an instance variable hidden ensures
that only a relatively small set of routines (the methods of that class) can change it. If the variable's
value is incorrect, we know exactly which routines to examine.
In a well-designed object-oriented system, it is often desirable to prevent the values of instance
variables from being changed outside of their class. However, it is often necessary to access (but not
change) an instance variable's value outside of the class. To facilitate this, instance variables can be
declared as read-only, using the keyword READONLY in the VAR command. Let's redefine the
Rectangle class to allow external routines to read our instance variables:
CREATE CLASS Rectangle
EXPORT:
VAR top, left READONLY
VAR bottom, right READONLY
METHOD init
METHOD set
METHOD width, height
METHOD area
END CLASS
Notice that we have placed the instance variable declarations after the EXPORT: statement. This
makes them accessible to any program, but the READONLY clause in the VAR command prevents
other programs from assigning to these variables, in other words changing their value. Naturally, the
methods of class Rectangle can still assign values to these variables - otherwise they would be useless.
Running TESTRECT now gives us the results we expect:
C:\>testrect
Method Definitions
The method definitions for a class consist of program code for all the methods specified for that class.
This code is usually placed after the class specification, in the same module (.PRG file).
When writing methods, you need to be aware of how a method differs from a normal function or
procedure. A method, unlike a normal function or procedure, is not called directly, but rather is invoked
as the result of a message sent to an object. A method cannot be invoked without an object being
associated with it. A method acts on the object receiving the message; but to do so, it needs to be able
to access it. To this end, a local variable called self exists in all methods. self refers to the object
which received the message that is being acted on. Instance variables and methods belonging to that
object are accessed via the self variable. In the set method in the code below, for example, the line:
self:top := nTop
METHOD width
RETURN self:right - self:left + 1
METHOD height
RETURN self:bottom - self:top + 1
METHOD area
RETURN self:width * self:height
// eof rectangle.prg
The code is mostly self-explanatory. However, the METHOD command is being used differently from
the way it was used inside the class specification.
When used after a class specification, the METHOD command begins the definition of a method, just
as the Clipper statements FUNCTION and PROCEDURE are used to begin the definition of a user-
defined function or procedure.
A method declared with the METHOD command must return a value, just like a function. If you need
to declare a method which does not return a value, you can use the METHOD PROCEDURE
command.
Tip It has become standard practice to return self from methods which do not otherwise need to return a
value. This allows message sends to be chained. For example, if our Rectangle class contained a
message called draw(), we might write an expression such as:
oRect:set(5,4,10,15):draw()
which would set the coordinates for the Rectangle object referred to by oRect, then send the draw()
message to that object. This is only possible if the set() method returns self.
The first method defined above, init, makes use of a special feature of the METHOD command.
Let's take a look at it.
METHOD init( top, left, bottom, right ), ()
After the parameter list, there is a comma, followed by a pair of parentheses. It is actually a second
parameter list, which in this case is empty. The reason for its existence will remain a mystery until the
next section, when we discuss inheritance. For the moment, it is sufficient to know that you should
always include this second parameter list when defining the init method.
As discussed in the previous section, new objects are usually created by sending the new() message to
a class object. For example, to create a new instance of the Rectangle class, we use code such as this:
obj := Rectangle():new( 5, 10, 15, 70 )
Chapter 3, Using Class(y) 15
This statement sends the new() message, with the specified parameters, to the Rectangle class. The
new() method responds by creating a new, empty Rectangle object, and sending the init() message
to it to initialize it. A reference to the newly created and initialized Rectangle object is returned, and in
the code above is stored in the variable obj.
Whenever a new object is created, by sending a new() message to a class, the initializer for that class
is called. The initializer's job is to initialize the new object, and it can take parameters as necessary to
facilitate this. By default, the initializer method for a class is called init.
A class does not have to have an initializer, but it is highly recommended. In some cases, a class will
inherit its initializer from a parent class (inheritance is described in the next section).
If an initializer is not defined for a particular class, and if it does not inherit an initializer from a parent
class, that class will still accept the new() message. Instead of returning a properly initialized object,
though, it will return an empty object - an object with all its instance variables set to NIL. This is not
usually very useful or desirable.
Design Tip Why is creating completely empty objects not desirable? One of the ways in which correctness can be
ensured in object-oriented programs is by ensuring that objects are always in a valid state. An empty
object is unlikely to be in a valid state. For example, a Rectangle object with its corner coordinates set
to NIL cannot be drawn and will not respond correctly to many messages. An object like this is in an
invalid state.
Now that we have finished with RECTANGLE.PRG, we have the makings of a complete system and
can actually test it. The system can be compiled as follows:
clipper rectangle /n/w/a/b
clipper testrect /w/a/b
rtlink fi testrect, rectangle lib classy
This should create the file TESTRECT.EXE which can then be run. Note that the /b switch is used, to
include debug information. It might be instructive to trace through the program in the debugger (CLD
TESTRECT) to get a feel for what is happening.
Inheritance
An object-oriented language is not complete if it does not support inheritance. Inheritance is the
mechanism which allows existing code to be reused in different circumstances without modification.
As an example, we will declare a class called ScreenRect which can draw a rectangle on the screen and
manipulate it. Since we already have a Rectangle class, we can save a lot of time by inheriting from
that class.
The code below defines the class ScreenRect. A new feature is introduced here: the double colon (::) is
a shorthand notation for sending a message to the self object in a method. This is particularly useful
in a statement such as the following, adapted from the hide method below:
RestScreen( self:top, self:left, self:bottom, self:right, ;
self:screenBuf )
which might look strange at first, but it is easy to get used to and is easier to read than the alternative.
Here is the code for the ScreenRect class:
// scrnrect.prg
#include "class(y).ch"
#include "box.ch"
EXPORT:
METHOD init
METHOD moveUp, moveDown
16 Class(y) Programmer's Guide
METHOD moveLeft, moveRight
METHOD hide, show
END CLASS
METHOD hide()
RestScreen( ::top, ::left, ::bottom, ::right, ::screenBuf )
RETURN self
METHOD show()
LOCAL oldColor := SetColor( ::color )
::screenBuf := SaveScreen(::top, ::left, ::bottom, ::right)
@ ::top, ::left, ::bottom, ::right BOX ::boxStyle
SetColor( oldColor )
RETURN self
METHOD moveUp( n )
::hide()
::set( ::top - n, NIL, ::bottom - n, NIL )
::show()
RETURN self
METHOD moveDown( n )
::hide()
::set( ::top + n, NIL, ::bottom + n, NIL )
::show()
RETURN self
METHOD moveLeft( n )
::hide()
::set( NIL, ::left - n, NIL, ::right - n )
::show()
RETURN self
METHOD moveRight( n )
::hide()
::set( NIL, ::left + n, NIL, ::right + n )
::show()
RETURN self
// eof scrnrect.prg
A number of new techniques have been sneaked in above. First and most important is inheritance. The
ScreenRect class has been declared by inheriting from the Rectangle class, using the statement:
CREATE CLASS ScreenRect FROM Rectangle
This means that the ScreenRect class inherits all of the instance variables and methods of the Rectangle
class, in addition to its own instance variables and methods. In the hide method, for example, the
instance variables top, left, bottom, and right, which are Rectangle instance variables, have
been accessed, along with screenBuf, which is a ScreenRect instance variable.
Inheriting from another class raises an issue with regard to the initializer method init: the instance
variables of the parent class, or superclass, should also be initialized when a new ScreenRect object is
created. Since the superclass would usually have its own initializer for this purpose, it makes sense to
use it - after all, one of the benefits of object-oriented programming is supposed to be reusability.
Class(y) automates this process, by allowing the parameters to be passed to the superclass to be
specified in the METHOD command. In the above example, the line:
METHOD init( top, left, bottom, right, color, boxStyle ), ;
( top, left, bottom, right )
begins the definition of an initializer method which accepts the specified parameters. Because of the
second parameter list, its first operation is to call the initializer in the superclass with the parameters
specified in the second list. The second parameter list must, of course, match what is expected by the
superclass initializer, in this case that of the class Rectangle.
This init method is typical of most initializers. It passes some of its parameters up to the initializer in
its superclass to ensure that inherited variables are initialized, and then it initializes the instance
variables belonging to the class in which it is defined.
#include "class(y).ch"
PROCEDURE main
LOCAL i
LOCAL rect1 := ScreenRect():new(5, 5, 10, 25, 'R+/G')
// the following uses default colors:
LOCAL rect2 := ScreenRect():new(15, 60, 22, 75)
FOR i := 1 TO 10
rect1:moveDown(1)
rect1:moveRight(1)
rect2:moveUp(1)
rect2:moveLeft(1)
NEXT
RETURN
Note that the RECTANGLE module has been linked in above. Since the ScreenRect class is inherited
from the Rectangle class, the RECTANGLE module is required when linking. If it is omitted, a link
error will occur referring to an undefined symbol RECTANGLE.
You can now run TESTSCRN.EXE. Again, tracing through it in the debugger is a useful exercise
(CLD TESTSCRN).
EXPORT:
METHOD init
METHOD moveUp, moveDown
METHOD moveLeft, moveRight
METHOD hide, show
END CLASS
The class variable activeRects was declared using the CLASS VAR command. Notice that it was
declared before the EXPORT: command, making it a hidden variable. There is no need for other
modules to have direct access to this variable, and making it hidden prevents an external routine from
mistakenly setting the variable to NIL, for example, thereby destroying our list of active objects!
Two class methods were declared using the CLASS METHOD command: hideAll and
initClass. The hideAll method is the one we're trying to define; but where did initClass
come from? It is there for the sole purpose of initializing any class variables. Only one copy of each
class variable is shared by all instances of a class, so initializing these variables in the init method
doesn't make sense, since the class variables would get reinitialized every time an object was created.
We could put in an IF statement to check whether the class variables had already been initialized, but
this would be a workaround at best. The initClass method is intended to take care of this issue. If a
class has a method called initClass, Class(y) will automatically invoke that method only once,
when the class is first created. This makes the initClass method an ideal place to initialize class
variables. Note that initClass must be declared using CLASS METHOD.
Here are the method definitions for initClass and hideAll as they appear in SCRNRECT.PRG:
METHOD initClass
::activeRects := {}
RETURN self
METHOD hideAll
LOCAL i
FOR i := 1 TO LEN(::activeRects)
::activeRects[i]:hide
NEXT i
RETURN self
For this to work correctly, we will need to make sure that the activeRects variable is maintained
correctly, by adding the following code somewhere inside the ScreenRect initializer:
AADD( ::activeRects, self )
This will add each new ScreenRect object, as it is created, to the ::activeRects array.
Finally, we should also ensure that ScreenRect objects get removed from the active list when they are
no longer active, in other words when hide() is invoked. We can do this using something of a
sledgehammer approach, by adding the following code to the hide method:
FOR i := 1 TO LEN( ::activeRects )
IF ::activeRects[i] == self
ADEL( ::activeRects, i )
ASIZE( ::activeRects, LEN( ::activeRects ) - 1 )
END
NEXT i
This is somewhat inefficient, in that it has to scan the array of active rectangles to find self, i.e. the
one that has received the hide message. A better implementation might be to enforce a stack-based
approach, so that only the most recently displayed ScreenRect can be hidden. This would also prevent
problem of restoring screen areas in the wrong sequence. In fact, the sample Window class supplied
with Class(y) uses a List class to achieve something very similar.
Introduction
Having covered some of the basics of creating a class and using inheritance, we'll move on to a more
sophisticated example. The program we are going to discuss implements a general pull-down menu
system. It has been kept fairly simple to serve as a clear illustration of OOP techniques.
Consider a typical pull-down menu:
File Window Block
Load
Edit
Save
Figure 1 Document
Text
We will examine the operation of a menu like this in some detail, to give us a specification to work
from.
A pull-down menu system consists of a number of components, the first of which is the menu bar,
running horizontally across the screen, containing a number of options. This menu behaves like a
normal Clipper menu, allowing options to be highlighted with a light bar controlled by the cursor keys,
or by typing the first letter of the desired option. When an option is selected by pressing the ENTER
key, an action associated with that option is performed. In most cases, this action will be to display a
corresponding menu, 'pulled down' from the menu bar. In the example above, the File option has been
selected, which has resulted in the Load/Edit/Save menu being pulled down. The pulled-down
menu also behaves like a standard Clipper menu, separate from the menu bar. Because the menu's
options are arranged vertically, the up and down arrows are used to move between options. Pressing a
left or right arrow results in the pulled-down menu being closed, and its sibling to the left or right being
pulled down instead. Finally, selecting an option from this menu again results in an action being
executed. Sometimes, this action will consist of yet another menu - but when this happens, the new
menu will not behave like a pull-down menu. Aside from not being pulled down from the menu bar, it
would have no siblings and so the left and right arrow keys cannot select other menus. We will refer to
this kind of menu as a pop-up menu. In the above example, the Save option has been selected from the
pull-down menu, resulting in a pop-up menu containing the options Document and Text.
Class: PopupMenu
Instance window
Class: MenuBar
Variables: width
Instance
Methods: new
Variables:
addItem
draw Methods: draw
exec addItem
menuTop newMenuPos
menuLeft
Class: PullDnMenu
Instance
Variables:
Methods: menuTop
menuLeft
moveLeft
moveRight
setKeys
clearKeys
#include "class(y).ch"
#include "win.ch" // Class(y) sample window library header
EXPORT:
VAR row, col READONLY
VAR isActive READONLY
METHOD init
METHOD draw
MESSAGE exec TO action
METHOD nextCol
METHOD nextRow
END CLASS
METHOD draw
IF ::isActive
@ ::row, ::col PROMPT ::label
ELSE
@ ::row, ::col SAY ::label
END
RETURN self
METHOD nextRow
RETURN ::row + 1
METHOD nextCol
RETURN ::col + LEN( ::label )
// eof menuitem.prg
We have already discussed the role of the self object within methods, but this is a good opportunity
to look at how it works in a real method. Remember that the double colon (::) is shorthand for sending a
message to the self object.
When a statement like item:draw() is executed, if the item variable refers to an object of class
MenuItem, then the draw method in that class will be invoked. Within this method, self is
automatically set to refer to the same MenuItem object referred to by the caller's item variable - the
variable which the draw message was sent to. The statement above, then, will cause that MenuItem's
label to display in the correct row and column, which is also stored in the MenuItem object.
The initializer for the MenuItem class is quite straightforward. It accepts up to five parameters
specifying the position, text, action and status of the MenuItem, and assigns these parameters to its
instance variables. The initializer will be invoked automatically when a new MenuItem object is
created, as in the following statement:
LOCAL item := MenuItem():new(1, 1, "Print", { || Print() })
This would create a new MenuItem object containing the specified values, and set the item variable to
refer to this object.
If you trace through this code in the Clipper debugger, you will see the class functions, such as
MenuItem, being called every time a new object is created. The first time such a function is called,
every line will be executed, since the class is being created; on subsequent occasions, it returns
immediately, passing a reference to the class back to the caller.
This statement makes use of the Class(y) feature known as message forwarding. The MESSAGE
command in this context indicates that we are not defining a method for this message, but we are
specifying what to do in response to the message. In this case, the "TO action" clause specifies that
when the exec message is received by a MenuItem object, the message will be forwarded, with all
parameters, to the object referred to by the action instance variable. If we did not use this message
forwarding feature, we would have to define a MenuItem method such as this:
METHOD exec( oParent )
RETURN ::action:exec( oParent )
Using message forwarding saves code and improves performance. In this example, forwarding is used
to allow MenuItem objects to receive exec messages and respond to them by executing the action
associated with that item - by forwarding the exec message to the object referred to by the action
instance variable. The BaseMenu class implements an exec method which displays and executes a
given menu, so if the object referred to by action is a menu, the exec message will have the desired
effect. In this way, menus can be nested to any depth.
At some point, though, we will want to perform an action other than displaying a menu. Many Clipper
menu systems work by storing actions as code blocks, and evaluating the appropriate block when an
option is selected. To cater for this, code blocks are treated as a special type of object, which can be
evaluated by sending an exec message to them. (Note: in standard Clipper, a similar but
By implementing an exec method in a given class, we can use that class with this menu system
without any modification to the existing menu classes. For example, we might have a Dialog Box class,
which on receiving an exec() message would display itself and allow the appropriate user input.
With a complete population of such classes (e.g. Report, Graph, Browse...), we would do away with
code blocks (for this purpose) completely.
As with exec in MenuItem, the MESSAGE command is used to declare messages which have no
corresponding method defined in the current class. The clause IS DEFERRED causes the messages to
invoke a method called deferred which is defined in the system class, Object. All the deferred
method does is generate an error as follows:
Message should be implemented by subclass
This forces subclasses to redefine these messages. If they do not, the above error will occur. Deferred
methods are often used in abstract classes such as BaseMenu. They document that the specified
message must be overridden in a subclass, and they ensure that if the message is not overridden, a
meaningful error will occur.
In this menu system, only the PullDnMenu class has any need to implement these methods. For the
other subclasses of BaseMenu (PopupMenu and MenuBar), no action needs to be taken in response to
the setKey and clearKey messages, so the following declarations are used in those classes:
MESSAGE setKeys IS NULL
MESSAGE clearKeys IS NULL
Here, the IS clause of the MESSAGE command is used to map the message to the null method,
which like deferred, is defined in the system class, Object. The IS clause can be used to cause any
method defined in a superclass to be invoked in response to the specified message. The Object class is
the ultimate superclass of all classes in Class(y), and it defines methods such as deferred and null
for exactly this purpose.
As discussed earlier, BaseMenu contains an array of MenuItem objects stored in an instance variable
called items. If you look at the draw method in BASEMENU.PRG, you will see that it is quite
simple, just looping through the item array and sending a draw() message to each item in turn. This
has the effect of displaying all the options for a particular menu on the screen. Note that it does not
draw a box around the items; this additional specialized behavior is implemented by the subclasses
PopupMenu and MenuBar, since the one draws a box, or window, while the other merely draws a bar
across the screen.
The BaseMenu initializer takes an array of item/action pairs and initializes the items instance
variable by invoking the addItem method for each item pair. The addItem method creates a new
instance of MenuItem and adds this to the items array.
#include "class(y).ch"
EXPORT:
METHOD init
METHOD addItem
METHOD draw
METHOD exec
METHOD newMenuPos
::items := {}
::currPos := 1
IF aItems != NIL
FOR i := 1 TO LEN( aItems )
// note: following is a bit tricky; invokes addItem
// in the subclass, which takes fewer parameters
// than BaseMenu's addItem.
::addItem( aItems[i, 1], aItems[i, 2] )
NEXT
END
RETURN self
METHOD draw()
LOCAL i
::parent := oParent
WHILE !finished
::draw()
::setKeys()
MENU TO ::currPos
::clearKeys()
finished := ( ::currPos == 0 )
IF !finished
::items[::currPos]:exec( self )
END
END
RETURN self
// eof basemenu.prg
Explicitly invoking a method in a superclass is a very common operation in OOP, and is typically used
in exactly this situation - to add functionality to a method defined in a superclass. In Class(y), super
is a reserved message which all classes accept (since it is defined in the Object class). Sending the
super message results in a reference to the part of the object defined by the superclass. In the above
example, this results in the draw method in the superclass being invoked, instead of the one defined in
the current class.
The MenuBar class, having no instance variables of its own, does not need to perform any initialization
of its own. Accordingly, no init method has been defined. However, MenuBar will inherit the init
method from the superclass, BaseMenu, ensuring that inherited instance variables are correctly
initialized.
// MenuBar.PRG
#include "class(y).ch"
#define OPTION_SPACING 4
METHOD draw()
winCurrent(0) // selects main screen
@ 0, 0 // draw the bar
::super:draw() // invoke superclass' draw method
RETURN self
METHOD newMenuPos
// tells a child menu where to put itself
RETURN ::items[::currPos]:col
// eof menubar.prg
#include "class(y).ch"
#include "win.ch"
EXPORT:
METHOD init
METHOD draw
METHOD addItem
METHOD exec
/*
init()
METHOD draw()
LOCAL bottom, right
IF ::window == NIL
bottom := ::menuTop + len(::items) + 1
right := ::menuLeft + ::width + 1
::window := Window():new( ::menuTop, ::menuLeft, ;
bottom, right, SNGLBORD )
END
::super:draw()
RETURN self
METHOD menuTop
RETURN winTop() + ::parent:newMenuPos()
METHOD menuLeft
RETURN winLeft() + 2
// eof popupmen.prg
28 Class(y) Programmer's Guide
The PullDnMenu Class
PullDnMenu is inherited from PopupMenu, and adds no new instance variables of its own. It overrides
the menuTop and menuLeft methods with its own, which return position values which assume that
its parent menu is a MenuBar (since PullDnMenus are always pulled down from a menu bar).
PullDnMenu also defines setKeys and clearKeys, which you may recall are specified as deferred
methods in BaseMenu, and called by BaseMenu's exec method. setKeys sets the left and right
arrow keys to refer to the moveLeft and moveRight methods, also defined in PullDnMenu.
clearKeys clears these settings.
Notice here that some foresight was required to actually put the calls to these deferred methods in
BaseMenu. In practice, you might find when defining a class lower down in the hierarchy, that you
need to modify a class further up the tree to make it more generic. In this case, declaring and calling the
deferred key-setting methods in BaseMenu have made it more generic, allowing inherited classes to
remap the keyboard as desired, just by defining two methods.
Increasing generality is one of the only valid reasons to modify an existing class when inheriting from
it. If instead we modified BaseMenu by making it more specific, for example by drawing a window
around its menu, all the subclasses would inherit this behavior, or be forced to override it, which would
be inefficient. Classes near the top of the inheritance tree should be, and are, more general, and they get
more specific as the tree expands downwards.
// PullDnMenu.PRG
#include "class(y).ch"
#include "win.ch"
#include "inkey.ch"
EXPORT:
METHOD moveLeft, moveRight
METHOD setKeys, clearKeys
END CLASS
METHOD menuTop
RETURN winTop() + 1
METHOD menuLeft
RETURN winLeft() + ::parent:newMenuPos()
METHOD moveLeft
KEYBOARD CHR(K_ESC) + CHR(K_LEFT) + CHR(K_ENTER)
RETURN self
METHOD moveRight
KEYBOARD CHR(K_ESC) + CHR(K_RIGHT) + CHR(K_ENTER)
RETURN self
METHOD setKeys
SET KEY K_LEFT TO ::moveLeft
SET KEY K_RIGHT TO ::moveRight
RETURN self
METHOD clearKeys
SET KEY K_LEFT TO
SET KEY K_RIGHT TO
RETURN self
// eof pulldnme.prg
...but this gets rather hard to read! In MENUDEMO.PRG, we have taken a middle path, defining one
menu at a time and assigning them to variables until they are needed. This means that the menu has to
be implemented in reverse, defining the deepest menus first (a bit like Reverse Polish Notation on a
Hewlett Packard calculator).
The nested arrays and code blocks do make the code look a bit icky, but again, data-driving the menu
would do away with such problems.
As discussed earlier, if this demonstration program were more object-oriented, it would not use code
blocks at all; instead, all the actions would be objects of some kind. The entire system could then
consist of objects, each with their own specific task, and in this case all tied together by the menu.
Tracing Execution
To get an idea of what happens during actual execution, let's ignore the creation of the menu objects,
which in principle we should already understand, and look at what happens when the statement
oMenuBar:exec() is executed.
Because MenuBar does not directly implement an exec method, the exec method in the superclass,
BaseMenu, is invoked. Once into the menu loop, the first thing this method does is to draw the menu by
sending the draw() message to the self object with the statement ::draw().
It is important to realize that even though we are now executing a BaseMenu method, the self object
at this point is not an instance of BaseMenu, but rather an instance of MenuBar, as referred to by the
variable oMenuBar.
Because of this, the draw method that now gets invoked is MenuBar's draw method; we are now back
down in the MenuBar class. But after this method has drawn a bar across the top of the screen, it
explicitly invokes draw in the superclass - and we're back in a BaseMenu method!
Once BaseMenu's draw has executed and displayed all the menu items, it returns to where it was
invoked, which was MenuBar's draw; this in turn returns to BaseMenu's exec, where execution
continues.
We could continue describing this flow of control for some time, but we have illustrated the basic
point, which is how execution switches up and down the inheritance tree depending on where methods
have been defined.
This can be tricky to come to grips with; again, tracing through the code in the debugger can help a
great deal.
The Code
To start with, we have declared the dBrowse class as follows:
CREATE CLASS dBrowse FROM TBrowse
VAR appendMode
EXPORT:
METHOD autoFields
METHOD exec
METHOD goBottom
METHOD goTop
METHOD skipper
END CLASS
So the dBrowse class inherits all of the instance variables and methods of the TBrowse class. In
addition, a new instance variable and five new or replacement methods are added.
Cargo is a Kludge!
The appendMode instance variable makes an important difference to the code. It stores the current
append mode - ie. whether records are currently being appended. As an instance variable, it is available
to all methods in the dBrowse class.
This is an obvious workaround which highlights the fact that the entire concept of the cargo slot, in
all four of Clipper's predefined classes, is a workaround for the fact that Clipper itself does not support
user-defined classes and inheritance.
TBDemo()
This function demonstrates the use of the MyBrowse() function and does not require much explanation.
A separate module has been created for this purpose, called DBROWDEM.PRG. The important lines
from this module look something like this:
LOCAL dBrow := dBrowse():new(5, 5, 15, 70)
dBrow:autoFields()
dBrow:exec()
We have implemented the browse in three lines here. What each of these lines do is explained more
fully below.
MyBrowse()
This is the main browsing function. It performs three major functions:
• A TBrowse object is created and some of its instance variables are initialized. In a class, this is
usually the job of the initializer, so we have extracted this code to the init method in our
dBrowse class.
• In StockBrowseNew(), a loop is used to create a TBColumn object for each field in the
currently selected database table. This object is then added to the TBrowse object using
addColumn(). To avoid making the dBrowse class so specific that it is only capable of
browsing all fields in the current DBF, this section has been made into a separate method, called
autoFields. It is then under the caller's control whether the columns to be browsed are taken
from the current DBF or from some other source.
Skipper()
This function controls moving the record pointer through the data. This has been made into a dBrowse
method, also called skipper(). The second parameter, browse, is no longer required - it is replaced
by self which is automatically available inside a method.
DoGet()
This function handles the editing of individual cells, or fields, in the browse. This has been made into a
method, also called doGet(). Again, the browse parameter is not required, since it is replaced by the
implicit self.
What Next?
What has been done so far provides little more than a start towards a powerful yet easy to use browsing
class. The changes made to TBDEMO were deliberately kept to a minimum to illustrate how easy it can
be to convert to a class-based design. As a result, our dBrowse class is not that much more useful than
the original TBDEMO. The crucial difference is that it should be easier to expand and enhance - as we
will now see.
dBrowse aBrowse
Database browsing class which Array browsing class
implements methods such as
autoFields()
With this class structure, maximum expandability is achieved, with minimum rewriting of code. An
important point to remember when dealing with inheritance is that you should only have to program the
exceptions and additions in an inherited class - if you end up duplicating code in different classes, there
is probably something wrong with the design. By moving the exec method up into the GenBrowse
class, we avoid such duplication.
To take this one step further, it can easily be seen that at the bottom of the above tree we will need to
inherit a new class from GenBrowse for each different data source we wish to browse. This is not
totally sensible; after all, we are trying to develop a browsing class, not a data providing class - but the
latter would in fact provide an excellent solution. If we had a general purpose data providing class, with
standard methods such as goTop, goBottom, and skip, we could have a subclass of this class for
any type of data we might wish to handle - even, for example, SQL or Paradox data. Hmm... I think
we've just invented RDDs - Replacable Database Drivers! A general purpose data providing class
would give us the capability to implement a single general purpose browsing class which accepted a
data-providing object as a parameter. We could then browse any type of data using the same browse
class. In addition, we would not have to repeatedly implement data-providing methods in in every class
which requires a data source - we would just pass data-providing objects around.
Event Handling
Another area which could stand improvement is the key handling in the exec method's event loop. At
present, the key mappings - which keys are handled and what they do - cannot be changed without
overriding the method in a subclass. To allow more flexibility, we could add an instance variable to the
class to contain an array of paired values - a key value and a code block. By setting this instance
variable to a suitable array, the keyboard mapping can be changed at will by the caller.
Field Editing
There is one last slightly more mundane, but useful, enhancement which could easily be made to our
browsing class system, relating to editing of fields in the browse. The code for editing a field in
TBDEMO and dBrowse is fairly complex - but what if we want a read-only browse? We could
override or otherwise disable the editing behaviour, but the unused code would remain in the class.
Thinking about it, a browsing class is not the right place to put field editing code, anyway.
Editing would then take place, or not, depending on what type of column object had been inserted when
the browse object was set up. No IF statement is necessary here. During setup, read-only and editable
columns could easily be mixed in the same browse.
Summary
The system proposed here would provide a set of classes which would meet the requirement for a
powerful, easy to use, and flexible browsing system. It would be easy to use because it would have
default behaviours, requiring very little code to implement in a calling program. It would be flexible
because other behaviour could be implemented by inheriting new classes from the existing ones. No
functionality has been lost, because methods all the way up the tree to TBrowse can still be used as
required. Having an object-based solution also makes it easy to implemement multiple simultaneously
active browses - something much harder to do with a procedural approach (without simulating objects).
The combination of all of these capabilities is undeniably very powerful, and is a good example of the
kind of benefits which can be achieved with object-oriented programming.
CLASS MESSAGE...IS
Synopsis Define alternative name for inherited class message
Description The behavior of this command is identical to that of the MESSAGE...IS command, except that it
defines an alternative name for a class message instead of an ordinary message.
CLASS MESSAGE...[IS...] TO
Synopsis Forward class message to class variable
Description The behavior of this command is identical to that of the MESSAGE...[IS...] TO command, except that
it defines a class message which is forwarded to a class variable, instead of an ordinary message which
is forwarded to an ordinary variable.
CLASS MESSAGE...METHOD
Synopsis Declare class message and corresponding method.
Arguments <messageName> is the name of the message being declared. Like a Clipper function or variable name,
up to 10 characters are significant. Extra characters will be ignored.
<methodName> is the name of the method to invoke when the specified message is sent. A method of
this name should be defined later in the same module using the METHOD definition command, unless
an external method is being declared.
Description Declares a class message and corresponding class method. Class messages declared with this command
can only be sent to the class object for the class being defined, not to instances of the class. See the
section on Class Messages for more information about class messages and methods.
This command is used when a class method cannot have the same name as the message which invokes
it. If a class message and its method have the same name, the CLASS METHOD command should be
used instead.
See the entry for the MESSAGE...METHOD command for examples and more information.
Arguments <method name list> is a list of names of methods to be declared. The names are separated by commas.
Each method declared should be defined later in the same module using the METHOD definition
command.
Description Declares one or more class methods, and corresponding messages with the same names.
A class method applies to an entire class, rather than to a single instance of a class. Inside a class
method, the self variable refers to the class object via which the method was invoked. Class
variables are thus directly accessible inside class methods, by sending messages to self.
Notes
• If a message and its method cannot have the same name for some reason, the CLASS
MESSAGE...METHOD command should be used instead.
• If a class uses class variables, it is often necessary to initialize them when that class is created. To assist
with this, if a class defines a class method called initClass, the method will be invoked
automatically when the class is first created. This allows one-time initialization of class variables. The
initClass method should be declared as follows:
and defined after the class specification using the METHOD definition command. See the Class
Initialization section for more information.
See Also CLASS MESSAGE...METHOD, Class Messages, Class Methods, Class Variables, Class
Initialization
CLASS VAR
Synopsis Declare class variable(s)
Arguments <variable name list> is a list of variable names to be included as class variables in the current class.
TYPE <type> specifies the type of value which can be assigned to the variable(s) being declared.
<type> can be any one of the following seven Clipper data types: Array, Block, Character,
Date, Integer, Logical or Numeric. An Integer type restriction will only accept integer
values, whereas Numeric accepts both integer and floating-point values. The types Character and
Numeric can be abbreviated to Char and Num.
READONLY or RO restricts assignment to the variable(s) being declared. If the the declared
variable(s) are exported (declared after the EXPORT: command), assignment will only be permitted
from the current class and its subclasses, ie. assignment will have protected visibility. If the variable(s)
are protected, assignment will only be permitted in the current class, ie. assignment will be hidden. If
the variable(s) are hidden, the READONLY clause has no effect.
ASSIGN <assign> allows more control over the restriction of assignment than the READONLY
clause. <assign> can be one of the following three values: HIDDEN, PROTECTED, or
EXPORTED. The value specified will determine the visibility of assignment to the variable(s) being
declared.
SHARED specifies that the class variable(s) being declared will be shared amongst subclasses of this
class. By default, each class has its own copy of all class variables, including those inherited from
superclasses. Specifying the SHARED clause means subclasses will share a single copy of such class
variables.
Description Used inside a class specification to declares one or more class variables. A single copy of each class
variable is shared by its entire class. In this respect, class variables are different from instance
variables, since each instance of a class has its own copy of the instance variables of that class.
Class variables are used to hold information that applies to an entire class. One of the most common
examples of this are variables used to hold lists or counts of active instances of a class. If these were
instance variables, separate copies of them would exist for each object in the class, and all these objects
would have to be updated when a new object was created. As class variables, they would only have to
be set once.
Note Class variable names are only significant up to 9 characters, instead of Clipper's usual ten. See the
section entitled Length of Variable Names.
CLASS VAR...IS
Synopsis Define alternative name for inherited class variable
Description The behavior of this command is identical to that of the VAR...IS command, except that it defines an
alternative name for a class variable instead of an ordinary variable.
Description The behavior of this command is identical to that of the VAR...[IS...] TO command, except that it
forwards class variable access and assign messages to another class variable, instead of forwarding
ordinary variable access and assign messages to another ordinary variable.
CREATE CLASS
Synopsis Begin a class specification
Arguments <class name> is the name of the class to be created. This name follows the same rules as those
governing Clipper function names.
<superclass name> is the name of the class from which the new class should be inherited. If omitted,
the new class will have the superclass Object (the default superclass of all classes). If more than one
class is specified (separated by commas), a multiply-inherited class is created. See the section on
Multiple Inheritance.
<metaclass name> is the name of the metaclass which should be used to instantiate this class. This is
an advanced feature which is not necessary in normal programming. See the section on Metaclasses for
more information.
STATIC specifies that the class function for this class will be declared as a static function, thus will
not be accessible outside the current module.
FUNCTION <function name> specifies a name for the class function which is different from the
class name.
Description Begins a class specification. A class specification is a sequence of Class(y) commands, beginning with
CREATE CLASS and ending with END CLASS, which constitute the structure of a given class, ie.
the names of all variables, messages and methods contained in that class.
Class members (variables, messages and methods) declared after a CREATE CLASS command are
considered hidden by default. This means that they cannot be accessed outside the methods of their
class (see the HIDDEN: command). The EXPORT: and PROTECTED: commands can be used to
achieve a visibility other than the default.
All of the commands defined in this chapter can be used after CREATE CLASS to specify the
structure of a class. Some of the most commonly used commands are:
• VAR
• METHOD
Chapter 6, Class Declaration Commands 39
• MESSAGE...METHOD
• CLASS VAR
• CLASS METHOD
• CLASS MESSAGE...METHOD
• EXPORT:
• PROTECTED:
• HIDDEN:
These commands are collectively referred to as class declaration commands. Refer to their individual
entries for more information.
Once the desired class structure has been declared using these commands, END CLASS is used to end
the class specification.
After the END CLASS command has appeared, the METHOD definition command can be used to
define the methods which were declared in the class specification.
Example A complete definition of a simple Box class follows. The class specification declares four instance
variables and two methods. The methods are defined after the class specification.
#include "class(y).ch"
Notes
• It is best to define only one class per module (single .PRG file). This is to avoid problems in
distinguishing between methods of the same name, such as init, which most classes implement.
• The CREATE CLASS command actually generates a Clipper function with the same name as the
class. This is explained further under the Class Functions and Class Objects sections.
See Also END CLASS, Class Functions, Class Objects, Multiple Inheritance, Metaclasses
END CLASS
Synopsis End a class specification
After the END CLASS command has appeared, the METHOD definition command can be used to
define the methods which were declared in the class specification.
Note After the END CLASS command has appeared in a module, any occurrences of the METHOD
command are treated as method definition commands, rather than method declaration commands.
This means that the behaviour and syntax of the METHOD command is different inside and outside a
class specification block. See the entries for the METHOD (declaration) and METHOD (definition)
commands for more information.
EXPORT:
Synopsis Cause subsequent messages to be exported
Syntax EXPORT:
Description This command causes subsequent variable, message and method declarations in a class specification to
be exported, ie. made accessible to methods and functions outside of the class.
HIDDEN:
Synopsis Cause subsequent messages to be hidden
Syntax HIDDEN:
Description This command causes subsequent variable, message and method declarations in a class specification to
be hidden, ie. not accessible outside of the methods of that class.
Sending a hidden message to an object outside a method of that object's class will result in a Class(y)
error, in the form of a hiddenErr messsage being sent to the object concerned.
MESSAGE...IS
Synopsis Define alternative name for inherited message
<original name> is the name of an inherited message (a message defined in an ancestor class) which
the declared message will be mapped to.
Description This command allows an alternative name to be defined for an existing message. The existing message
must be defined in an ancestor class of the class being created. The command allows a class to be
tailored so that its message names match its behavior.
One common way in which this command is used is to declare messages as deferred or null using a
statement of the following form:
This has the effect of mapping the specified message onto a predefined method in the Class(y) system
class, Object. See the sections on Deferred Methods and Null Methods for more information.
Example Assume that a Vehicle class defines the message width. We would like to define a subclass of
Vehicle, called Boat. However, the width of a boat at its widest point is usually referred to as its beam.
We could handle this by writing a method in the Boat class:
METHOD beam
RETURN ::width
The MESSAGE...IS command provides us with a more efficient shorthand for doing this in the class
specification, as follows:
This achieves the same effect, without the need for a method, and will provide better performance.
Note The MESSAGE...IS command can only be used to define an alternative name for a message in an
ancestor class. To define an alternative name for a method in the current class, use the
MESSAGE...METHOD command. Currently, an alternative name for a variable in the current class
cannot be defined directly.
MESSAGE...[IS...] IN
Synopsis Specify alternative class for multiply inherited message
<ancestor class> is the name of the class which contains the original message.
Description This command allows the default precedence of multiply inherited messages to be overridden. It allows
a message to be defined, in a multiply inherited class, which corresponds to a message in one of the
ancestor classes which would not otherwise be available.
Example Assume that a class named BordTextWindow is multiply inherited from two classes named
BorderedWindow and TextWindow as follows:
By default, because it is listed first, methods inherited from TextWindow will take precedence over
methods of the same name inherited from BorderedWindow. We can override the default precedence as
follows:
Assuming TextWindow also has a method named show, the above declaration will cause it to be
overridden by the one inherited from BorderedWindow. However, this does not necessarily solve all
problems caused by name conflicts. See the warning below for more information.
In our example so far, special action (use of a scope resolution message) would be required to access
the show method inherited from TextWindow. To resolve this, we could define an alternative name for
show in the TextWindow class:
This provides a convenient way to access both methods. Of course, we could have achieved something
similar the other way around:
This relies on the fact the default show would be inherited from TextWindow, and defines an
alternative name (showBorder) for the other show method, inherited from BorderedWindow.
Warning In the first part of the example above, the show message was defined so that it would invoke the show
method defined in BorderedWindow, instead of the one defined in TextWindow. However, this can
lead to a problem if the show message is used in any of the methods defined in TextWindow, or other
ancestor classes. When these methods are invoked via an instance of BordTextWindow, sending the
show message will invoke the method defined in BorderedWindow. This is unlikely to be the behavior
expected by the methods in TextWindow, and usually leads to serious problems.
For this reason, it is best to use the MESSAGE...[IS...] IN command to redefine messages which are
not used in the class in which they are originally defined. See Multiple Inheritance for more
information.
<target name> is the name of the message to be forwarded to the target variable. This argument should
be omitted if its value is the same as <message name>.
<target variable> is the name of the instance variable that the message is being forwarded to. The
variable must already be defined in the current class or an ancestor class. Two special keywords can be
used instead of specifying a target variable: CLASS <className> and SENDER. Their use is
described below.
Description This command causes the specified message to be forwarded to the object referred to by the specified
target variable. This allows messages to be redirected automatically without requiring methods to be
written. See Message Forwarding for more information.
Instead of being forwarded to an instance variable in the current class, messages can be forwarded to an
object's class using the CLASS <className> clause in place of the <target variable>. The name of
the class being forwarded to should always be the name of the current class; other values will be
ignored. This feature will be made more flexible in future versions.
Examples of message forwarding are given below. Taken further, message forwarding can be used to
implement a technique called delegation, which in its fullest sense involves communication in both
directions between delegator and delegatee. One way to achieve this bi-directional communication, in a
class which is designed to act as a target for delegation, is to use the keyword SENDER as the <target
variable> in this command. This causes the specified message to be forwarded to the sending object
(the delegator), allowing the receiver of a delegated message to communicate with the object which
delegated the message. This is discussed further in the Delegation section.
Example
CLASS Graph
VAR oWin // refers to Window object
MESSAGE close TO oWin
...
END CLASS
This class specification would cause instances of the Graph class, upon receiving the close message,
to reroute the message, including any parameters, to the object referred to by the instance variable
oWin.
CLASS Window
VAR oCursor // refers to Cursor object
MESSAGE hideCursor IS hide TO oCursor
MESSAGE showCursor IS show TO oCursor
MESSAGE updateCurs IS update TO oCursor
...
END CLASS
MESSAGE...METHOD
Synopsis Declare message and corresponding method
Arguments <message name> is the name of the message being declared. Like a Clipper function or variable name,
up to 10 characters are significant. Extra characters will be ignored.
<method name> is the name of the method to invoke when the specified message is sent. A method of
this name should be defined later in the same module using the METHOD definition command, unless
an external method is being declared.
Description This command allows a method to be declared which is invoked by a message with a name different
from that of the method. This may be necessary for one of several reasons:
1. The message name conflicts with a Clipper reserved name. Examples of this are left, delete, and
at (see the Clipper documentation section on reserved words). These are names which the Clipper
compiler handles specially, so having methods with the same names is problematic. However, this does
not prevent the use of these names as messages. To map a message to a method with a different name, a
statement such as the following can be used:
In this example, the message left is mapped to the method called boxLeft.
2. When an ordinary function is called which has the same name as the method from which it is being
called. For example, attempting to call a function called Display() from a method called display
will result in an endless recursive loop. To resolve this, give the method a different name, for example:
This maps the display message to a method called boxDisplay. Now if a function called
Display() is called from within the boxDisplay method, everything works as expected.
3. To map a message to a method which is defined in a module other than the one containing the class
specification. This is not recommended practice. See External Methods.
4. To map two or more messages in a class to the same method. This might be required to support an
obsolete message which is still in use. For example:
In this case, the messages getValue and get both map to the same method, named get. Note that
the second statement above uses the default behaviour of the METHOD command to name a message
and method the same name - it would be unnecessary (although it would work) to use MESSAGE get
METHOD get.
Note If the optional CONSTRUCTOR clause is included (or its abbreviation CTOR), then the message is
flagged as a constructor message. See Constructor Messages and Methods for more information.
See Also METHOD (declaration), METHOD (definition), External Methods, Constructor Messages and
Methods
METHOD (declaration)
Synopsis Declare method(s) with same named message(s)
(inside class specification)
Arguments <method name list> is a list of methods to be declared. Each such method should be defined later in
the same module using the METHOD definition command.
Description This command is used within a class specification to declare one or more methods for that class. Once
a method has been declared using this command, the actual code of the method must be defined using
the METHOD definition command (see next entry).
Methods declared and defined in this way can be invoked by sending a message of the same name as
the method to an instance of the class.
With Class(y), the name of a message and the name of the associated method is usually the same, and
the METHOD command assumes this. See the MESSAGE...METHOD command for information
about declaring messages whose methods have a different name.
It is strongly recommended that methods declared using this syntax are defined in the same program
module as the class specification. See the section External Methods for alternatives.
If the optional CONSTRUCTOR clause is included (or its abbreviation CTOR), then the message is
flagged as a constructor message. See Constructor Messages for more information.
Note This form of the METHOD command is referred to as a method declaration command, and is only
valid inside a class specification. After the class specification, the METHOD command is treated as a
method definition command (described in the following entry).
Syntax PROTECTED:
Description This command causes subsequent variable, message and method declarations in a class specification to
be treated as protected, ie. accessible to methods of the current class and its subclasses, but not
accessible from elsewhere.
Sending a protected message to an object outside a method of that object's class or subclasses will
result in a Class(y) error, in the form of a protectErr messsage being sent to the object.
VAR
Synopsis Declare instance variable(s)
Arguments <variable name list> is a list of names of one or more variable to be included in the class being
defined.
CLASS <class> specifies the name of the class which values assigned to the declared variable(s) must
belong. Any attempt to assign values of a different class will result in a runtime error. Under the
Class(y) 2.0 error system, a wrongClass message will be sent to the object which caused the error.
TYPE <type> specifies the type of value which can be assigned to the variable(s) being declared.
<type> can be any one of the following seven Clipper data types: Array, Block, Character,
Date, Integer, Logical or Numeric. An Integer type restriction will only accept integer
values, whereas Numeric accepts both integer and floating-point values. The types Character and
Numeric can be abbreviated to Char and Num.
READONLY or RO restricts assignment to the variable(s) being declared. If the the declared
variable(s) are exported (declared after the EXPORT: command), assignment will only be permitted
from the current class and its subclasses, ie. assignment will have protected visibility. If the variable(s)
are protected, assignment will only be permitted in the current class, ie. assignment will be hidden. If
the variable(s) are hidden, the READONLY clause has no effect.
ASSIGN <assign> allows more control over the restriction of assignment than the READONLY
clause. <assign> can be one of the following three values: HIDDEN, PROTECTED, or
EXPORTED. The value specified will determine the visibility of assignment to the variable(s) being
declared.
Description Includes the named variables, as instance variables, in the class being created. Each object (instance of
a class) created contains its own copy of the instance variables of that class.
If the optional READONLY clause (abbreviation RO) is included, Class(y) will prevent the variable
from being assigned to, except in methods of that class. This has no meaning when used with variables
declared hidden with the HIDDEN: command.
If an assignment occurs which is illegal because a variable has been flagged as read-only, a Class(y)
error will be generated. The error will take the form of a readOnlyErr message being sent to the
object concerned.
Examples
In this class specification, the first VAR statement declares a variable named id. The next statement
declares two variables, nTop and nLeft which are restricted to numeric values. Then a variable named
oWin is declared, which is only permitted to refer to objects of class Window. After the
PROTECTED: statement, a variable named nCount is declared as READONLY. Since the variable
is protected, declaring it read-only prevents assignment to the variable outside the methods of the
Dialog class. Finally, an exported variable named cTitle is declared, restricted to containing character
data and, since it is exported but read-only, it is not assignable outside methods of the Dialog class or
its subclasses.
Note Instance variable names are only significant up to 9 characters, as opposed to Clipper's usual 10. See
the section entitled Length of Variable Names.
VAR...IS
Synopsis Define alternative name for inherited variable
Arguments <variable name> is the alternative name that the inherited variable will be given in the current class
<original name> is the name of an inherited variable (a variable defined in an ancestor class) which
the declared alternative name will be mapped to.
Description This command allows an alternative name to be defined for an existing variable. The existing variable
must be defined in an ancestor class of the class being created. The command allows a class to be
tailored so that its message names match its behavior.
METHOD beam
RETURN ::width
This handles access to the width variable via the beam message. As discussed under the
MESSAGE...IS command, this could be done more efficiently in the class specification as follows:
However, neither of these solutions handles assignment via the beam message. To support assignment
statements of the form:
oBoat:beam := x
This will allow both access and assignment to the width variable via the beam message.
Note The VAR...IS command can only be used to define an alternative name for a variable in an ancestor
class. Currently, an alternative name for a variable in the current class cannot be defined directly. A
method can be defined to do this if necessary.
VAR...[IS...] IN
Synopsis Specify alternative class for multiply inherited variable
Arguments <variable name> is the name that the variable will have in the current class.
<original name> is the name of an inherited variable (a variable defined in an ancestor class) which
the declared variable will be mapped to. This argument should be omitted if its value is the same as
<variable name>.
<ancestor class> is the name of the class which contains the original message. This argument is only
required in multiply inherited classes.
Description If a multiply inherited class inherits a variable with the same name from two different classes, one will
always override the other. This command can be used to specify that such a variable should be
inherited from a class other than the default, or given a different name in the subclass being defined.
Specifying a class other than the default from which to inherit a variable is probably only useful if the
variable has been defined with qualifications, such as a TYPE or CLASS restriction.
Chapter 6, Class Declaration Commands 49
Specifying an alternative name for a multiply inherited variable will only help to resolve name conflicts
if the variable is not used in an ancestor class.
Example Assume that a class named BordTextWindow is multiply inherited from two classes named
BorderedWindow and TextWindow as follows:
By default, because it is listed first, variables inherited from TextWindow will take precedence over
variables of the same name inherited from BorderedWindow. We can override the default precedence
as follows:
VAR x IN BorderedWindow
Assuming TextWindow also has a variable named x, the above declaration will cause it to be
overridden by the one inherited from BorderedWindow. However, this does not necessarily solve all
problems caused by name conflicts. See the warning below for more information.
In our example so far, special action (use of a scope resolution message) would be required to access
the x variable inherited from TextWindow. To resolve this, we could define an alternative name for x
in the TextWindow class:
This provides a convenient way to access both methods. Of course, we could have achieved something
similar the other way around:
This relies on the fact the default x would be inherited from TextWindow, and defines an alternative
name (bordX) for the other x variable, inherited from BorderedWindow.
Warning In the first part of the example above, x was defined so that it would refer to the x variable defined in
BorderedWindow, instead of the one defined in TextWindow. However, this can lead to a problem if x
is used in any of the methods defined in TextWindow, or other ancestor classes. When these variables
are accessed via an instance of BordTextWindow, the variable defined in BorderedWindow will be
accessed. This is unlikely to be the behavior expected by the methods in TextWindow, and usually
leads to serious problems.
For this reason, it is best to use the VAR...[IS...] IN command to redefine variables which are not used
in the class in which they are originally defined. See Multiple Inheritance for more information.
VAR...[IS...] TO
Synopsis Forward variable's messages (assign and access)
<target name> is the name of the variable, in the target class, which <variable name> will be
mapped to. This argument should be omitted if its value is the same as <variable name>.
<target variable> is the name of the instance variable that the variable messages being declared will
be forwarded to. The target variable must be a member of the current class or of a superclass. Two
special keywords can be used instead of specifying a target variable: CLASS <className> and
SENDER. Their use is described in the entry for the MESSAGE...[IS...] TO command.
Description This command causes the specified variable's access and assignment messages to be forwarded to the
object contained in the specified target variable. This makes it appear as though the variable exists in
the class in which this command is used.
Example
CLASS Graph
VAR oWin // refers to Window object
EXPORT:
VAR nGraphTop IS nTop TO oWin
// ...
END CLASS
This class specification will cause instances of the Graph class, upon receiving the nGraphTop
message for access or assign, to respond by sending the equivalent nTop message to the object referred
to by the instance variable oWin. Thus, for example, the following code has the ultimate effect of
assigning the number 5 to the nTop variable in the Window object contained in the Graph object
oGraph.
VISIBLE:
Synopsis Cause subsequent messages to be exported
Syntax VISIBLE:
Description This command is equivalent to the EXPORT: command. It is provided for symmetry with the
HIDDEN: command. Its use is optional.
METHOD (definition)
Synopsis Begin definition of a method (after a class specification)
Arguments <method name> is the name of the method being defined. It must be a valid Clipper function name.
<parameter list> is the optional list of parameters expected by the method. If included, it must be
surrounded by parentheses.
Description This command is used to begin the definition of a method (the actual code of a method). It is usually
included in the same module (.PRG file) as the class specification. Use of this command ensures that
the self variable is properly initialized and that the method is not directly visible from other modules.
Methods defined with this METHOD definition command must also be declared inside the class
specification, using the METHOD declarationcommand (see previous entry).
By default, a method defined with the METHOD definition command, without the FUNCTION or
PROCEDURE specifier, will behave like a Clipper function, in that it must return a value using the
Clipper RETURN statement. To declare a method which does not return a value, the optional
PROCEDURE specifier can be used. However, it has become a convention to return self from
methods which have no other return value, eliminating the need for the PROCEDURE specifier in
most cases.
Notes
• The METHOD (definition) command also has an extended form which is used when defining
initializer methods, discussed in the Usage Reference.
• Methods declared with this command are internally represented as static Clipper functions, so cannot
be accessed directly outside the module in which they are defined and will not conflict with methods or
functions declared in other modules.
self
Synopsis Reference to current object in methods
Description self is a local instance variable which is automatically defined in all methods. It contains a reference to
the current object, ie. the object which received the message which invoked the current method.
For example, a statement such as obj:open() will normally invoke the open() method. Inside that
52 Class(y) Programmer's Guide
method, the self variable will contain a reference to the same object referred to by obj.
Since self is typically used heavily inside methods, a special operator, the double-colon (::) has been
provided as a shorthand for sending message to self. See the :: entry for more information.
See Also ::
::
Synopsis Send message to self
<parameter list> is the list of parameters to be sent to the method being invoked.
Description The double-colon is shorthand for a message send to self, used inside a method.
For example, instead of an expression such as self:aMsg, the double colon can be used, as in
::aMsg. Examples of this abound in the sample source.
When messages to self are being sent often, which is the case in most methods, this abbreviation can
make code far more readable. Its use is recommended.
@:
Synopsis Invoke method in same class without using a message send
Arguments <method name> is the name of the method to be invoked. It must be a method in the same class as the
currently executing method, and should either be defined in the same program module or according to
the rules governing external methods.
<parameter list> is the list of parameters to be sent to the method being invoked.
Description Similar to the double-colon (::) for message sends to self, the 'at-colon' allows direct invocation of
methods in the same class as the invoking method. Since the location of the invoked method is known
in such a case, a message lookup is not necessary. A direct invocation of this kind is faster than the
equivalent message send, but this is a trade-off for loss of flexibility.
For those familiar with C++, @: allows methods to be invoked in a way similar to that in which
ordinary (non-virtual) C++ member functions are invoked. Because of dynamic or late binding,
methods in Clipper normally behave in a similar way to C++ virtual methods.
Classes
Class Objects
In Class(y), every class is associated with a class object. Class objects are very important - without
them, you would not be able to create new objects.
A class object contains details of the class it is associated with, such as its name and details of its
structure. The values of the class variables that have been defined for a class are stored in its class
object, along with the values of class variables inherited from its ancestor classes.
A class object responds to messages associated with any class variables and class methods that have
been defined for that class.
A reference to a class object can be obtained in one of the following ways:
• by calling a class function (see next section)
• by sending the class message to an object
• by accessing self inside a class method
The following sections deal with various aspects of classes and class objects in more depth.
Class Functions
Declaring a class with the CREATE CLASS command causes a Clipper function to be created. This
function has the same name as the class and is called a class function. Calling a class function returns
the class object for that class. The class function is most often used when creating new objects (see
next section). Class functions can also be used to access class variables and class methods. Here are
some examples of different ways to use class functions:
oCust := Customer():new( 5, 10, 15, 70 )
? Window():nWinCount
Window():closeAll()
The first line creates a new object of class Customer by sending the new() message to the Customer
class. The second line accesses a class variable called nWinCount in the Window class. The third line
sends the closeAll() message to the Window class. In all of these cases, a class function is called
to obtain a reference to a class object.
Terminology For most purposes, the terms class and class object are interchangeable. The class object is the class.
Note For example, we said above "The third line sends the closeAll() message to the Window class".
We could have added the word object onto the end of that sentence, but it would be redundant. There is
no way to send a message to a class other than via a class object.
Class Messages
A class message applies to a class as a whole, rather than to individual instances of that class. Class
messages can be associated with class variables or class methods. A single copy of each class variable
is shared among all the instances of a class. A class method can access these class variables, via its
self variable, which is always a class object. Class variables and class methods are discussed in more
detail below.
Class Variables
Class variables are declared using the CLASS VAR command.
Chapter 8, Usage Reference 55
The value of a class variable is shared by all instances of a class. Changing the value of a class variable
changes it for all objects in the class.
The class variable values for a class are stored in its class object. The value of a class variable can be
accessed or changed by sending a message to the appropriate class object.
Class variables are useful for maintaining information that applies to all objects in a class. Common
uses for class variables include:
• maintaining a count of active instances, such as the number of active windows in a Window class.
• maintaining a reference to the currently active instance, such as the currently active DBF object in
a DBF class.
• maintaining lists of instances for various purposes, such as a list of active windows.
A class inherits its own copy of the class variables in its superclass. In other words, the values of
inherited class variables are not shared. However, it is possible for a subclass to access a class variable
belong to a superclass - either by using the class function for the desired superclass, or by using the
super message.
For example, in a Window class which needs to keep track of which window is currently selected, we
might use a class variable called currSelected for this purpose. In methods in the Window class,
we could then use code such as this:
IF self == ::class:currSelected
to test whether self is the currently selected window. The expression ::class sends the class
message to self, which results in a reference to self's class object. The currSelected message
is then sent to the class object to obtain the value of the corresponding class variable.
By using the class message to obtain a reference to the class object, we ensure that subclasses of this
class will access their own copy of the currSelected variable. In many cases, this is the desired
behavior; but depending on the structure of the class hierarchy, we may wish subclasses of Window to
share the same currSelected variable. We could do this by rewriting the code as follows:
IF self == Window():currSelected
Using a class function such as Window() to access a class variable will always result in the same class
variable being accessed. Even if this method was being executed for an instance of a subclass of
Window, the Window class variable currSelected is accessed.
However, this technique suffers from a serious disadvantage because subclasses still have their own
copy of the variables, even if they are never used. Aside from the wasted storage, the potential exists
for inadvertently accessing the wrong variable, causing the program to behave incorrectly.
A safer technique for implementing a variable whose value will be shared by subclasses, is to use a file-
wide STATIC variable in the appropriate class, and provide methods to access and change the value of
that variable as necessary.
Class(y) provides yet another alternative in the form of the SHARED clause of the CLASS VAR
command. Specifying the SHARED clause when declaring a class variable will cause that variable to
be shared by subclasses. See the CLASS VAR command for more details.
Class Methods
Class methods are declared using the CLASS METHOD command.
A class method is a method which operates on an entire class (or a subset of a class), rather than on a
single instance of that class. Any operation which deals with more than one instance of a class is a good
candidate for a class method.
In a class method, the self variable refers to the class object for that class. This means that class
methods can access class variables directly. For example, in the previous section, the expression:
::class:currSelected
Class Initialization
When a class is first created (the first time its class function is called), some initialization may need to
take place. For example, if a class has class variables, those variables may need to be initialized. To
assist with this, if a class defines a class method called initClass, the method will be invoked
automatically when the class is first created. This allows one-time initialization of class variables. The
initClass method should be declared in the class specification as follows:
CLASS METHOD initClass
and defined after the class specification using the METHOD definition command. The method will be
invoked when its class is first created.
The initClass method should be written with inheritance in mind. A subclass inherits its own copy
of all the class variables defined in its ancestor classes. When such a subclass is first created, it is
necessary to initialize those inherited variables. If initClass methods have been properly defined in
the ancestor classes, a method definition such as the following will ensure correct initialization:
METHOD initClass(), ()
// initialize class variables defined in
// this class, but not inherited variables
The first line of this method definition includes an empty second parameter list, which causes the
initClass() method in the superclass to be invoked, ensuring correct initialization.
Warning If operations other than initialization of class variables are performed during the initialization of a
class, care must be taken to ensure that these operations are not repeated unintentionally when
subclasses are created. For example, if a database table is opened during initialization of a class, it may
not be desirable to reopen the table when a subclass is created. Appropriate conditional code should be
included in the initClass method to prevent this.
Shared class variables - class variables declared with the SHARED clause of the CLASS VAR
command - are particularly prone to this problem. In most cases, an inherited shared class variable
should not be reinitialized when a subclass is created. To prevent this, code such as this can be used:
METHOD initClass(), ()
IF ::someVar == NIL
::someVar := <value>
END
RETURN self
Assuming someVar has been declared as a shared class variable, this initClass method ensures that
it will not be initialized more than once, even when subclasses are created.
This statement sends the new() message, with the specified parameters, to the Window class. The
Window class responds by creating a new Window object, and initializing it by invoking the new
method defined for the Window class. A reference to the newly created and initialized Window object
is returned to the caller, and in this case the object reference is stored in the variable obj.
Comparing Classes
Class objects can be used to check whether different objects belong to the same class. For example:
IF obj1:class == obj2:class
? "obj1 and obj2 belong to the same class"
ELSE
? "obj1 and obj2 do not belong to the same class"
ENDIF
This code uses the Class(y) predefined message class to obtain a reference to each object's class
object, and compares the two references.
This uses the class message to obtain a reference to the class object for the class to which obj
belongs. By comparing this to the reference to the class object returned by a class function such as
Window() (assuming Window is a class), we can determine whether obj belongs to that class or not.
Comparing Objects
When comparing two variables to see if both refer to the same object, the exact comparison operator
(double equal, ==) must be used. The inexact comparison operator (single equal, =) will cause a
runtime error, as will a 'not equal' (<> or !=). If you wish to check that two variables refer to different
objects, it can be done as follows:
IF !(obj1 == obj2)
This is the standard behaviour of the Clipper language, and similarly applies to comparisons of arrays.
When comparing an object with NIL, however, the 'not equal' operator can be used. For example:
IF obj <> NIL
Constructor Messages
Constructor messages are used to create new instances of a class. The default constructor message,
new(), is implemented by Class(y) and inherited by all classes.
When a constructor message is sent to a class object, Class(y) creates a new, empty object of that class
and sends the init() message to it, to initialize it. The init method is referred to as an initializer
method, and is discussed in the next section.
All arguments passed to the constructor are passed in turn to the init method to allow it to initialize
the object correctly.
When the initializer method returns, the constructor returns the new object to the caller. It is thus not
necessary for the initializer method to return a value, although by convention, methods which do not
return a value return self.
Initializer Methods
If a class defines a method named init, that method will be invoked automatically when new objects
of that class are created. The init method is referred to as an initializer method. Its purpose is to
initialize a new object to a valid initial state, usually based on arguments supplied to the constructor.
When writing the code for an initializer, if the class has a superclass, it is usually necessary to invoke
the superclass' constructor to initialize the part of the object defined by the superclass. Class(y)
provides an automatic mechanism for doing this. The METHOD definition command has an extended
form which allows a second parameter list to be included. Including this second parameter list causes
the corresponding superclass method to be invoked first, with the specified parameters. The syntax for
this is as follows:
METHOD <name>(<params,...>), [<superName>](<superParm,...>)]
where:
<name> is the name of the initializer method.
<params> the parameters which the method expects to receive.
<superName> is the name of the superclass' initializer. This can and should be omitted when the
superclass method being invoked has the same name as the method being defined. In
particular, when defining an init method it is not necessary to specify the name of
the superclass method unless it is other than init.
<superParm> are the parameters expected by the superclass' constructor. These would typically be a
subset of the first parameter list.
Sometimes a subclass may not need to perform any work to initialize itself (perhaps because it has no
instance variables). In such cases, it is not necessary to define an initializer for that class. If a superclass
defines an initializer, it will be inherited by the subclass and used during initialization.
Anomaly
There is an 'anomaly' in the Clipper debugger in version 5.01: if an object only has one instance
variable, the instance variable name is not displayed. To remedy this, Class(y) causes a dummy
instance variable to appear in the instance variable list in such cases. The name of this dummy variable
is "__________". Its value is NIL. Try to ignore this variable.
By using METHODNAME() in ERRORSYS, you can get the following more informative listing:
Called from: POPUPMENU(↑):EXEC(71)
Called from: POPUPMENU:EXEC(73)
Called from: MENUITEM:EXEC(53)
Called from: PULLDNMENU/POPUPMENU(↑):EXEC(72)
Called from: PULLDNMENU/POPUPMENU:EXEC(73)
Called from: MENUITEM:EXEC(53)
Called from: MENUBAR/BASEMENU:EXEC(72)
Called from: MAIN(49)
A file called CYERRSYS.OBJ is provided with Class(y) to implement this. It is a recompiled version
of a very slightly modified ERRORSYS.PRG. It can be linked in along with the other object files in an
application, automatically resulting in the enhanced error trace.
If you have your own modified version of ERRORSYS, all you need to do is change one line to obtain
the enhanced error trace. In the original ERRORSYS, there is a line similar to the following (usually
line 131):
? "Called from", Trim(ProcName(i)) + ;
Recompile the module. Link this module with your system to obtain the enhanced error trace.
The general format of the string returned by METHODNAME() is as follows:
<recvClass>[/<implClass>][(↑)]:<message>[(*)]
This will result in a private variable named tmp being created. The names of these intermediate
variable should be chosen so as not to conflict with variables in the application being debugged.
• Complexity of expressions is limited in other ways. For example, the expressions of the form
obj:arr[n] will not evaluate. Again, the workaround for this is to use intermediate variables.
Deferred Methods
A deferred method is a method which is declared in a particular class, but only defined later in a
subclass of that class. Declaring a method as deferred documents the fact that a class expects the
specified method to be reimplemented by a subclass, and will cause a meaningful error to be generated
if an attempt is made to invoke a deferred method that has not been redefined.
A deferred method is declared in a class specification as follows:
MESSAGE <message name> IS DEFERRED
A method declared as DEFERRED must not be defined in the same module as it is declared. Any such
definition will be ignored. Rather, it should be both declared (as a normal method) and defined in
subclasses of the class in which it is declared as deferred.
Null Methods
A null method is a method which takes no action, and returns a value of NIL. A method can be declared
null to suppress the behavior of that method in a subclass of the class in which it was originally
defined, or to declare a method in a superclass which can be optionally overridden in a subclass.
A null method is declared in a class specification as follows:
MESSAGE <message name> IS NULL
A method declared as NULL must not be defined in the class where it is declared. Any such definition
will be ignored.
Also, the Class(y) instantiation syntax can (optionally) be used instead of Clipper's. Four class
functions have been provided for this purpose: TBrowse(), TBColumn(), Get(), and Error(). For
example:
oBr := TBrowseNew(5, 5, 15, 70)
becomes:
oBr := TBrowse():new(5, 5, 15, 70)
The VAR cargo declaration in the MemoGet class redefines the cargo variable so that it can be used
by clients of MemoGet.
If your classes will be used by other people, it is best to redefine cargo in this way, so that full
compatibility with the published specification for the GET class is maintained.
Performance Because of the way in which inheritance from GET has been implemented, access to instance variables
Note in subclasses of GET is slower than it is for other classes. In most cases, this is not noticeable.
However, it can become a problem with large or complex subclasses of GET. If that is the case, a
possible alternative to subclassing GET is to design a wrapper class which contains a GET object as
one of its instance variables, and delegates all GET messages to that object. Of course, the performance
gain obtained by not subclassing GET may be cancelled by the overhead of delegating all of GET's
messages. The best solution depends on the requirements of the application.
The above script can also be used with Blinker, if CLASSY.LIB is not being overlaid.
Blinker
If you don’t need to overlay the Class(y) library, you can use the above statements with Blinker. In a
large system, however, you will probably want to explicitly overlay Class(y) using script statements
such as the following:
MODULE _CYCLASS, CSYXSEND # MUST be in the application root!
BEGINAREA
#...
FI C:\CLASSY2\LIB\CSYINSP.LIB # optional: for object inspector
FI C:\CLASSY2\CYERRSYS # optional: for error trace
LIB CLASSY
#...
ENDAREA
Note that CLASSY.LIB contains two modules, _CYCLASS and CSYXSEND, which must not be
overlaid. To ensure this, the statement MODULE _CYCLASS, CSYXSEND is placed outside of the
overlay area.
Do not include an .OBJ extension in the module names. These are module names, not filenames −
Blinker will not recognize an extension if one is included, and this will result in an incorrectly linked
executable.
The enhanced object inspector contained in CSYINSP.LIB can be completely overlayed. It is
implemented in Clipper, so it will usually be overlayed automatically depending on which linker and
linker mode is being used.
Linking of Classes
Because methods are not usually called directly, but invoked via a message at runtime, it is not possible
for the linker to automatically link in only the methods required by a particular application. This is why
all the methods for a class are usually defined in the same module as the class specification, so that all
the methods of a class are linked in along with the class function, whether they are used in the linked
application or not.
This is an inevitable consequence of late binding in an object oriented system. Even (mostly) early
bound C++ is not immune to this effect.
Those who have experienced memory problems with Clipper in the past may shudder at the idea of
linking in procedures which are not necessary at runtime, but it is not as bad as it sounds. The code
overlaying system in Clipper 5.x was designed with such systems in mind. In fact, the ceiling for
program size under Clipper 5.x is likely to be related to the size of the symbol table and use of fixed
memory rather than actual code size. The fact that Clipper linkers can perform symbol table
compaction also helps in this area.
Object-oriented systems, especially in the Clipper world, also often tend to be more data driven than
their procedural counterparts, resulting in smaller code sizes.
So, in general, one should not try to optimize linkage of classes by using external methods, since this
results in what amounts to manual linking.
However, a good design practice which should be followed in any case, is to keep individual classes
small. Large, single, 'do-everything' classes should be avoided. Functionality should be added by
inheritance. When choosing which class to use for a particular purpose, in most cases it should be
possible to choose a class at the correct level of the inheritance tree such that a minimum of
unnecessary methods are linked in.
Message Forwarding
Methods of the following form occur quite often in most programs:
METHOD foo( <params,...> )
RETURN ::someObj:foo( <params> )
Such methods can easily be replaced by a class declaration command such as:
Chapter 8, Usage Reference 65
MESSAGE foo TO someObj
Performance
The Class(y) kernel is written in optimized C and assembler code, and it executes as fast as it is
possible to do in the Clipper environment.
As you know, Clipper is a sophisticated and powerful application development language, which in
many ways pushes DOS past its limits - for example, by making use of virtual memory to circumvent
the 640K limitation.
As a result, Clipper and Class(y) will perform best if given the resources they need.
For best performance, Clipper applications should be linked as protected mode applications, using a
DOS extender such as Computer Associates’ ExoSpace. The resulting applications run in protected
mode and can access up to 16MB of extended memory directly, resulting in the best possible
performance for a Clipper application, especially one which makes heavy use of objects and thus
virtual memory (all objects in Clipper are stored in virtual memory, along with character strings and
arrays).
If, for some reason, you are unable to make use of protected mode in your application, the next best
alternative is to provide plenty of expanded memory (EMS) to the application - preferably at least a
megabyte, or more for larger applications. Otherwise, Clipper may be forced to swap virtual memory
pages to disk, resulting in a dramatic performance drop.
Important! If your application is running in real mode and no EMS is available to it, performance may be severely
affected. Providing EMS or running in protected mode can speed up such an application as much as ten
times - or more!
Applications should, of course, be overlayed as efficiently as possible, so that as much memory as
possible is available for virtual memory paging and other purposes. Link options which increase the
root size of an application, such as incremental linking, should be avoided if memory or performance is
at a premium. Symbol table compaction should be enabled when linking such systems.
For additional information about performance, please see the section Instantiation and Performance
(under Constructors and Initializers).
Predefined Messages
Every class created by Class(y) is automatically given three predefined messages, as follows:
super
class
perform
In addition, a number of messages are defined in the Object class, and are thus inherited by all classes.
Two such messages are standard messages which native Clipper supports:
66 Class(y) Programmer's Guide
classH
className
super
The super message allows messages to be sent to an object's superclass. This is a very important
capability, since in a subclass method, it is often necessary to invoke a method of the same name in a
superclass. The easiest way to do this is by using the super message, as follows:
obj:super:message()
Note that the result of a super message is just a reference to the original object, so it does not usually
make sense to save this value. See Reference Objects for more information.
class
The class message, when sent to an object, returns the class object for that object's class. See the
section Class Objects for more information.
classH
The classH message, when sent to an object, causes a number to be returned which identifies the
class of the receiving object. This number is used mainly for internal purposes. Although it could be
used for comparisons between objects in a similar way to the class message, this is not
recommended.
className
The className message retrieves the class name, as a character string, of the object to which it is
sent. For example, the expression oWin:className would return the string "Window" if the
variable oWin referred to an object of class Window.
The className message is supported by standard Clipper. The Clipper classes such as TBrowse
return a class name string which is capitalized, eg. "TBROWSE". However, class names of Class(y)
classes retain the capitalization used in their definition.
Important! Using the className message to determine what class an object belongs to is not recommended. It is
intended mainly for informational and debugging purposes. To determine the class of an object, use the
class message as described in the section Checking an Object's Class.
perform
The perform message sends a specified message to the target object. The message to be sent is
specified by an object of class Symbol. A Symbol object must be the first parameter to the perform
message. All following parameters are passed to the invoked method as is.
For example, the following code fragment creates a Symbol object referring to the move message and
sends it to the object obj, using the perform message.
LOCAL oSym := Symbol():new( "move" )
obj:perform( oSym, 15 )
This has the effect of sending the move message to obj. The second parameter passed to perform
above, 15, will be passed to the move method as its first parameter.
Alternate Constructors
Constructor messages other than new() can be declared using the CONSTRUCTOR clause of the
METHOD (declaration) command, as follows:
METHOD <methodName> CONSTRUCTOR
or:
METHOD <methodName> CTOR // abbreviated form
Each constructor message declared in this way must have a corresponding initializer method defined in
that class. The initializer method should have the same name as the constructor message.
When such a constructor message is sent to a class, it invokes a predefined system method (called
altNew) which creates a new object and sends the appropriate initializer message to the new object.
Delegation
In object-oriented programming, the term delegation can be defined in various ways.
At one end of the scale, message forwarding, in which an object forwards a message to another object,
can be described as delegation, if the first object is delegating responsibility for responding to the
message to the second object. Message forwarding is very simple to implement, using the
MESSAGE...[IS...] TO command, as described in the section on Message Forwarding.
At the other end of the scale, some (mostly academic or experimental) object-oriented languages use
delegation to completely eliminate the need for classes or class inheritance. To achieve this, such
languages usually handle the self variable differently. When the receiver of a delegated message (the
delegatee) sends a message to self, the message is sent to the original delegator.
Although class-based languages do not behave this way, this behavior is important in that it gives the
delegatee a means of communicating with the delegator, allowing polymorphism to be achieved so that
the delegator can override methods defined in the delegatee.
Class(y) provides a facility to help simulate this behavior, by allowing messages to be sent to the
sender, or delegator, of a message. This can be done in one of two ways: by using the SENDER()
function to obtain a reference to the sender (delegator), or by using the MESSAGE...[IS...] TO
SENDER command to define messages which are automatically forwarded to the sender.
The following sections cover these topics in more detail.
This kind of code takes delegation to an extreme, and should only be used in situations where there is a
clear advantage to using delegation instead of inheritance.
When implementing full delegation like this, be aware that at present, the SENDER() function currently
only supports full polymorphism for one level of delegation. If a message is doubly delegated, ie.
delegated by the delegatee, SENDER() will refer to the intermediate delegatee, not to the original
delegator. This is likely to change in future versions.
Partial Polymorphism
In most uses of delegation other than simple message forwarding, it is likely that something short of
full polymorphism between the delegator and delegatee will be required. In these cases, message sends
to SENDER() in the delegatee class will be mixed with message sends to self, as appropriate.
Warning When writing such code, care should be taken to avoid the incorrect invocation of methods which
assume that they are being invoked via delegation. The SENDER() function will only be valid if such
methods are in fact invoked via delegation. Invoking such methods in another way can lead to endless
recursion and other aberrant behavior. In future verions, the SENDER() function may be changed to
return NIL if it is invoked in an incorrect context, so be aware of this.
Delegating to Sender
When implementing partially polymorphic delegatee classes as described above, it can be useful to
have certain messages forwarded to the delegator automatically. For example, if a TextWindow class
delegates messages to a TextBlock class, the TextBlock may need to obtain the coordinates of the
delegating window. These can be obtained using expressions such as SENDER():nTop. However, if a
subclass of TextBlock were defined, the implementor of the subclass would need to know that it should
obtain these values from the sender. This situation can be improved by defining messages or variables
in the TextBlock class that are forwarded to the sender, for example:
VAR nTop TO SENDER
This allows subclasses of TextBlock to send the nTop message to self, instead of SENDER(), to
obtain the required value. The implementor of the subclass no longer needs to know which messages or
variables are actually defined in the delegating class.
Warning As always with partially polymorphic delegatee methods, they should only be invoked via delegation.
A method that relies on messages that are automatically forwarded to the sender, as in the above
example, will behave incorrectly if not invoked via delegation.
Error System
All classes in Class(y) v2.0 ultimately inherit from the Object class (defined in CSYOBJEC.PRG). If
no superclass is specified when creating a class, Object is used automatically.
External Methods
An external method is a method which is defined in a different module from the one in which its class
specification appears. This could be done for a number of reasons:
• To implement a method in a C module.
• To have a single method which applies to a number of different classes. In most cases, however,
such methods would be better implemented in a parent class from which all classes requiring that
method are inherited, directly or indirectly.
• To prevent a method from being linked in automatically. This is not a recommended practice. See
the section Linking of classes.
External methods should be defined (using the METHOD (definition) command) in a module which
does not contain a class specification. Since such methods then automatically have public scope, they
should be given names that will not conflict with any other names in the application. To cause an
external method to be linked into an application, you must include a Clipper EXTERNAL declaration
with the name of the method either in the module containing that method's class specification, or in the
module which invokes the method.
Instantiation
This section will examine in detail how an object gets created. Class(y)'s instantiation mechanism is
very open and flexible, and can be customized if necessary.
We will examine what happens when the new() message is sent to a class object, as in the following
statement:
oWin := Window():new( 7, 4, 18, 25 )
1 The new() message is sent to the class. Assuming the class does not have a class method named new,
the predefined new method in the Class class will be invoked.
2 The new method in the Class class creates an empty instance of the class described by the class object
which received the message. The empty instance is created with the Class method basicNew(),
which creates a new instance of a class but does not initialize it. Another example of the use of
basicNew() can be found in the copy() method in the Object class.
3 Having created an empty instance of the correct class, the next step is to initialize it. This is done by
sending the init() message to the newly created object, along with the parameters received by
new(). This invokes the new method defined in the class (the initializer), which should initialize its
instance variables appropriately, and return. It returns to the Class new method, which returns the
initialized object to the caller.
70 Class(y) Programmer's Guide
Here is a slightly simplified version of the source to the new() method in the Class class:
METHOD new
// create an instance of the class described by SELF.
LOCAL newObj := ::basicNew()
// invoke the initializer method for that object
newObj:init()
RETURN newObj
One thing is missing from the above code: parameters. Usually, a new() message is accompanied by
parameters specific to the class being instantiated. Under the 2.0 architecture, these parameters would
usually be passed to the new() method in Class, but in fact it is the initializer method in the class
being instantiated that needs the parameters. Accordingly, all parameters supplied to the Class new()
method are passed through to the new object's init() method unchanged. To achieve this efficiently,
the Class new() method is actually implemented in C.
One further twist requires explanation: constructors with names other than new. This allows multiple
constructor methods for different instantiation requirements. A class which supports constructor
methods other than new needs a definition for the corresponding messages in both the class and the
metaclass. If we examine the relevant command definition in CLASS(Y).CH, we will see something
like this:
#xcommand METHOD <method> CONSTRUCTOR ;
=> ;
METHOD <method> ;;
CLASS MESSAGE <method> IS altNew
An ordinary method is declared using the METHOD command - this is the actual constructor method
which will initialize the parameter. Then a class message is declared which is mapped onto the
altNew message, defined in the Class class.
When a constructor message other than new is sent to such a class, the altNew() method in Class
will be invoked, via the synonym which was declared.
There is thus no longer anything special about constructor methods themselves other than the fact that
ones other than new require a class message mapped onto the altNew method.
Note that the CONSTRUCTOR clause in the METHOD command is only required when declaring
constructor methods other than new().
Metaclasses
Class(y) v2.0 treats classes as real objects in every sense. They have a class of their own - the
predefined class called Class, or a subclass thereof.
The Class class is used to create new classes, as can be seen in the v2.0 headers. The information used
to create a class remains in existence in the class object for each class, so classes can be queried at
runtime as to their structure.
Important The operation of metaclasses is not an issue with which most programmers need be concerned.
Note Metaclasses are necessary to create a consistent, object-oriented, open architecture which allows
Class(y) to be cleanly enhanced, extended and integrated with other object-oriented system-level
products. However, for most ordinary programming tasks, metaclasses are not required.
For those who are interested, the architecture of the metaclass system will be briefly discussed.
Warning! This subject can be quite confusing, even to experts, and the description given here is far from being
comprehensive. Again, it is not necessary to understand the metaclass architecture to use Class(y) v2.0.
Any simple class is created as an instance of the Class class. Instances of the Class class contain
variables such as name, superClass, and messages. The contents of these variables describe a
particular class.
Sending the class message to an object will return the class object for that class. Examining these
objects in the debugger can be instructive.
If a class has class variables, then its class object must contain those variables. For example, if a
Window class contains a class variable called activeList, the variable is stored in Window's class
Chapter 9, Advanced Topics 71
object, not in its instances. The class object also contains the variables mentioned above, such as name
and superClass.
Such a class object - one containing an additional variable such as activeList - is obviously not just
an instance of the Class class. It must have a class of its own, which we call a metaclass, because it is
the class of a class.
A metaclass, like any other class, is an instance of the Class class. But what sets it apart from ordinary
classes is that its superClass field refers to the Class class, or to another metaclass. In other words,
all metaclasses ultimately inherit from the Class class. This ensures that instances of a metaclass have
the same basic structure as described by the Class class, in addition to any other variables or methods
which they may define.
In the Window example, the Window metaclass would contain just a few entries in its messages array,
including an entry defining the activeList variable. Instances of the Window metaclass will
therefore contain such a variable, in addition to the variables which they inherit from their superclass,
which as mentioned above, is Class.
The diagram below illustrates this. The boxes are somewhat similar to the display provided by the
debugger when looking at a class object. The predefined Object class is shown, since all classes
ultimately inherit from it. The Class class is shown, since all classes are instances of it or one of its
subclasses. A Rectangle class is shown as a direct instance of the Class class. The Rectangle class has
no class variables of its own, so it can be a direct instance of Class.
The Window class inherits from the Rectangle class. Since Window has a class variable of its own,
activeList, it needs a metaclass to describe its structure. The Window metaclass is shown inheriting
from the Class class. The Window class is an instance of the Window metaclass. Since the class
described by the Window metaclass is a subclass of Class because the superclass field refers to Class -
the Window class inherits all of the methods and instance variables defined in Class, as well as the
variable activeList, defined in the Window metaclass.
Each box below is a Class(y) object. The boxes also happen to be class objects, since they are all
instances of the Class class or one of its subclasses (even the Class class object is an instance of the
Class class, an interesting and necessary circular relationship).
In the diagram below, the header of each box specifies the class represented by that box. In each box's
left-hand column, the names of the class object's variables are listed. In the right hand column,
corresponding to each variable, is the value of that variable. Overall, it is somewhat similar to the kind
of display you see in the debugger.
The heavy arrows point from a subclass to its superclass. The light arrows point from an object to its
class, ie. to the class it is an instance of.
instance
subclass
of
of
inst
Class class Rectangle class
of
name "Class" name "Rectangle"
superClass Object superClass Object
messages { "init", inst messages { "init",
"name", "draw",
of
"superClass", "top",
"messages", "left",
... } "bottom",
"right",
... }
subclass instance
of of subclass
of
inst
Window class class Window class
of
name "Window class" name "Window"
superClass Class superClass Rectangle
messages { "activeList" } messages { "init",
"buf",
"open",
"close",
... }
activeList NIL
To complete the picture, sample individual instances of the Rectangle and Window classes are shown
below.
Point of The arrows in the first diagram indicate that Object is an instance of Class, even though Class is a
Interest subclass of Object. This important 'chicken-and-egg' relationship is set up by Class(y) on initialization.
Another aspect of this relationship is the instance arrow which points from the Class class back to
itself. The Class class object is itself an instance of the Class class.
Multiple Inheritance
The CREATE CLASS command can be used to create multiply inherited classes, as follows:
To implement multiple inheritance correctly, Class(y) examines the ancestor classes of a multiply
inherited class, and determines a single path through the ancestor tree. This affects which messages
take precedence in the final class. It allows repeated inheritance, in which the same class occurs
repeatedly in the ancestor tree. Class(y) treats repeatedly inherited classes as a single class. A diagram
should help to explain this:
Object
|
Box
|
Window
/ \
TextWindow BorderedWindow
\ /
BordTextWindow
The class BordTextWindow is multiply inherited from TextWindow and BorderedWindow. Assume
that the BordTextWindow class was created with the following statement:
CREATE CLASS BordTextWindow FROM TextWindow, BorderedWindow
Class(y) will search for messages in the classes in the following sequence:
BordTextWindow, TextWindow, BorderedWindow, Window, Box, Object
Although the Window class is inherited from both branches of the tree, it is treated as a single class, ie.
you do not end up with two sets of Window instance variables in the resulting class.
The algorithm for determining a single path through the inheritance tree uses two rules:
• A class always has precedence over its superclass; ie. a superclass is never searched before any of
its subclasses present in the ancestor tree of a multiply inherited class.
• Each class sets the precedence order of its direct superclasses.
Invoking Inititalizers
In the init() method of a multiply inherited class, it is usually necessary to invoke the initializers of
all direct parent classes. Since the METHOD definition command only allows a single parent
initializer to be invoked, it is best to invoke parent initializers explicitly. For example, assume a class is
multiply inherited from two classes, x and y. The init method in the subclass should then look
something like this:
METHOD init(...)
::x:init(...)
::y:init(...)
//...
RETURN self
This makes use of scope resolution messages to invoke the correct method in the specified parent class.
Scope resolution messages are discussed further in the following sections.
Name conflicts
As mentioned, repeated inheritance is handled by treating repeatedly inherited classes as a single class.
This prevents any possibility of naming conflicts due to repeated inheritance. However, naming
conflicts can still occur between classes.
A naming conflict occurs when a message of the same name is inherited from two different branches of
the hierarchy. When this occurs, the default behavior for the duplicate message will be determined by
whichever class is first in the linearized superclass precedence list described earlier. In other words,
one message will override the other.
In such situations, it is usually necessary to be able to access both messages. One mechanism for doing
this is the scope resolution message, of the form:
obj:<className>:<message>
The default inheritance sequence can be overridden for specific messages, by including the appropriate
statement in the class specification. For example, in the class BordTextWindow, we could include the
following statement:
MESSAGE show IN BorderedWindow
This would cause BorderedWindow's show to be invoked by default when the show message is sent to
an instance of BordTextWindow.
You can also provide an alias for a message as follows:
MESSAGE borderedShow IS show IN BorderedWindow
This allows both show messages to be accessed without resorting to the use of a scope resolution
message. Since a scope resolution message involves an extra message send, using message aliases is
more efficient.
Warning
Duplicate messages in multiply inherited classes can lead to some very subtle problems. Imagine that
another method such as move() in the BorderedWindow class invoked show() as follows:
::show()
When executing move() for an instance of BorderedWindow, there is no problem. However, when
executing move() for an instance of BordTextWindow, the line above will always result in the default
show() method being invoked, which in our original example is in the TextWindow class. This
behavior is likely to be incorrect. There are at least two ways of addressing this problem:
• Redefine the duplicate message in the subclass, and take appropriate action, eg. in
BordTextWindow:
METHOD show
::TextWindow:show()
::BorderedWindow:show()
RETURN
These scope resolution messages would be redundant for instances of the BorderedWindow class,
but would help to ensure correct behavior in multiply inherited subclasses of BorderedWindow.
Reference Objects
Reference objects are mainly for internal use, but they may be of use in applications.
A reference object refers to another object, in much the same way that two different variables in
Clipper can refer to the same array (or object). The difference is that a reference object can belong to a
different class from the object it refers to - it can belong to any class in the superclass chain of the
object it refers to. A reference object can be obtained in one of two ways:
• By sending the classname of an ancestor class to an object. For example, in the expression
obj:rectangle, if obj belonged to a class which had an ancestor class named Rectangle, a
reference object of class Rectangle would be returned. The resulting object refers to the same
Chapter 9, Advanced Topics 75
object as obj, but will only handle messages belonging to class Rectangle and its superclasses.
This is similar to casting an object pointer in C++.
• By sending the super message to an object. The expression obj:super results in a reference
object belonging to the superclass of the class of obj, but referring to the same object as obj.
Reserved Words
Class(y) defines a number of system classes. Some of these have names which may conflict with other
names in your application, specifically Class and Object. If your application defines functions or
classes with these names, you will need to modify your application accordingly. Most other Class(y)
system classes have names which have been qualified by a three-letter prefix such as CSY or CMS, so
they should not conflict with other class or function names. See the System Classes section for more
information.
It is also unwise to use the scalar class names Array, Block, Character, Date, Integer,
Logical, Nil, and Numeric, since these class names are used by standard Clipper, and some of
them, such as Array and NIL, are Clipper reserved words.
Reserved The Class and Object classes define a number of messages which are inherited by other classes in the
Message system. Some of these, such as those covered in the Predefined Messages section, should not be used
Names as message names elsewhere. However, others can be overridden, as with any inherited method.
Overriding inherited system methods should be done with caution, since it could lead to compatibility
problems when used with classes written by other people.
Clipper Clipper reserved words cannot be used as method names, but they can be used as message names. To
Reserved define a method with the same name as a Clipper reserved word, use the MESSAGE...METHOD
Words command. See the Clipper documentation for a full list of reserved words.
Scalar Classes
Class(y) treats standard Clipper data types as though they were objects. Each data type corresponds to a
class. The class names are the same as those used in the TYPE clause of the VAR command, with the
addition of Nil - Array, Block, Character, Date, Integer, Logical, Nil, and Numeric.
Strictly speaking, the term scalar refers to a value that has magnitude but no other attributes. The term
is stretched slightly here when applied to character strings, code blocks and even dates. However, with
the exception of arrays, from a Clipper standpoint the built-in data types are all scalar-like in that they
consist of a single value. We will therefore use the terms scalar values and scalar classes to refer to
Clipper data values and data types other than arrays.
The Array class is nevertheless dealt with here, since as a built-in data type it follows most of the rules
for defining scalar classes.
Source code to the Array class is provided. Many of the methods defined in the Array class are used by
the Class(y) kernel. The Array class can be extended and even subclassed, and other scalar classes can
be user-defined. More information can be found in the files CSYARRAY.PRG and ARRSIZE.PRG.
Default scalar classes other than Array are automatically created by Class(y) if they are not supplied by
the user.
To define one of these scalar classes, a command such as the following should be used:
CREATE CLASS Character ;
FROM ScalarObject FUNCTION csyCharacter
All scalar classes except Array must inherit from the ScalarObject class, as shown above.
Since some of the classnames used by Clipper, such as ARRAY, are also the names of Clipper functions,
a FUNCTION clause has been used above to define a class with a name different from that of its class
function. This clause must be included when defining a scalar class, and the specified function name
must consist of the letters CSY prepended to the one of the abovementioned scalar class names. It is
this function name that Class(y) uses to detect user-supplied scalar classes.
76 Class(y) Programmer's Guide
The scalar classes can clean up code quite a bit; for example, now, you can have statements like:
IF obj:isKindOf( Window() )
instead of:
IF obj != NIL .AND. obj:isKindOf( Window() )
The reason is that the NIL class is a subclass of Object and supports the isKindOf message, just like
any other class. This sort of thing happens quite often in most code, in one form or another.
Another example is using eval; you can now use:
block:eval(...)
instead of:
IF block != NIL
EVAL(block, ...)
END
This works because eval (and exec) have been defined in the Object class as null methods; sending
them to NIL will do nothing.
The deepCopy method in the Object class provides another example of this. For each element of an
array/object, it executes:
newObj[i] := self[i]:copy()
No matter what type of value is stored in self[i], the above code will work, because even data such
as numbers and strings will respond correctly to the copy message (this behavior for scalar data is
defined in the ScalarObject class).
It is also straightforward to cause objects of all types, including scalars, to respond to standard
messages such as size. For example:
CREATE CLASS Date FROM ScalarObject FUNCTION csyDate
EXPORT:
METHOD size
END CLASS
METHOD size
RETURN 8
By default, Class(y) supports the size message when sent to arrays and strings. Behavior of other
scalar classes is user-definable.
Subclassing Scalars
Given the architecture of Clipper, it is meaningless to attempt to subclass a scalar class, since the type
of a value determines its class, and the Clipper data types are predefined.
Index 3