Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                

CLASSY

Download as pdf or txt
Download as pdf or txt
You are on page 1of 87
At a glance
Powered by AI
This document provides an introduction to object-oriented programming concepts in Clipper and an overview of how to use the Class(y) extension library to implement object-oriented features in Clipper.

The purpose of this document is to serve as both a programmer's guide and manual for the Class(y) library, which allows developers to add object-oriented capabilities to the Clipper programming language.

Some of the main topics covered in the document include introductions to object-oriented concepts, how to use the Class(y) library to create classes and define methods, inheritance between classes, class and instance variables, and more.

Class(y)

The Object-Oriented Language Extension


for CA-Clipper

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.

"Class(y)" is a trademark of Anton van Straaten. “AppSolutions” is a trademark of AppSolutions, Inc.


Other brand and product names are used for identification purposes only and may be trademarks or
registered trademarks of their respective holders.
Table of Contents

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

ii Class(y) Programmer's Guide


RTLINK and ExoSpace ............................................................... 64
Blinker ......................................................................................... 64
Link Tips and Traps ................................................................................... 64
Linking of Classes ...................................................................................... 65
Message Forwarding ................................................................................................ 65
Performance ............................................................................................................. 66
Predefined Messages ................................................................................................ 66
super........................................................................................................... 67
class............................................................................................................ 67
classH ......................................................................................................... 67
className .................................................................................................. 67
perform ...................................................................................................... 67
9 Advanced Topics 68
Alternate Constructors.............................................................................................. 68
Delegation ................................................................................................................ 68
Delegation vs. Inheritance.......................................................................... 68
Full Polymorphism Using Delegation ........................................................ 68
Partial Polymorphism................................................................................. 69
Delegating to Sender.................................................................... 69
Error System............................................................................................................. 69
External Methods ..................................................................................................... 70
Instantiation .............................................................................................................. 70
Metaclasses .............................................................................................................. 71
Multiple Inheritance ................................................................................................. 73
Invoking Inititalizers .................................................................................. 74
Name conflicts ........................................................................................... 74
Warning ..................................................................................................... 75
Reference Objects .................................................................................................... 75
Reserved Words ....................................................................................................... 76
Scalar Classes ........................................................................................................... 76
Scalars and new() ....................................................................................... 77
Subclassing Scalars .................................................................................... 77
System Classes ......................................................................................................... 78
Index 1

Table of Contents iii


1 Getting Started
Welcome to Class(y), the standard for class creation in CA-Clipper. Class(y) provides complete object-
oriented programming capabilities, seamlessly extending Clipper’s power as an application
development language.
We at AppSolutions, Inc. thank you for purchasing Class(y) and hope you will enjoy using it.
Please take a few moments to fill out the enclosed software registration card, and mail it today.
Software registration is an important part of a software purchase and is the only way we know who you
are and how to get in touch with you. Product upgrades, bug fixes, documentation addenda, and the like
are not possible until you fill out the registration card and mail it to us. Please don’t put it off − do it
today!

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.

AppSolutions, Inc. LICENSE AGREEMENT AND LIMITED PRODUCT WARRANTY

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.

Major and Minor Upgrades


A change in the major version number indicates a major upgrade, and a change in the minor version
number indicates a minor upgrade. Both major and minor upgrades include new features and offers
benefits over previous versions. A major upgrade typically provides more significant features and
benefits than a minor upgrade. The upgrade from version 1 of Class(y) to version 2 was an example of
a major upgrade.
When a major or minor upgrade becomes available, we will notify all registered users by mail. The
price of a major or minor upgrade depends on the significance of the features that have been added, and

2 Class(y) Programmer’s Guide


the amount of work which went into its development. Minor upgrades are priced lower than major
upgrades.

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.

Chapter 1, Getting Started 3


Upgrading from Class(y) v1.0x
If you are upgrading from Class(y) v1.0x, please print out and read the file CONVERT.TXT, which can
be found in the \CLASSY2\DOCS directory. It explains how to compile code in the “Version 1
compatibility mode” supported by Class(y) v2.0. “Version 1 compatibility mode” is provided to allow
Class(y) v1.0x code to be compiled with few or no changes. CONVERT.TXT also describes how to
convert files to “Version 2 native mode”. We suggest that you begin by using compatibility mode to
compile v1.0x classes, and convert to native mode incrementally. Classes compiled in different modes
can be mixed in the same application.

Guide to the Manual

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.

Introduction and Tutorial


To familiarize yourself with Class(y), we suggest that you work through this section, i.e. chapters 2
through 5:
Chapter 2, Introduction to Object Orientation, is essential reading unless you are already very
familiar with object-oriented concepts and terminology. Even if you are familiar with another object-
oriented language such as C++, you should read this chapter since there are areas in which object-
orientation in Clipper differs from other OO languages.
Chapter 3, Using Class(y), deals with the basics of defining and using classes, including compiling and
linking a program with Class(y). If you’re in a hurry to get started, you could start by working through
this section, but don’t forget to read Chapter 2 and work through the tutorials in Chapters 4 and 5
before attempting to make serious use of Class(y).
Chapter 4, Pull-Down Menu Tutorial, and Chapter 5, Inheriting from Tbrowse, are tutorials which
cover the design of some sample classes in some detail. You should work through these regardless of
your degree of expertise since they contain hints and information about ways to use Class(y) in real
programs.

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.

4 Class(y) Programmer’s Guide


Other Reading
Most of the material in this manual deals either with object-oriented programming in general or with
programming in Class(y). However, there is more to object technology than OOP. A very important
aspect is that of object-oriented analysis (OOA) and object-oriented design (OOD).
Object-oriented analysis refers to the process of analyzing a problem from an object-oriented
perspective, with the aim of achieving an object-oriented design for a solution to the problem being
analyzed. During the analysis and design phases, the problem is broken down by identifying classes and
objects, and the relationships (such as inheritance or composition) between them. The programming
phase involves actually writing the code to implement the classes, objects and relationships identified
during analysis and design.
The process of successfully developing a typical application usually iterates repeatedly through
analysis, design and programming. Issues raised during design or programming often lead to a new or
more detailed analysis being performed, which in turn leads to new design and programming. This kind
of development has proved to be one of the most successful ways of implementing systems, and object-
oriented systems are well suited to developing in this way.
It is beyond the scope of this manual to provide an in-depth discussion of object-oriented analysis and
design. However, many excellent books on the topic are available, as shown in the list below.
The list contains books about object-oriented analysis, design and programming, as well as other
aspects of object technology such as database systems (ODBMS), interface design, and managerial
perspectives. If you are serious about taking advantage of object technology, you will want to obtain
and read some of these books.
At the very least, we highly recommend that you read more than one of the books covering analysis and
design. Different authors espouse different approaches, and familiarity with more than one approach
helps in determining which issues are fundamental, and which are artifacts of the particular approach
being used.
The list below is based on a list compiled by Larry Dysert and posted on the CLIPPER forum on
CompuServe. Thanks, Larry, and all the other regulars in the OOP section on the Clipper forum (they
know who they are)!

Chapter 1, Getting Started 5


Book List
An Introduction To Object-Oriented Programming
Author: Timothy Budd
ISBN: 0-201-54709-0
Publisher: Addison-Wesley
Designing Object-Oriented Software
Author: Rebecca Wirfs-Brock, Brian Wilkerson, and Lauren Wiener
ISBN: 0-13-629825-7
Publisher: Prentice-Hall
Object-Orientation - Concepts, Languages, Databases, User Interfaces
Author: Setrag Khoshafian and Razmik Abnous
ISBN: 0-471-51801-8
Publisher: John Wiley & Sons, Inc.
Object-Oriented Analysis
Author: Peter Coad and Edward Yourdon
ISBN: 0-13-629981-4
Publisher: Yourdon Press (Prentice-Hall)
Object-Oriented Analysis & Design
Author: James Martin and James Odell
ISBN: 0-13-630245-9
Publisher: Prentice-Hall
Object-Oriented Databases
Author: Dimitris Chorafas and Heirich Steinmann
ISBN: 0-13-491804-5
Publisher: Prentice-Hall
Object-Oriented Databases
Author: Setrag Khoshafian
ISBN: 0471-57056-7
Publisher: John Wiley & Sons
Object Oriented Design
Author: Peter Coad and Edward Yourdon
ISBN: 0-13-630070-7
Publisher: Yourdon Press (Addison-Wesley)
Object-Oriented Design With Applications
Author: Grady Booch
ISBN: 0-8053-0091-0
Publisher: Benjamin Cummings Publishing Company
Object-Oriented GUI Application Development
Author: Geoff Lee
ISBN: 0-13-363086-2
Publisher: Prentice Hall
Object-Oriented Information Systems
Author: David Taylor
ISBN: 0-471-54364-0
Publisher: John Wiley & Sons, Inc.
Object-Oriented Interface Design
Author: IBM
ISBN: 1-56529-170-0
Publisher: Que
Object-Oriented Methods
Author: Ian Graham
6 Class(y) Programmer’s Guide
ISBN: 0-201-56521-8
Publisher: Addison Wesley
Object-Oriented Modeling And Design
Author: James Rumbaugh, et al
ISBN: 0-13-629841-9
Publisher: Prentice-Hall
Object-Oriented Programming: An Introduction
Author: Greg Voss
ISBN: 0-07-881682-3
Publisher: McGraw Hill
Object-Oriented Software Construction
Author: Bertrand Meyer
ISBN: 0-13-629049-3
Publisher: Prentice-Hall
Object-Oriented Systems Analysis
Author: David Embley/Barry Kurtz/Scott Woodfield
ISBN: 0-13-629973-3
Publisher: Yourdon Press
Object-Oriented System Development
Author: Dennis de Champeaux, Doug Lea, and Penelope Faure
ISBN: 0-201-56355-X
Publisher: Addison-Wesley
Object-Oriented Technology: A Manager's Guide
Author: David Taylor
ISBN:
Publisher: Addison-Wesley
Objects In Action: Commercial Application Of O-O Technology
Author: Phil Harmon and David Taylor
ISBN: 0-201-63336-1
Publisher:
Programming In An O-O Environment
Author: Raimund Ege
ISBN: 0-12-232931-7
Publisher: AP Professional
The Tao Of Objects
Author: Gary Entsminger
ISBN: 1-55851-155-5
Publisher: M&T Books

