Object lessons. (programming object-oriented versions of Turbo Pascal) (Column)
by Tom Campbell
Most people know better than to ask me for programming advice, but when they do, the refrain they hear most is not to reinvent the wheel. And if you own an object-oriented version of Turbo Pascal (version 5.5 or later), you have a lot of reinventing not to do.
In other words, you may be wasting a lot of time rewriting code that comes free with every copy of TP. The Turbo Pascal class libraries represent a large body of well-written, well-tested code designed from the ground up for general-purpose use. If you plan to write a linked list, EMS handling code, or anything that uses buffered file I/O, you owe it to yourself to learn how to use the class libraries that come with Turbo Pascal. Turbo's own documentation is not at its best when dealing with the class libraries; an otherwise good tutorial on linked lists in the OOP guide omits mention of the class library at all, and the reference documentation is hideously short of examples (the sample programs and tutorials will take you a good way there, however).
This month's column uses Turbo's collections to implement a simple but functional database in a hundred lines of code. The database uses two fields, Name and Address, and sorts by name.
Collections let you store objects of any size in a dynamically allocated list that may contain up to 16K objects. This may sound only moderately interesting until you hear the whole story. The objects stored in a collection do not have to be of the same uniform size; that is, unlike in an array or most linked list implementations, you can store different types and sizes of objects in the same list.
Collections can be sorted or unsorted; the only code you need to supply is a comparison routine. Procedures to add items, delete items, loop through the list one item at a time automatically, and even treat the list as an array are all supplied. All you have to do is let the collection know what kind of objects you want it to deal with, and library code takes care of the rest.
Think about this for a moment. Here is a canned set of routines that let you do all of the most common list-processing tasks your programs will need--and all you have to do is decide what data type you want to use and supply an occasional glue routine.
I once wrote a commercial product in C that used four different kinds of linked lists. Each kind required slightly different handling, so scattered throughout a 250,000-line program are four different sets of routines to allocate items for the list, add them to the list, deallocate the list, and so on. Finding a bug in one routine meant finding and fixing three other similar bugs in the other linked list code. It was not a pretty picture, as far as maintenance went. With objects, I would only have had to do the work once.
The first thing to do is define your data types:
PDatabase =[caret]TDatabase; TDatabase = OBJECT(TSortedCollection) FUNCTION KeyOf(Item : POINTER) : POINTER; VIRTUAL; FUNCTION Compare(Key1, Key2: POINTER) : INTEGER; VIRTUAL; END; PInfoRec =[caret]TInfoRec; TInfoRec = OBJECT(TObject) Name, Street : PString; CONSTRUCTION Init; DESTRUCTOR Done; VIRTUAL; END;
TDatabase is the name we give to an object type that descends from the sorted collection type. Sorted collections have lots of neat features, such as automatic sorting and optional duplicate key suppression. By convention, object types begin with T and pointer types with P. The KeyOf and Compare routines are virtual because you may later decide to override them (that is, provide similarly named routines for different purposes in the same program).
Objects let you embed procedures and routines in them, calling them with the same notation used for records: for example, Database[caret]. ForEach(@PrintClient). But thanks to objects, you will see little of that in the accompanying program, because the collection routines that call the glue procedures defined in COLLECT.PAS are in the library code.
This sophisticated use of objects has a soft white underbelly: Type checking is frequently impossible because untyped pointers (similar to the void pointers of C) are required for truly reusable code.
The KeyOf function tells us what field to sort on. In a real application you could take advantage of Turbo Pascal's virtual routines and allow sorting via any field in the database. It contains a single line in the example and uses typecasting, a relatively new language feature inspired by C's KeyOf := PInfoRec(Item)[caret].Name;.
Since Item is a untyped pointer, the code PInfoRec(Item) forces the runtime code to treat the parameter called Item as a PInfoRec, or pointer to an InfoRec type. The dot means that it should further be constrained to treat it as a Name field, which is a pointer to a string. The caret means that Item is a pointer to a record, not the record itself. Why not just make the Item parameter a VAR InfoRec or PInfoRec? Because the TSortedCollection type must let any data type appear in the collection, and that means using Pointer types and casting them to the appropriate type.
The TInfoRec.Init procedure is a construction, which means that it is called automatically when an object of type TInfoRec is allocated. Other than that, it's a regular, run-of-the-mill procedure.
Constructors ease maintenance greatly because one of the most common programming errors is forgetting to initialize dynamically allocated data. Constructors make it a lot harder to forget. This program is so small that it makes sense to put the data entry routine right in the constructor; more complex programs would normally put this in a different module.
The most interesting part of the program, and the toughest to cover here, is PrintAll, which lits all the clients. PrintAll uses the ForEach iterator (a term stolen from SmallTalk, like the concept of collections itself). An iterator routine steps through the collection item by item, while a routine you plug in deals with the item. One severe shortcoming is that the user-supplied code (PrintClient, in this case) must appear as a procedure nested within the ForEach procedure.
That's pretty much it--a database program in 97 lines. The price you pay for using the wealth of code in the class libraries is a few intense days with the Turbo manuals.
Learning objects isn't as easy as learning the drivers in the Borland Graphical Interface or deciding when a FOR loop is better than a WHILE, but it's high time you got started. You can't afford not to.