Chapter 1, Getting Started 7


2 Introduction to Object Orientation

Object Orientation and Clipper 5


Object orientation and object-oriented programming (OOP) is at present revolutionizing the
programming landscape. Until relatively recently, however, this subject would not have been of
immediate interest to Clipper programmers, since neither Clipper 5.01 nor Clipper 5.2 provide the
features necessary to implement OOP. Most importantly, while Clipper provides four predefined object
classes (TBrowse, TBColumn, Get and Error), it does not provide the ability to create one's own object
classes, or user-defined objects (UDOs). Because of this, for Clipper programmers, OOP has been more
of a dream to which we could only aspire, than an attainable goal.
Fortunately, the outlook for OOP in Clipper changed with the introduction of Class(y) (pronounced
"CLASSY"). Class(y) actually extends the Clipper language, providing a comprehensive range of
features designed to support true object-oriented programming.
Class(y) enhances Clipper by providing the ability to create and use user-defined object classes,
allowing fully object-oriented programs to be developed. It provides all the key features of an object-
oriented language, including encapsulation, inheritance and polymorphism.
Class(y) combines the best object-oriented features of C++ and Smalltalk with the database power of
Clipper, and adds some uniquely powerful capabilities of its own. As a result, Clipper with Class(y)
stands alongside C++ as one of the very few object-oriented languages capable of being used for
practical commercial application development.
How does Class(y) work? In the Clipper library CLIPPER.LIB, there is a module which is used
internally to create the predefined object classes. This module is also responsible for handling messages
sent to these objects. Class(y) replaces this module completely. The replacement module provides
object-oriented capabilities not found in standard Clipper, such as the ability to create user-defined
classes and objects, as well as other features designed to support true object-oriented programming.
The replacement module is written in C and assembly language, and is optimized for high performance.
The new features work totally transparently, allowing the creation of user-defined classes which can
then be used in exactly the same way as Clipper's predefined classes.
To use Class(y) effectively, an understanding of the concepts involved in object orientation is required.
In the next section, we will examine object orientation in general. This will be followed by practical
examples using Class(y).

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

8 Class(y) Programmer's Guide


Windows, as well as UNIX GUIs such as Open Software Foundation's Motif, and Sun Microsystems'
Open Look.
Although object orientation applies in slightly different ways in each of the areas mentioned above, the
same basic concepts are being applied in each case. Because of its broad scope, the term is often
misused, especially in marketing claims; indeed, articles have been written on this subject, such as "My
Cat is Object-Oriented", by Roger King of the University of Colorado.
This abuse often arises from the fact that object orientation in user interfaces is not easy to define
clearly, and it is through user interfaces that end users encounter object orientation, usually without
realizing it. Some vendors assert that their products are object-oriented merely because they use screen
windows - the windows are objects, the argument goes, and therefore the program is object-oriented.
This is perhaps an extreme of misrepresentation, but the situation is complicated by in-between
products such as Microsoft Windows. At both the user interface and programming level, Windows is
object-oriented in many ways, but in other ways falls far short of being "fully" object-oriented.
The aspect of object orientation which Class(y) addresses is that of object-oriented languages. The
features required of an object-oriented language are well defined, and existing language products set
something of a standard in this area. Once familiar with the principles of object-oriented languages, it
becomes much easier to differentiate between true and false claims about object orientation in other
areas.
One of the main driving forces for the adoption of OOP is likely to be the need to produce programs
that run under graphical user interfaces such as Microsoft Windows. This means that changing from
procedural to object-oriented programming may involve changing not just the language being used, but
the operating environment, resulting in an extremely steep learning curve.
While GUIs promise to make life easier for the end user, they will only make it harder for the
programmer, unless we are prepared to change our programming style. Writing programs with a
completely object-oriented architecture simplifies development for GUIs, since the program
architecture reflects the architecture of the underlying environment.
Although we cannot write Microsoft Windows applications using standard Clipper just yet, we can
prepare ourselves by starting to develop object-oriented programs. This will allow us to climb the
learning curve gradually, rather than suddenly being forced to learn a new programming style as well as
the complexities of event driven programming in a GUI.
We'll start our climb of the learning curve with a brief look at the history of object-oriented languages,
followed by an introduction to object-oriented concepts.

Brief History of Object-Oriented Languages


The concept of an object class and inheritance, central to object-oriented languages, was first
implemented in the language Simula 67, an extension of Algol 60 designed in 1967 by Ole-Johan Dahl
and Kristen Nygaard from the University of Oslo and the Norwegian Computing Center (Norsk
Regnesentral). Although Simula, as it is now called, is a general purpose programming language, it is
not in wide usage.
A major milestone in the development of object-oriented languages was the Smalltalk research project
at the Xerox Corporation's Palo Alto Research Centre (PARC). Starting in the early 1970s, the
Smalltalk project, initiated by Alan Kay, had as its goals more than just the development of a
programming language; rather, a complete integrated environment was the goal, including an object-
oriented language, development tools, and a graphical interface. The standard components of modern
graphical user interfaces, such as windows, icons, and the mouse, were pioneered at Xerox PARC.
The Smalltalk language itself was the first 'true' object-oriented language in that it dealt exclusively
with objects. All subsequent object-oriented languages have been based on the concepts used in
Smalltalk. Smalltalk was important, not just for its language, but for the development tools available in
the Smalltalk environment. These include class browsers and object inspectors. A class browser is a
very powerful tool which allows program code to be edited in a much more convenient and structured
way than with conventional editors. Because of the inherently well-defined structure of object-oriented
Chapter 2, Introduction to Object Orientation 9
programs, the class browser is capable of displaying a given program's class hierarchy in graphical
form, allowing the user to 'point and shoot' to select a particular method (procedure) to be edited. Many
programming tasks become menu driven, such as the creation of new classes, modifying the structure
of the inheritance tree, and modifying the structure of a class. These operations are more complex and
tedious when performed in a traditional editing environment.
Tools such as these are an integral part of the promise of object-oriented technology. They can simplify
a programmer's life, reducing development time and costs. Although they are a rarity in the DOS world
at present, as the move toward object-oriented technology grows, and as we move towards GUIs like
Microsoft Windows, these tools will become more commonplace.

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.

Classes, Instances and Instance Variables


In any given system, many objects will exist. Many of these objects will be very similar to each other:
for example, you might have thousands of invoice objects stored on disk. Although each one is
different, they all share a common set of attributes. The same operations are valid on all of them. There
is a term to describe such a collection of similar objects: it is called a class.
A class can be thought of as a template, or specification, for creating an object. The class itself consists
of details specifying what the structure of its objects should be. The class can also be said to 'contain'
the program procedures which are permitted to operate on objects of that class.
For example, an Invoice class would contain procedures for printing and updating invoices. It would
also contain details of the structure of an invoice object, for example that each invoice object must
contain variables named date, customer, amount, etc.
To look at it another way, we can define an object as an instance of a class. A given class may have
many instances of itself (objects) in existence at any one time. Each of these instances has a structure
determined by the class to which it belongs, but they are distinguished from each other by the data
within that structure, which is stored in instance variables. The term instance variable distinguishes a
variable that belongs to an object class from the ordinary stand-alone variables that we are used to.
Instance variables are contained within objects, and are not directly accessible outside of those objects,
although they can be accessed by sending messages to their objects.

10 Class(y) Programmer's Guide


Messages and Methods
Earlier, an object was defined as a module containing both procedures and data. An object's procedures
are known as methods. This terminology helps to distinguish them from procedures which are not
associated with an object, since there are fundamental differences. In a fully object-oriented system
such as Smalltalk, there are no procedures, only methods. In a hybrid system such as C++, or Clipper
with Class(y), both methods and procedures can coexist.
Methods are not called in the same way that procedures are. Rather, messages are sent to objects,
which respond by executing the appropriate method. All valid operations on or using the data in an
object are defined by its methods, so all operations on an object are accomplished by sending messages
to the object. Because of this, it is not necessary for other objects to access, or even know about, the
internals of foreign objects. Objects behave like black boxes: send them a message, and they respond
by executing the appropriate method. Send the print message to an invoice object, and it will respond
by printing itself. This black box approach is known generally as encapsulation, and while it is
possible to achieve in procedural systems, object-oriented systems actively encourage, support and
enforce it.

Inheritance - Superclasses and Subclasses


Common properties among groups of classes can often be combined to form a parent class, or
superclass. For example, it might make sense for a Quotation class, an Order class, and an Invoice
class to all share the same superclass, a Sales Document class. The Quotation, Order, and Invoice
classes are thus subclasses of the Sales Document class. This is known as inheritance. The subclasses
inherit all the properties of their superclass, and may add unique, individual properties of their own.
This concept can be extended further, with subclasses of subclasses. Such class hierarchies are a
common feature of object-oriented systems.
Inheritance is one of the most powerful features of object-oriented programming, since it allows reuse
of existing code in new situations without modification. When a subclass is derived from a superclass,
only the differences in behavior need to be programmed into the subclass. The superclass remains
intact and will usually continue to be used as is in other parts of the system, while other subclasses are
using it in different ways.

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).

The Class Specification


We will use the creation of a simple rectangle class as our first example. This class will consist of little
more than the coordinates of the rectangle, along with a method to set these coordinates. Here is the
class specification:
// rectangle.prg

#include "class(y).ch"

CREATE CLASS Rectangle


VAR top, left
VAR bottom, right
EXPORT:
METHOD init
METHOD set
METHOD width, height
METHOD area
END CLASS

12 Class(y) Programmer's Guide


This code is used by Class(y) to create the specified class. It is actually a type of function, which is
usually called whenever instances of the class need to be created. It should be placed at the beginning
of the module (.PRG file) in which the code (methods) for the class are defined.
The first statement, CREATE CLASS, is straightforward. We are creating a class called Rectangle.
All following statements up to the END CLASS statement are Class(y) class declaration statements,
the purpose of which is to declare the structure of a class.
VAR is one such statement. It is followed by one or more instance variable names. In this example, we
are saying that the Rectangle class contains four instance variables: top, left, bottom and right.
Following the VAR statements is the EXPORT: statement. This statement causes any methods and
variables defined subsequently to be made accessible to any user of this class. By default, methods and
variables are considered hidden, and can only be accessed from the methods of that class. This is how
encapsulation is enforced in Class(y).
The METHOD command is used to declare the names of the methods defined in this class. It is
followed by one or more method names. In class Rectangle, we are declaring that five methods will be
defined: init, set, width, height and area.

Using the Class


Before we cover the actual definition of the methods, we will look at how the class might be used.
// testrect.prg

LOCAL x := Rectangle():new(5, 10, 15, 40)

? 'The dimensions of Rectangle x are:'


? ' width:', x:width
? ' height:', x:height
? ' area:', x:area
? ' top:', x:top
? ' left:', x:left
? ' bottom:', x:bottom
? ' right:', x:right

// 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

The dimensions of Rectangle x are:


Chapter 3, Using Class(y) 13
width: 31
height: 11
area: 341
Error CLASS(Y)/41 Scope violation (private): RECTANGLE:TOP
Called from obj:TOP(0)
Called from TESTRECT(10)
C:\>

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

The dimensions of Rectangle x are:


width: 31
height: 11
area: 341
top: 5
left: 10
bottom: 15
right: 40
C:\>

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

14 Class(y) Programmer's Guide


assigns the value of the parameter nTop to the instance variable top of the rectangle which received
the set() message.
Note The names of parameters and the names of the corresponding instance variables in this example have
purposely been made different. Although the statement self:top := top would work correctly -
since there is currently no ambiguity between instance variable names and ordinary Clipper variable
names - in future versions of Clipper, this may change.
Here are the method definitions for class Rectangle:
// rectangle.prg continued...

METHOD init( top, left, bottom, right ), ()


self:set( top, left, bottom, right )
RETURN self

METHOD set( nTop, nLeft, nBottom, nRight )


IF nTop <> NIL
self:top := nTop
END
IF nLeft <> NIL
self:left := nLeft
END
IF nBottom <> NIL
self:bottom := nBottom
END
IF nRight <> NIL
self:right := nRight
END
RETURN self

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 )

With the double colon, this becomes:


RestScreen( ::top, ::left, ::bottom, ::right, ::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"

CREATE CLASS ScreenRect FROM Rectangle


VAR screenBuf
VAR boxStyle
VAR color

EXPORT:
METHOD init
METHOD moveUp, moveDown
16 Class(y) Programmer's Guide
METHOD moveLeft, moveRight
METHOD hide, show
END CLASS

METHOD init( top, left, bottom, right, color, boxStyle ), ;


( top, left, bottom, right )

::boxStyle := IF( boxStyle == nil, B_DOUBLE, boxStyle )


::color := color
::show()
RETURN self

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.

Chapter 3, Using Class(y) 17


An interesting point here is that because the instance variables of class Rectangle were declared read-
only, class ScreenRect would be prevented from directly setting these variables. The only way
ScreenRect methods can affect these variables is by invoking the initializer in the Rectangle class, or
using the set method which was written for that purpose. Note that if the instance variables of class
Rectangle had been declared after a PROTECTED: command, then they could be updated from the
ScreenRect class, since it is a subclass, but could not be updated from other, unrelated classes.
The following sample program will allow us to test the ScreenRect class:
// testscrn.prg

#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

Compile this program as follows:


clipper testscrn /w/b
clipper scrnrect /n/w/b
rtlink fi testscrn, scrnrect, rectangle lib classy

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).

Class Variables & Methods


One other fairly important concept needs to be introduced: class variables and methods. Up until now,
we have talked about instance variables, and the methods we have referred to are, in a sense, instance
methods in that they deal with instances of the class and are invoked by sending messages to such
instances.
Every object, or instance of a class, has its own set of instance variables with its own unique values,
which differentiate it from other members of the same class. Similarly, every method has implicit
access to the object self, which is the specific object, or instance, that the method is operating on.
There are some situations, however, where all instances of a class need to share the same data, or where
a method needs to take action which affects the class as a whole. Class variables and class methods
exist to cater for these situations.
A class variable is a variable, defined in the class specification, which is shared among all instances
(objects) of its class. Where instance variables can have a different value in each instance of a class,
only one copy of a class variable exists for an entire class.
Similarly, a class method is a method which applies to an entire class, in that it can only be invoked by
sending a message to a class object, and its self variable will then refer to that class object. Because
of this, a class method cannot directly access (via self) the instance variables defined in its class, but
it can access its class variables.
For example, if we were to expand our ScreenRect class into a more complete windowing system, one
of the features we might want is a method that would close all windows on the screen. To implement
this, we would need a class method, perhaps called hideAll. It should be a class method since it
relates to all existing instances of the class, not just a single instance. To do its job, hideAll would
need access to all of the instances in existence at a given time. To achieve this, we could define a class
18 Class(y) Programmer's Guide
variable that contains an array of all existing ScreenRect objects. We will call this variable
activeRects.
To implement this, we might modify our class specification as follows:
CREATE CLASS ScreenRect FROM Rectangle
VAR screenBuf
VAR boxStyle
VAR color

CLASS VAR activeRects

EXPORT:
METHOD init
METHOD moveUp, moveDown
METHOD moveLeft, moveRight
METHOD hide, show

CLASS METHOD hideAll


CLASS METHOD initClass

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.

Chapter 3, Using Class(y) 19


4 Pull-Down Menu Tutorial

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.

Designing the Classes


Now, to write an object-oriented program to implement this menu system, a good first step is to decide
on the classes which will make up the system. From the specification, we can see that we are dealing
with three varieties of menu: the menu bar, the pull-down menu, and the pop-up menu. It would make
sense to have a class for each one of these menu types. However, the different types of menu have
many common characteristics. To avoid duplicating code, we need a class which has the characteristics
common to all three types of menu. We will call this class BaseMenu.
A class such as BaseMenu is often referred to as an abstract class, since you would not normally create
instances of that class. Instead, you would create instances of the three real menu classes (which we
shall call MenuBar, PullDnMenu, and PopupMenu), which will all be inherited from the BaseMenu
class. This means that all three menu varieties will have the same basic set of instance variables and
methods, derived from BaseMenu. In addition, each of the classes may implement new methods and
variables of their own.
The most important data which the BaseMenu class will contain are the details of the menu items
themselves. But what constitutes a menu item? It must include option's label text, which is displayed on
the menu, and an action associated with that option. We might also want to store the row and column at
which the label should be displayed. With all these related attributes, it is worth creating a MenuItem
class to group all of this information together.

20 Class(y) Programmer's Guide


So the BaseMenu class will contain an array of MenuItem objects. This array will be referred to by an
instance variable called items.

Figure 2 Class: BaseMenu


Pulldown
Instance items Class: MenuItem
menu Variables: currPos
Instance row
program parent
Variables: col
class Methods: new label
diagram addItem action
draw isActive
exec
Methods: new
newMenuPos
draw
setKeys
exec
clearKeys
nextCol
nextRow

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

Pull-Down Menu Class Diagram


A diagram of the class hierarchy for our pull-down menu system is shown in Figure 2 above. Please
note that this diagram does not completely conform to any standard diagramming methodology.
Nevertheless, it serves to illustrate our example, and it shows how easily an object-oriented design can
be meaningfully diagrammed.
Each class is represented by its own box. Inside the box are listed the name of the class, its instance
variables, and methods. By convention, solid arrows between the classes point from a subclass to its
superclass. The dotted arrow pointing from the instance variable items in the BaseMenu class has an
arrow pointing to the MenuItem class, indicating that the items variable in a BaseMenu object will
contain MenuItem objects.
The diagram shows that the MenuBar and PopupMenu classes have been inherited from BaseMenu, as
described earlier. However, PullDnMenu has not been inherited directly from BaseMenu. The reason
for this is that a PullDnMenu is very similar to a PopupMenu. A PullDnMenu does all of the things
which a PopupMenu does, such as drawing a window on the screen. However, according to our
specification, it has one extra behavior which PopupMenu doesn't have: it responds to the left and right
arrow keys by activating the appropriate sibling menu on the menu bar. To avoid duplicating code,
PullDnMenu has been inherited from PopupMenu. This means that PullDnMenu only needs to
implement the code necessary for its extra, unique behavior; it inherits all its other behavior from
PopupMenu in the form of methods and instance variables.

Chapter 4, Pull-Down Menu Tutorial 21


Generally speaking, in an inheritance tree (which usually has a root at the top, and branches out
downwards), the classes nearer the root are always the most general classes. As you move away from
the root, the classes should become more and more specialized, only implementing the extra features
required, without needing to reimplement behavior that is already defined further up the tree.

Explanation of the Code


When going through the code for the pull-down menu system, don't be overly concerned if it does not
make perfect sense at first. The program has been designed to illustrate many of the most important
concepts in OOP in general, using a system consisting only of interacting classes.
You may notice that the program makes use of a Window class, which is included with Class(y). We do
not cover the operation of this class in detail, since it is incidental to the overall structure of the
program. In brief, the Window class allows windows to be created, popped up and popped down,
handling screen saving and related functions transparently. Screen I/O within a window is relative to
the window's borders, so when a window is active, an @ 0, 0 SAY... command will refer to the top
left corner of that window.

Creating a Class - MenuItem


We will start with the MenuItem class, which is a stand-alone class, and hence quite straightforward.
The source code for this is contained in the file MENUITEM.PRG on the distribution disk. As
mentioned earlier, it contains instance variables for row, column, label, and action. It also has a
flag, isActive, which can be used to deactivate a particular option. Here is the class specification:
// MenuItem.prg

#include "class(y).ch"
#include "win.ch" // Class(y) sample window library header

CREATE CLASS MenuItem


VAR label
VAR action

EXPORT:
VAR row, col READONLY
VAR isActive READONLY

METHOD init
METHOD draw
MESSAGE exec TO action
METHOD nextCol
METHOD nextRow
END CLASS

METHOD init( nRow, nCol, cLabel, oAction, isActive ), ()


::row := nRow
::col := nCol
::label := cLabel
::action := oAction
::isActive := IF( isActive == NIL, .T., isActive )
RETURN self

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

22 Class(y) Programmer's Guide


Skipping through the code a bit, we can see the definition of one of the methods, draw:
METHOD draw
IF ::isActive
@ ::row, ::col PROMPT ::label
ELSE
@ ::row, ::col SAY ::label
END
RETURN self

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.

Other MenuItem Methods


Only two other methods are defined in the MenuItem class: nextrow and nextcol. They are used to
calculate the next available row or column after the current option. They are invoked by the addItem
method in the PopupMenu and MenuBar classes respectively.
There remains one unexplained declaration in the MenuItem class specification:
MESSAGE exec TO action

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

Chapter 4, Pull-Down Menu Tutorial 23


undocumented capability exists, allowing an eval message to be sent to a code block. Class(y)
supports both the exec and eval messages).
So with Class(y), there is more than one way to evaluate a code block:
LOCAL bSquare := { |x| x * x }
LOCAL n := 17 // value for testing

? eval(bSquare, n) // the old way


? bSquare:eval( n ) // the undocumented Clipper way
? bSquare:exec( n ) // the Class(y) way
? { |x| x + 2 }:exec( n ) // this also works!

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.

The BaseMenu Class


Much of the functionality of the menu system is encapsulated in the BaseMenu class (code in
BASEMENU.PRG). However, different specific features are required by each of its subclasses. To
support this, two messages are declared in the BaseMenu class, without corresponding methods, as
follows:
MESSAGE setKeys IS DEFERRED
MESSAGE clearKeys IS DEFERRED

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.

24 Class(y) Programmer's Guide


The exec method in BaseMenu causes the menu to be displayed on the screen, using the draw method,
and executes a MENU TO command to allow the user to choose an option. If an option is selected, the
exec message is sent to that MenuItem object, causing the appropriate action to take place.
The newMenuPos method is invoked by the various classes on their parent menu to establish where
on the screen to draw themselves.
// BaseMenu.prg

#include "class(y).ch"

CREATE CLASS BaseMenu


PROTECTED:
VAR items
VAR currPos
VAR parent

EXPORT:
METHOD init
METHOD addItem
METHOD draw
METHOD exec
METHOD newMenuPos

// declaring the following two methods as deferred


// will force subclasses to override them.
MESSAGE setKeys IS DEFERRED
MESSAGE clearKeys IS DEFERRED
END CLASS

METHOD init( aItems ), ()


LOCAL i

::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

FOR i := 1 TO LEN( ::items )


::items[i]:draw()
NEXT i
RETURN self

METHOD addItem( nRow, nCol, cLabel, oAction, lActive )


AADD( ::items, ;
MenuItem():new(nRow, nCol, cLabel, oAction, lActive ))
RETURN self

METHOD exec( oParent )


LOCAL finished := .f.

::parent := oParent

WHILE !finished
::draw()

::setKeys()
MENU TO ::currPos
::clearKeys()

finished := ( ::currPos == 0 )

IF !finished
::items[::currPos]:exec( self )
END
END
RETURN self

Chapter 4, Pull-Down Menu Tutorial 25


METHOD newMenuPos
RETURN ::currPos

// eof basemenu.prg

The MenuBar Class


This class inherits all the behavior of BaseMenu. It adds no instance variables of its own. It adds a
replacement draw method, which highlights the bar across the top of the screen and then invokes the
draw method in BaseMenu. This is done with the following statement:
::super:draw()

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

CREATE CLASS MenuBar FROM BaseMenu


EXPORT:
METHOD draw
METHOD addItem
METHOD newMenuPos

// override parent's DEFERRED messages, but this class


// doesn't need to do anything with them, so map to NULL.
MESSAGE setKeys IS NULL
MESSAGE clearKeys IS NULL
END CLASS

METHOD addItem( cLabel, oAction, isActive )


LOCAL nCol

// establish screen column for new option


IF len(::items) == 0
nCol := OPTION_SPACING
ELSE
nCol := ATAIL( ::items ):nextCol() + OPTION_SPACING
END

// invoke addItem in the superclass (BaseMenu)


::super:addItem( 0, nCol, cLabel, oAction, isActive )
RETURN self

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

26 Class(y) Programmer's Guide


Overriding an Inherited Method
For inheritance to be an effective mechanism for code reuse, we need a way of modifying behavior that
has been inherited from a superclass, without modifying the superclass itself. This can be done by
overriding methods that have been defined in a superclass.
The newMenuPos method in MenuBar completely overrides the method of the same name in
BaseMenu. Remember that newMenuPos is invoked by a child menu and is used to determine where
that menu should draw itself. When a MenuBar is the parent, the child menu must be drawn underneath
the corresponding option on the bar, so newMenuPos returns the relevant column position. The
corresponding method in BaseMenu is used without change by the other two subclasses.
The only other method in MenuBar is addItem, also a reimplementation of a BaseMenu method. This
addItem takes only three parameters: the label for the option, the action object and the
isActive flag. It decides where to put the new item, using the nextCol method in MenuItem. It
then invokes addItem in the superclass with the additional row and column parameters. So in this case,
although we have overridden the addItem method defined in the superclass, the overriding method
still makes use of it; in this way, we add functionality rather than replacing it.
Calculating the row and column like this means that the user of the MenuBar class does not have to
worry about it. MenuBar's addItem method becomes a simplified shell or interface to BaseMenu's
addItem method.

The PopupMenu Class


This class is similar in structure to MenuBar, since it is also inherited directly from BaseMenu. Like
MenuBar, it refines the draw and addItem methods. In this case the refinements have to do with
drawing a box, or window, around the menu. It adds two instance variables for this purpose, window
to refer to the window object and width which is used in its addItem method to determine the
window's required width. In addition, it has to redefine the exec method, so that it can remove the
window after execution is complete.
Two other methods are defined: menuTop and menuLeft. These are needed to make the window
drawing behavior more general, since PullDnMenu is inherited from PopupMenu and it has slightly
different window drawing requirements. In PopupMenu, these methods return coordinates based on the
parent menu's position, assuming that the parent menu is a PullDnMenu or another PopupMenu.

Chapter 4, Pull-Down Menu Tutorial 27


// PopupMenu.PRG

#include "class(y).ch"
#include "win.ch"

CREATE CLASS PopupMenu FROM BaseMenu


PROTECTED:
VAR window
VAR width

METHOD menuTop, menuLeft

EXPORT:
METHOD init
METHOD draw
METHOD addItem
METHOD exec

// override parent's DEFERRED messages, but this class


// doesn't need to do anything with them, so map to NULL.
MESSAGE setKeys IS NULL
MESSAGE clearKeys IS NULL
END CLASS

/*
init()

The ::width variable must be initialized before superclass'


initializer is invoked, so we aren't use the extended
METHOD syntax below. Instead, the superclass' initializer
is invoked explicitly with the statement ::super:init(...).
*/

METHOD init( aItems )


::width := 0
::super:init( aItems )
RETURN self

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

METHOD addItem( cLabel, oAction, isActive )


LOCAL nRow

// establish screen row for new option


IF LEN( ::items ) == 0
nRow := 0
ELSE
nRow := ATAIL( ::items ):nextRow
END

::super:addItem( nRow, 0, cLabel, oAction, isActive )


::width := MAX( ::width, LEN( cLabel ) )
RETURN self

METHOD exec( oParent )


// invoke the exec method in the superclass (BaseMenu)
::super:exec( oParent )
::window:kill()
::window := NIL
RETURN self

// 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"

CREATE CLASS PullDnMenu FROM PopupMenu


PROTECTED:
METHOD menuTop, menuLeft

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

The Menu Demonstration


The demonstration program, MENUDEMO.PRG, is a normal procedural program which uses the menu
objects to display a menu. In practice, you would probably want to make a routine like this data-driven,
Chapter 4, Pull-Down Menu Tutorial 29
so that the details of the menu layout are read in from a configuration file. However, the purpose of this
demonstration program is merely to show how the menu objects can be created and used.
Pull-down menu systems tend to be nested structures by their very nature, and the code in
MENUDEMO reflects this. Using the methods in the classes we have written, there are various ways in
which a menu could be defined. On the one hand, an empty menu could be created, after which items
could be added to it one by one. This approach is demonstrated in the comment near the end of the
program. At the other extreme, you can nest your calls to the menu constructor as deep as you like, for
example:
oMenuBar:addItem(" File ", ;
PullDnMenu():new( { ;
{ " Load ", { || LoadFile() } }, ;
{ " File ", { || FileOpt() } }, ;
{ " Save ", ;
PopupMenu():new( { ;
{ "Document ", { || SaveDoc() } ,;
{ " Text ", { || SaveText() }},;
}) ) )

...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.

30 Class(y) Programmer's Guide


5 Inheriting From TBrowse
In this chapter we will look at an example which involves inheriting from Clipper's TBrowse class. The
programs discussed in this chapter can be found on the distribution disk in the files DBROWSE.PRG
and DBROWDEM.PRG.
Executing the batch file MAKEALL.BAT in Class(y)'s SOURCE\SAMPLE directory will compile and
link all of the sample programs, including the browse demonstration, DBROWDEM.EXE. It will have
debug information included, and is interesting to trace through in the Clipper debugger.
The pull-down menu example in the previous chapter is highly object-oriented, consisting of five
interacting object classes. This provides an interesting view of how such a system can be structured.
But for various reasons, not all of us are in a position to begin writing completely object-oriented
Clipper systems right now - although many Class(y) users are successfully doing so.
This chapter discusses a more immediately practicable example of using user-defined classes in a
Clipper program. We are going to inherit a new class from the built-in Clipper class, TBrowse,
resulting in a class which is more complete, easier to use, and without sacrificing flexibility.
This example is not intended to be an "ultimate" browsing class. Instead, it is based on a piece of code
which will be familiar to many people: the Clipper sample program TBDEMO.PRG, which has been
reworked as a new class, called dBrowse, inherited from TBrowse.
TBDEMO.PRG can be found in the subdirectory SOURCE\SAMPLE in the directory in which Clipper
is installed. This text refers to the version supplied with Clipper 5.01a. The sample program was based
on the version supplied with the original Clipper 5.0, so there are some discrepancies in the way things
are implemented in DBROWSE.PRG, when compared to newer versions of TBDEMO.PRG.
TBDEMO uses the TBrowse class to implement a fairly simple database browse, with editing. The
majority of the code in TBDEMO is devoted to setting up and using a TBrowse object. Essentially,
TBDEMO specializes the behaviour of TBrowse, by providing a specifically database oriented
browsing capability.
It is not a coincidence that this exact kind of specialization is a major characteristic of any inherited
class in a well designed object-oriented system.
What most of the code in TBDEMO tells us, quite plainly, is that it should be implemented as a
subclass of TBrowse. It makes little sense to revert to procedural programming, just when we could
most use object orientation to manage the complexity of a general class such as TBrowse.

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.

Chapter 5, Inheriting From TBrowse 31


In the original TBDEMO, the cargo variable in the TBrowse class is used to store the state of the
append mode. To retain readability and maintainability, the preprocessor was used to hide the
underlying instance variable name, as follows (taken with comments from TBDEMO.PRG):
// These #defines use the browse's "cargo" slot to hold the
// "append mode" flag for the browse. The #defines make it
// easy to change this later (e.g. if you need to keep
// several items in the cargo slot).
#define TURN_ON_APPEND_MODE(b) (b:cargo := .T.)
#define TURN_OFF_APPEND_MODE(b) (b:cargo := .F.)
#define IS_APPEND_MODE(b) (b:cargo)

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.

Overall Program Structure


The original TBDEMO code consists of ten functions. We will examine some of the key functions:
TBDemo(), MyBrowse(), Skipper(), and DoGet(). We will examine how these functions were modified
to become part of our dBrowse class.
Before we do that, though, there is one change which was made repeatedly throughout the file which
we should examine. In the original TBDEMO, there are over forty occurrences of a message send to
browse, which is the variable containing the TBrowse object. This variable gets passed around as a
parameter among the ten functions.
But with a class-based structure for TBDEMO, the TBrowse object is no longer a foreign entity, since
TBrowse is dBrowse's superclass. This means that all TBrowse methods needed in dBrowse can be
invoked by sending a message to self. So all occurrences of browse: in the dBrowse methods were
replaced with the double colon (::), the Class(y) shorthand for sending a message to self.
The very fact that so many messages were being sent to the TBrowse object in TBDEMO was another
strong hint that those functions should have been methods in the class or a subclass, such as dBrowse.

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.

32 Class(y) Programmer's Guide


• Finally, an event handling loop is entered which stabilizes the browse while waiting for a
keystroke. When a key is pressed, a CASE statement in the function ApplyKey() is used to
execute the appropriate action. For dBrowse, the event handling loop has been made into a
method called exec, since it is when this method is called that the browse is actually activated.

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.

Where Did the Code Blocks Go?


Two methods in the dBrowse class have not yet been mentioned, since they do not have direct
equivalents in TBDEMO. These are goTop and goBottom. They are replacements for methods of the
same name in the TBrowse class. This has been done more as an interesting exercise than because it is
necessary. In the TBrowse class, these methods operate by evaluating the corresponding code blocks
contained in the instance variables goTopBlock and goBottomBlock. This technique was needed
to enable TBrowse to browse data from different sources, where the code to move to the top or bottom
of the data would vary with the data source. Here again, as with the use of the cargo instance variable
described earlier, a workaround has been used to compensate for Clipper's lack of an inheritance
capability - in this case by using instance variables containing user-supplied code blocks. Using
inheritance, these blocks become unnecessary, and the goTop and goBottom methods were
reimplemented without using code blocks.
Unfortunately, the same approach could not be taken with TBrowse's skipBlock instance variable,
since there is no single corresponding method which evaluates it. Rather, skipBlock is evaluated
directly in a number of places inside TBrowse. This forces us to use a level of indirection, using the
skipBlock code block merely to invoke our skipper method.

How to Use the Class


The demonstration program, DBROWDEM.PRG, shows how a browse can be created and activated in
only three lines. This could easily be done in one line, but the advantage of the former approach is that
we can manipulate the dBrowse object in other ways, before starting the event loop using the exec
message. After all, dBrowse is a subclass of TBrowse, and as such has all of TBrowse's methods
available to it. For example, instead of invoking the autoFields method, we could easily write a
loop using the addColumn() message to create calculated fields, or to obtain data from multiple
related DBFs.

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.

Chapter 5, Inheriting From TBrowse 33


First of all, dBrowse actually goes too far, too quickly. In one step from TBrowse, we have a class that
implements an event loop, does field editing, and is tied to a database source. However, the event loop
is quite a general piece of code, which we could use in, say, an array browsing class. The obvious
solution here is to have an intermediate class, which we will call GenBrowse, as illustrated in the
following class hierarchy diagram:
Figure 3 TBrowse
Browsing
class
hierarchy GenBrowse
General purpose browsing class
which implements a default event
handling loop in the exec() method

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.

34 Class(y) Programmer's Guide


A more logical place to implement an edit method is in the TBColumn class (or a subclass), since
that is where the contents of individual cells is decided.
So let's inherit two new classes from TBColumn - call them ROColumn and RWColumn - and
implement an edit method in each. ROColumn (read-only column) would have a dummy, or null,
edit method which would do nothing, or perhaps just generate a beep. RWColumn would have a fully
functional edit method, similar to the doGet method currently in the dBrowse class (which would
now become redundant). In the generic event loop in the GenBrowse class, we could specify that
hitting the ENTER key, say, would cause an edit message to be sent to the currently selected column
object, which could be either an ROColumn or an RWColumn object. This would look something like
the following:
CASE nKey == K_ENTER
columnObj:edit

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.

Chapter 5, Inheriting From TBrowse 35


6 Class Declaration Commands

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.

See Also MESSAGE...IS, Class Messages

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.

See Also MESSAGE...[IS...] TO, Class Messages, Message Forwarding

CLASS MESSAGE...METHOD
Synopsis Declare class message and corresponding method.

Syntax CLASS MESSAGE <messageName> METHOD <methodName>

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.

36 Class(y) Programmer's Guide


See Also MESSAGE...METHOD, CLASS METHOD, Class Messages, External MethodsCLASS
METHOD
Synopsis Declare class method(s)

Syntax CLASS METHOD <method name list>

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.

See the section on Class Methods for more information.

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:

CLASS METHOD initClass

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)

Syntax CLASS VAR <variable name list>


[CLASS <class> | TYPE <type>]
[READONLY | RO | ASSIGN <assign>]
[SHARED]

Arguments <variable name list> is a list of variable names to be included as class variables in the current class.

Chapter 6, Class Declaration Commands 37


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.

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.

See Also VAR, 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.

See Also VAR...IS, Class Variables

38 Class(y) Programmer's Guide


CLASS VAR...[IS...] TO
Synopsis Forward class variable access and assign messages

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.

See Also VAR...[IS...] TO, Class Variables, Message Forwarding

CREATE CLASS
Synopsis Begin a class specification

Syntax CREATE CLASS <class name>


[INHERIT <superclass name>]
[METACLASS <metaclass name>]
[STATIC][FUNCTION <function name>]

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"

CREATE CLASS Box


VAR top, left, bottom, right
EXPORT:
METHOD init
METHOD draw
END CLASS

METHOD init( nTop, nLeft, nBottom, nRight )


::top := nTop
::left := nLeft
::bottom := nBottom
::right := nRight
RETURN self
METHOD draw()
DispBox(::top, ::left, ::bottom, ::right)
RETURN self

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

Syntax END CLASS

40 Class(y) Programmer's Guide


Description This command ends a class specification. This command can only be used if a CREATE CLASS
command has already been used to begin 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.

See Also CREATE CLASS, METHOD (declaration), METHOD (definition)

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.

See Also HIDDEN:, PROTECTED:

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.

See Also EXPORT:, PROTECTED:

MESSAGE...IS
Synopsis Define alternative name for inherited message

Chapter 6, Class Declaration Commands 41


Syntax MESSAGE <message name> IS <original name>

Arguments <message name> is the name of the message being declared.

<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:

MESSAGE <name> IS [DEFERRED | NULL]

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:

CREATE CLASS Boat FROM Vehicle


// ...
MESSAGE beam IS width

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.

See Also VAR...IS, Deferred Methods, Null Methods

MESSAGE...[IS...] IN
Synopsis Specify alternative class for multiply inherited message

Syntax MESSAGE <message name> [IS <original name>]


IN <ancestor class>

Arguments <message name> is the name of the message being declared.

42 Class(y) Programmer's Guide


<original name> is the name of a message defined in an ancestor class which the declared message
will be mapped to. This argument should be omitted if its value is the same as <message name>.

<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:

CREATE CLASS BorderedTextWindow ;


FROM TextWindow, BorderedWindow
// ...

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:

MESSAGE show IN BorderedWindow

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:

MESSAGE show IN BorderedWindow // as above


MESSAGE showText IS show IN TextWindow

This provides a convenient way to access both methods. Of course, we could have achieved something
similar the other way around:

MESSAGE showBorder IS show IN BorderedWindow

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.

See Also Multiple Inheritance

Chapter 6, Class Declaration Commands 43


MESSAGE...[IS...] TO
Synopsis Forward message to variable

Syntax MESSAGE <message name> [IS <target name>]


TO <target variable>

Arguments <message name> is the name of the message being declared.

<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.

Here is another example which makes use of the IS clause:

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

44 Class(y) Programmer's Guide


Based on these declarations, objects of class Window will respond to the messages hideCursor,
showCursor and updateCursor by sending the message hide, show, or update respectively to
the object referred to by the instance variable oCursor.

See Also VAR...[IS...] TO, Message Forwarding, Delegation

MESSAGE...METHOD
Synopsis Declare message and corresponding method

Syntax MESSAGE <message name> METHOD <method name>


[CONSTRUCTOR | CTOR]

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:

MESSAGE left METHOD boxLeft

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:

MESSAGE display METHOD boxDisplay

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:

MESSAGE getValue METHOD get

Chapter 6, Class Declaration Commands 45


METHOD get

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)

Syntax METHOD <method name list> [CONSTRUCTOR | CTOR]

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).

See Also MESSAGE...METHOD, METHOD (definition), External Methods, Constructor Messages

46 Class(y) Programmer's Guide


PROTECTED:
Synopsis Cause subsequent messages to be protected

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.

See Also HIDDEN:, EXPORT:

VAR
Synopsis Declare instance variable(s)

Syntax VAR <variable name list>


[CLASS <class> | TYPE <type>]
[READONLY | RO | ASSIGN <assign>]

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.

Chapter 6, Class Declaration Commands 47


The VAR command is usually used inside a class specification (CREATE CLASS...END CLASS
block).

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

CREATE CLASS Dialog


VAR id
VAR nTop, nLeft TYPE Numeric
VAR oWin CLASS Window
PROTECTED:
VAR nCount READONLY
EXPORT:
VAR cTitle TYPE Char READONLY
//...
END CLASS

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.

See Also CLASS VAR, Length of Variable Names

VAR...IS
Synopsis Define alternative name for inherited variable

Syntax VAR <variable name> IS <original name>

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.

48 Class(y) Programmer's Guide


Example Assume that a Vehicle class defines a variable called 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

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:

MESSAGE beam IS width

However, neither of these solutions handles assignment via the beam message. To support assignment
statements of the form:

oBoat:beam := x

we can use the VAR...IS command in the class specification as follows:

CREATE CLASS Boat FROM Vehicle


// ...
VAR beam IS width

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.

See Also MESSAGE...IS

VAR...[IS...] IN
Synopsis Specify alternative class for multiply inherited variable

Syntax VAR <variable name> [IS <original name>]


IN <ancestor class>

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:

CREATE CLASS BorderedTextWindow ;


FROM TextWindow, BorderedWindow
// ...

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:

VAR x IN BorderedWindow // as above


VAR textX IS x IN TextWindow

This provides a convenient way to access both methods. Of course, we could have achieved something
similar the other way around:

MESSAGE bordX IS x IN BorderedWindow

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.

See Also MESSAGE...[IS...] IN, Multiple Inheritance

VAR...[IS...] TO
Synopsis Forward variable's messages (assign and access)

Syntax VAR <variable name> [IS <target name>]


TO <target variable>

50 Class(y) Programmer's Guide


Arguments <variable name> is the name of the 'variable' being declared. In fact, this command does not declare a
variable but rather allows access to a variable in another object.

<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.

LOCAL oGraph := Graph():new(...)


oGraph:nGraphTop := 5
? oGraph:nGraphTop

See Also MESSAGE...[IS...] TO, Message Forwarding

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.

See Also EXPORT:

Chapter 6, Class Declaration Commands 51


7 Method Definition Commands, Etc.

METHOD (definition)
Synopsis Begin definition of a method (after a class specification)

Syntax METHOD [FUNCTION|PROCEDURE] <method name>


[ ( [<parameter list>] ) ]

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.

See Also METHOD (declaration), Initializer Methods

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

Syntax ::<message>[ ( [<parameter list>] ) ]

Arguments <message> is the name of the message to be sent 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.

See Also self

@:
Synopsis Invoke method in same class without using a message send

Syntax @:<method name>[ ( [<parameter list>] ) ]

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.

Chapter 7, Method Definition Commands 53


Warning This feature should be used with great caution, since it prevents methods invoked in this fashion from
being overridden in a subclass. This is sometimes desirable, however.

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.

54 Class(y) Programmer's Guide


8 Usage Reference

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

56 Class(y) Programmer's Guide


was used to access the class variable currSelected. In a class method, we would instead use
::currSelected to access the variable.
If a regular method is accessing class variables and methods repeatedly, it is worth exploring whether
that aspect of its operation can be extracted and made into a class method. It is more efficient to access
class variables from within a class method. Also, it often makes better sense from a conceptual design
perspective to encapsulate, within a class method, code which repeatedly uses class messages.

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.

Uses of Class Objects

Creating New Objects


New objects are usually created by sending the new() message to a class object. For example, to
create a new instance of a Window class, we use code such as the following:
obj := Window():new( 5, 10, 15, 70 )

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.

Chapter 8, Usage Reference 57


For a more detailed discussion of object creation, see Instantiation.

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.

Checking an Object's Class


A variation on the technique described above is to check whether an object belongs to a particular
class. This can be done as follows:
IF obj:class == Window()

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

This will work in all cases.


The reason for this behavior is that such comparisons of objects are actually comparing references to
objects (pointers). Asking whether one such reference is greater than or less than another is
meaningless, in the Clipper environment. The inexact comparison operator is similarly meaningless.

Constructors and Initializers

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.

58 Class(y) Programmer's Guide


It is possible to define constructors other than new(). This can be useful when there is more than one
way to initialize the objects of a class. For more information, see the section Alternate Constructors.

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.

Instantiation and Performance


Creating new objects (instantiation) in Class(y) is a relatively fast operation. However, other factors,
such as class creation, can make it seem slower than it really is.
When the first object in a system is created, Class(y) initializes itself by creating various system classes
and performing other internal operations. You can force Class(y) to initialize itself before any objects
are created, by including a call to a function called Class(). This will initialize Class(y).
Whenever any class is used for the first time, that class must first be created. Each class is created only
once during the execution of an application. To force a class to be created before any objects of that
class are created, the class function for that class should be called. For example, if an application
includes a class called InvItem, the class can be created by calling the InvItem() function during
initialization of the application.
Creating a class in this way will also have the side effect of initializing Class(y), eliminating the need
for an explicit call to Class().
Other factors which may affect performance are discussed under Performance.

Chapter 8, Usage Reference 59


Debugging

Enhanced Object Inspector


Class(y) 2.0 provides an enhanced object inspector which replaces the debugger's default object
inspector, providing a greatly enhanced view of your objects.
To use it, link the library CSYINSP.LIB into your application (note that the entire library can be
overlaid). Since no functions in the library are called directly, it is best to link the entire library into
your application, eg:
RTLINK FI MYAPP, CSYINSP.LIB LIB CLASSY

Then enter the debugger and examine your objects as normal.


Three columns are displayed. The second and third columns display the name and value, respectively,
of each of the object's variables. The first column shows the class in which that variable was defined.
All variables, including variables inherited from superclasses, are displayed.
Values can be edited in the same way as values in Clipper's default object inspector. Pressing ENTER
on the value field will begin editing.
You can find out more about a variable or message, such as its scope, by selecting and pressing ENTER
on a message in the second column. This has the effect of displaying the internal Class(y) object which
refers to that message or variable. The instance variable scopeDesc specifies the scope of the item.
Other data, such as class or type restrictions on instance variables, are also displayed.
Class objects can also be examined by pressing ENTER in the first column.
If you don't link an application with CSYINSP.LIB, the standard Clipper/Class(y) object inspector,
described in the following section, is used.

Standard Object Inspector


If the Class(y) 2.0 Enhanced Object Inspector, described in the previous section, is not linked into an
application, a slightly enhanced version of the standard Clipper object inspector is used.
To support the expanded capabilities Class(y) provides over Clipper, three enhancements have been
made to the way in which objects appear in the debugger's object inspector.

Viewing class variables


An instance variable called class appears (in lower case) at the end of the normal list of instance
variables. Selecting this instance variable will display a list of the class variables for that object's class.
To see an example of this, try inspecting the OBOX variable in the example program HERITEST.

Viewing superclass variables


If the object being viewed belongs to a class which has a superclass, an instance variable called super
will appear in lower case at the end of the instance variable list (as with class variables above).
Selecting this entry will display the superclass' instance variables as though it were a separate object.

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.

60 Class(y) Programmer's Guide


Error Reporting
A function called METHODNAME() is provided. It is similar in behavior to, and can be used instead of,
Clipper's PROCNAME(). It is intended to provide more informative error traces. The following old-style
error trace was obtained with MENUDEMO.EXE:
Called from: obj:EXEC(71)
Called from: obj:EXEC(73)
Called from: obj:EXEC(53)
Called from: obj:EXEC(72)
Called from: obj:EXEC(73)
Called from: obj:EXEC(53)
Called from: obj:EXEC(72)
Called from: MAIN(49)

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)) + ;

Merely change the PROCNAME(i) call to METHODNAME(i), as follows:


? "Called from", Trim(MethodName(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>[(*)]

<recvClass> Class of the object receiving the message


<implClass> Class in which the method is implemented. This will be omitted if it is the same as the
receiver's class. When present, it will always be an ancestor class of the receiver.
(↑) an up-arrow is included if the method concerned is actually implemented in a class
higher up the inheritance tree than the one displayed. This can occur when the super
message, or an explicit class name message, is used to override the normal behavior of
a message search.
(*) an asterisk appears after the message name if the message could not be found in the
class. This means one of two things:
• The message name was not the same as the method name; i.e., it was declared
with the MESSAGE...METHOD command.
• The message does not exist.

The Debugger's Command Window


When using the Clipper debugger, it is often useful to use the command window to evaluate
expressions. Unfortunately, the expression evaluator in the debugger relies on the Clipper macro
subsystem to evaluate expressions. For this reason, there are serious restrictions on the complexity of
expressions that can be evaluated. Here are some specific restrictions that apply in the debugger's
command window, along with workarounds for them:

Chapter 8, Usage Reference 61


• Understandably, preprocessor translation cannot be handled. The Class(y) double-colon (::)
operator for message sends to self thus cannot be used. Instead, when debugging inside a
method, the self variable should be used directly, as in expressions such as ? self:nTop.
• Only one message send can be handled in an expression. Expressions of the form
obj:msg1:msg2 will not evaluate. One workaround for this is to use intermediate variables to
store results, for example:
? tmp := obj:var1
? tmp:var2

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 and Null Methods

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.

Inheriting from Clipper's Classes


Clipper's classes can be used as though they were Class(y) classes. This means they can be inherited
from, for example:
CREATE CLASS BetterBrowse FROM TBrowse
.
.

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)

62 Class(y) Programmer's Guide


This allows greater consistency. Either or both syntaxes can be used in the same program.

Inheriting from TBROWSE


Chapter 4 of the manual, Inheriting from TBrowse, discusses an example of inheritance from
Clipper's TBrowse class.

Inheriting From GET


Inheriting from Clipper's GET class presents some special problems for Class(y). When a GET object
receives or loses focus, its internal structure is rearranged by Clipper. This rearrangement does not take
subclasses into account, since Clipper 5.01 is not designed to support inheritance. As a result, variables
defined in subclasses of GET would be deleted or otherwise corrupted, if Class(y) did not take special
action to prevent this.
Class(y) uses the cargo variable in each GET object to hold subclass instance variables. The cargo
variable thus cannot be used for other purposes, unless it is redefined.
If a GET subclass or its client application wishes to make use of cargo, the variable should be
redefined in the first-level subclass of GET, as in the following example:
CREATE CLASS MemoGet FROM Get
VAR cargo
// ...

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.

Length of Variable Names


Due to the way assignment to instance (and class) variables is implemented in Clipper, there is a
limitation on the length of such variable names. By treating instance and class variables as though their
names are only significant to nine characters, no problems will occur.
Technical The reason for the nine-character limitation on instance variable names is that there are two messages
Note associated with each member variable - the name of the variable as specified in the class specification,
and the same name with an underscore prepended. The Clipper compiler converts assignments to
member variables into a message send using the modified instance variable name. Due to the standard
ten character width of the Clipper symbol table, a variable with a ten character name effectively has the
last character truncated for assignment purposes. This means that it is not possible to have two
variables in a class distinguished only by the tenth character of their name.

Chapter 8, Usage Reference 63


Linking

Sample Link Scripts


Sample link script statements for use with Class(y) are provided below. To link Class(y) into an
application, include these statements in the application’s link script. Ensure that the reference to
CLASSY.LIB appears before any explicit references to CLIPPER.LIB.

RTLINK and ExoSpace


Include the following statements in an .RTLink or ExoSpace link script.
#...
FI C:\CLASSY2\LIB\CSYINSP.LIB # optional: for enhanced object inspector
FI C:\CLASSY2\CYERRSYS # optional: for more detailed error trace
LIB CLASSY
#...

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.

Link Tips and Traps


You should have no problems linking Class(y) if you use statements equivalent to those listed above.
However, link scripts for large applications can become rather complex, which can lead to problems.
Some of the most common points to watch out for are listed below.
• The sequence in which CLASSY.LIB is linked can be important. Ensure that CLASSY.LIB is
specified before CLIPPER.LIB in the link script, if CLIPPER.LIB is explicitly specified.
• If products based on the Class(y) runtime version are used, the reference to CLASSY.LIB should
appear before any references to the library containing the runtime version of Class(y). However,
the exact link sequence may depend on the combination of libraries being used. If you are using a
product which uses the Class(y) runtime, check that product’s manual for any special linking
instructions.
• In some situations, Class(y) can be incompletely linked into an application, resulting in erratic
behavior, ranging from an “incorrectly linked” error message to VM errors, crashing and rebooting.
This can happen in either of the following circumstances:
64 Class(y) Programmer's Guide
• When linking a small test program which does not define any classes.
• When all the classes defined in an application are contained in a library, i.e. the stand-alone
object files being linked do not contain any classes.
In these cases, you can ensure correct linkage by including the statement EXTERNAL CLASS at
the beginning of your main program. This forces Class(y) to be fully linked in.
• When using Blinker, we advise against using the SEARCH command with CLASSY.LIB, i.e.
SEARCH CLASSY. We recommend using LIB CLASSY instead. If you do use SEARCH, you
may encounter unresolved externals such as TBROWSENEW and GETNEW. To force these
externals to be resolved, use a statement such as EXTERNAL TBROWSENEW somewhere in
your program. This should not be necessary if you specify LIB CLASSY.
• Class(y) replaces the SEND module in CLIPPER.LIB. Do not include a statement in your link
script forcing the SEND module to be linked in. For example, using Blinker, the command
MODULE SEND in your link script will lead to erratic behavior. If you suspect that an executable
is being linked incorrectly, you may wish to check the MAP file for any occurences of the string
“SEND_TEXT”. If you do find this string, it means that the Clipper SEND module has somehow
been linked in, and you will need to modify your link script to prevent this.
• When overlaying Class(y) with Blinker, you must ensure that two Class(y) kernel modules,
_CYCLASS and CSYXSEND, are not overlaid. See the instructions in the previous section, after
the sample Blinker link script statements.

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

This eliminates the need to define a method.


Both of the above cases are examples of message forwarding. The latter example, using the
MESSAGE...TO command, is merely a more automatic and efficient version of the former. As this
example shows, message forwarding can and should be used when all a method is doing is forwarding a
message on to a component object. Automatic message forwarding is simple to implement, it saves
code, executes faster, and can clarify the relationship between a class and its component objects. Rather
than having to examine the methods of a class to determine its relationship to its components, the class
specification clearly identifies these relationships, improving readability and allowing easier
maintenance.
See the MESSAGE...[IS...] TO command for more information and examples of defining forwarded
messages.
Message forwarding is also used when implementing delegation. See Delegation under Advanced
Topics for more information.

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

These predefined and standard messages are discussed below.

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.

Chapter 8, Usage Reference 67


9 Advanced Topics

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.

Delegation vs. Inheritance


There are certain similarities between delegation and inheritance, and in fact inheritance can be
implemented using delegation. However, delegation can be more flexible than inheritance, in that an
object being delegated to can be replaced by some other object at runtime. The replacement object
could be another object of the same class as the original delegatee, or it could even be an object of a
different class. Using inheritance, equivalent operations are either not possible or practical.
Changing the target of delegation in this way occurs at runtime and affects individual instances, making
it possible for different instances of the same class to behave very differently. It is this that makes
delegation potentially more powerful than inheritance in the right circumstances.

Full Polymorphism Using Delegation


Message forwarding alone is not as powerful as inheritance. The reason is that no polymorphism exists
between the delegator and the delegatee. If a delegator defines a method that is also defined in a

68 Class(y) Programmer's Guide


delegatee, methods of the delegatee will not be able to invoke the method defined in the delegator by
sending a message to self. This is in contrast to the situation with class inheritance, in which a
method defined in a subclass can be invoked by methods defined in ancestor classes, by sending a
message to self.
To achieve full polymorphism between a delegator and delegatee in Class(y), methods in the class of
the delegatee should avoid the use of self in most cases. Instead, they should send messages to
SENDER(). This means that the delegator must support all messages that the delegatee will send,
although it can do so via delegation if appropriate.
A typical method in a delegatee class would then look something like this:
METHOD foo( p1, p2 )
LOCAL sender := SENDER()
sender:bar( p1 )
sender:x += p2
RETURN sender

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.

Chapter 9, Advanced Topics 69


The Object class defines some methods which are absolutely essential to the correct functioning of the
object system. Among these are the error handling methods.
When a Class(y) error occurs, a message is sent to the object which caused the error. For example, if
the code obj:cloze is executed, and the message cloze is not found in the class of obj, then a
msgNotFound() message will be sent to obj.
A default msgNotFound method is defined in the Object class. In most cases, this method will be
executed, and the standard Clipper error system will be invoked with the appropriate message.
However, in some cases it may be desirable in a particular class to handle errors in some way other
than the default. Merely overriding the error-handling method concerned will achieve this.
For example, the Symbol class has been implemented using this feature. The msgNotFound method
was overriden, so that the Symbol class will understand any message sent to it, and respond by
returning a symbol object.
This feature could also be used to create classes which ignore invalid messages, rather than generating
an error.

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.

72 Class(y) Programmer's Guide


Class/ Object class
Metaclass
Relationships name "Object"
superClass NIL
messages { "init",
"class",
"classH",
"className",
... }

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.

Sample Rectangle object Window object


Instances
top 5 top 7
left 5 left 4
bottom 10 bottom 18
right 34 right 25
buf "..."

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:

Chapter 9, Advanced Topics 73


CREATE CLASS <className> ;
FROM <parentClass1>, <parentClass2> [, ...]

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>

74 Class(y) Programmer's Guide


to access the non-default message. For example, assuming a method show() was defined in
TextWindow and BorderedWindow, and we wish to invoke the show() method from
BorderedWindow, we would use:
::BorderedWindow:show()

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

If this kind of contention-handling code is needed repeatedly in a class, it is possible that


composition rather than inheritance should be used to implement that class.
• A more extreme technique would be to use scope resolution messages throughout the code of the
original class. This is unlikely to ever be a good solution, but is mentioned for completeness. For
example, in BorderedWindow:
METHOD move
::BorderedWindow:show()
...

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.

Scalars and new()


Do not use the new() method in conjunction with scalar classes. Scalar classes allow ordinary Clipper
data values to respond to messages. These values should still be created by ordinary means. For
example, use “x := 5” instead of “x := CsyNumeric():new( 5 )”. The latter will create a completely
useless empty object.

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.

Chapter 9, Advanced Topics 77


System Classes
Class(y) defines a number of system classes which are used internally. Most of these classes will not be
linked in to an application unless they are used. For example, if you do not use delegation, the classes
relating to delegated messages will not be linked in. Aside from a few core classes such as Class and
Object, you only get what you use.
The names of the system classes are considered reserved words. See the Reserved Words section for
further information.
The system classes are:
Object Default base class
Class Default metaclass
MIClass Default metaclass for multiply inherited classes
ScalarClass Default metaclass for scalar classes
ScalarObject Default base class for scalar classes
Symbol Symbol-handling class
cmsVariable Describes instance variables
cmsMethod Describes methods
cmsDeleg Describes delegated messages
cmsVarDeleg Describes delegated variable messages
cmsRedef Describes renamed messages
cmsVarRedef Describes renamed variables
cmsShrClsVar Describes shared class variables
In addition, there are the scalar classes: Array, Block, Character, Date, Integer, Logical, Nil,
and Numeric. See the Scalar Classes section for more information.

78 Class(y) Programmer's Guide


Index
:\:....................................................................................................................................... 16, 53
@: ........................................................................................................................................... 53
<> ........................................................................................................................................... 58
== ........................................................................................................................................... 58
abstract class ..................................................................................................................... 20, 24
alternate constructors .............................................................................................................. 68
altNew() ............................................................................................................................. 68
assignment .............................................................................................................................. 63
BASEMENU.PRG ................................................................................................................. 24
cargo ........................................................................................................................... 31, 32, 63
class .............................................................................................................. 8, 9, 10, 12, 55, 56
class (predefined message) .......................................................................... see messages, class
class browser ...................................................................................................................... 9, 10
Class class............................................................................................................. 70, 71, 72, 73
class creation .............................................................................................................. 12, 22, 39
class declaration commands ............................................................................................. 13, 36
class function ................................................................................................. 13, 23, 55, 56, 59
class hierarchy ................................................................................................ 10, 11, 21, 29, 34
class initialization ................................................................................................................... 57
class message .......................................................................................................................... 55
class method ........................................................................................................ 18, 19, 55, 56
class object .............................................................................................. 13, 15, 18, 55, 56, 58
class specification ............................................................................... 12, 14, 15, 18, 19, 22, 39
class variable ........................................................................................................ 18, 19, 55, 56
Class() ............................................................................................................................... 59
CLASS(Y).CH........................................................................................................................ 12
classH ................................................................................................................................. 67
className .......................................................................................................................... 67
CLASSY.LIB ................................................................................................................... 12, 64
code block ...................................................................................................... 23, 24, 30, 33, 34
commands
CLASS MESSAGE...[IS...] TO ...................................................................................... 36
CLASS MESSAGE...IS .................................................................................................. 36
CLASS MESSAGE...METHOD ..................................................................................... 36
CLASS METHOD ............................................................................................. 19, 37, 56
CLASS VAR ................................................................................................ 19, 37, 55, 56
CLASS VAR...[IS...] TO................................................................................................. 39
CLASS VAR...IS............................................................................................................. 38
CREATE CLASS ................................................................................................ 13, 39, 55
END CLASS ............................................................................................................. 13, 40
EXPORT: ............................................................................................................ 13, 14, 41
EXPORT\: ...................................................................................................................... 19
HIDDEN: ........................................................................................................................ 41
MESSAGE ..................................................................................................................... 24
MESSAGE...[IS...] IN ..................................................................................................... 42
MESSAGE...[IS...] TO ........................................................................................ 44, 65, 68
MESSAGE...IS ................................................................................................................ 41
MESSAGE...METHOD .................................................................................................. 45
METHOD............................................................................................................ 13, 15, 17
METHOD (declaration) .................................................................................................. 46
METHOD (definition)..................................................................................................... 52
METHOD PROCEDURE ...................................................................................... 15, 52
PROTECTED:................................................................................................................. 47
PROTECTED\:.............................................................................................................. 18
READONLY ............................................................................................................. 14, 38
VAR .................................................................................................................... 13, 14, 47
VAR...[IS...] IN ............................................................................................................... 49
VAR...[IS...] TO .............................................................................................................. 50
VAR...IS .......................................................................................................................... 48
VISIBLE: ........................................................................................................................ 51
Index 1
comparing classes ................................................................................................................... 58
comparing objects................................................................................................................... 58
compiling ................................................................................................................................ 18
constructor ................................................................................................................ 13, 30, 58
alternate ........................................................................................................................... 68
CSYINSP.LIB ........................................................................................................................ 60
CSYOBJEC.PRG ................................................................................................................... 69
CYERRSYS.OBJ ................................................................................................................... 61
Dahl, Ole-Johan ........................................................................................................................ 9
DBROWSE.PRG .................................................................................................................... 31
debugger ................................................................................................................................. 60
command window............................................................................................................ 61
DEFERRED .................................................................................................................... 24, 62
deferred method .......................................................................................................... 24, 29, 62
delegation ............................................................................................................................... 68
double colon ..................................................................................................................... 16, 53
double equal ........................................................................................................................... 58
encapsulation .......................................................................................................... 8, 11, 13, 24
error reporting ........................................................................................................................ 61
error system ............................................................................................................................ 69
ERRORSYS ........................................................................................................................... 61
eval() ................................................................................................................................. 24
exec() ............................................................................................................... 23, 24, 25, 27
external method ...................................................................................................................... 70
functions
METHODNAME() ......................................................................................................... 61
METHODNAME() ............................................................................................................ 61
PROCNAME() ................................................................................................................. 61
SENDER() ................................................................................................................ 68, 69
Get .......................................................................................................................................... 12
inheritance from .............................................................................................................. 63
graphical user interface ............................................................................................................. 9
hidden instance variable ......................................................................................................... 14
hidden messages ..................................................................................................................... 13
incremental linking ................................................................................................................. 66
inheritance ........................................................................................ 8, 9, 11, 15, 16, 17, 20, 27
from Clipper's classes ...................................................................................................... 62
from Get .......................................................................................................................... 63
inheritance tree ............................................................................................... 10, 11, 22, 29, 30
inherited variables ............................................................................................................ 17, 26
init() ................................................................................. 13, 15, 16, 17, 19, 26, 58, 59, 70
initClass()................................................................................................................ 19, 57
initializer ..................................................................................... 13, 16, 17, 19, 23, 24, 58, 59
instance .................................................................................................................................. 10
instance methods..................................................................................................................... 18
instance variable ..................................................................................... 10, 13, 14, 15, 17, 18
instantiation ................................................................................................................ 57, 59, 70
invalid state............................................................................................................................. 16
IS clause ................................................................................................................................. 24
Kay, Alan.................................................................................................................................. 9
linking ..................................................................................................................................... 60
overlaying ........................................................................................................................ 66
sequence .......................................................................................................................... 64
MENUDEMO.PRG .......................................................................................................... 29, 30
MENUITEM.PRG.................................................................................................................. 22
message ...................................................................................................... 8, 11, 12, 14, 16, 23
message chaining .................................................................................................................. 15
message forwarding .................................................................................................. 23, 65, 68
messages
class ............................................................................................................... 56, 58, 66, 67
classH .............................................................................................................................. 67
className........................................................................................................................ 67
perform ...................................................................................................................... 66, 67
2 Class(y) Programmer's Guide
super .................................................................................................................... 61, 66, 67
metaclass ................................................................................................................................ 71
method ................................................................................................................................... 11
method definitions .......................................................................................... 12, 14, 15, 19, 52
METHODNAME() ................................................................................................................ 61
msgNotFound() ................................................................................................................. 70
multiple inheritance ................................................................................................................ 73
name conflicts ......................................................................................................................... 74
new() ................................................................................................ 13, 15, 16, 55, 57, 58, 70
not equal ................................................................................................................................. 58
NULL ..................................................................................................................................... 62
null method................................................................................................................ 24, 62
Nygaard, Kristen....................................................................................................................... 9
object ...................................................................................................................................... 10
comparing ........................................................................................................................ 58
creation ................................................................................................................ 57, 59, 70
object class ................................................................................................................... see class
Object class .................................................................................. 24, 26, 66, 69, 70, 72, 76, 77
object inspector .................................................................................................................. 9, 60
object orientation ............................................................................................................ 8, 9, 10
object-oriented language........................................................................................................... 9
object-oriented programming ................................................................................................... 8
OOP .......................................................................................................................................... 8
overlaying ............................................................................................................................... 66
parent class...................................................................................................................... 11, 17
perform .................................................................................................................................. 67
performance ............................................................................................................................ 66
polymorphism ..................................................................................................................... 8, 11
predefined messages ............................................................................................................... 66
PROCNAME() ....................................................................................................................... 61
pull-down menu ...................................................................................................................... 20
read-only variables ........................................................................................................... 14, 18
reference object ...................................................................................................................... 75
repeated inheritance ............................................................................................................. 74
replacable database driver ...................................................................................................... 34
reserved messages............................................................................................................. 26, 66
reserved words ........................................................................................................................ 76
reusability ............................................................................................................................... 17
scalar classes........................................................................................................................... 76
ScalarObject class............................................................................................................. 76, 77
scope resolution message ....................................................................................................... 74
scopeDesc .......................................................................................................................... 60
self ...................................................................................................... 14, 16, 18, 19, 23, 30, 52
send operator .......................................................................................................................... 12
sender..................................................................................................................................... 68
SHARED ......................................................................................................................... 56, 57
Simula ....................................................................................................................................... 9
subclass ...................................................................................................................... 11, 24, 29
super .............................................................................................................................. 26, 67
superclass ............................................................................................................ 11, 17, 27, 30
Symbol.............................................................................................................................. 67, 70
symbol table compaction ........................................................................................................ 66
system classes ......................................................................................................................... 78
TBDEMO.PRG ...................................................................................................................... 31
TBrowse ........................................................................................................................... 12, 31
UDO ......................................................................................................................................... 8
user interface ............................................................................................................................ 9
user-defined class ................................................................................................................... 12
user-defined object ................................................................................................................... 8
variable name length ............................................................................................................... 63
Window class.......................................................................................................................... 22

Index 3

You might also like