Chapter 2

The Telescript
Object-Oriented Environment

The Telescript language is object-oriented. It provides a programming world of classes and objects, encapsulation and inheritance, properties and methods. If these terms mean something to you, then chances are good that you've worked with object-oriented programming before, and you'll find much of Telescript's object orientation familiar. If you're unfamiliar with the terms, you'll need to learn the fundamentals of object-oriented programming presented here before proceeding to learn more about Telescript.

This chapter provides an overview of Telescript's particular flavor of object-oriented programming. If you're already familiar with object-oriented programming, you can skim through, taking note of special terms used in the Telescript world, and special conventions not found in other object-oriented languages such as C++ or Smalltalk. If this is your first exposure to object-oriented programming, you'll want to pay close attention to the concepts in this chapter; they lie at the heart of all Telescript programming. And if you feel you need a deeper introduction to object-oriented programming than a single chapter can provide, you may want to check the Bibliography at the end of this book for other books on object-oriented programming. <<<The Bibliography is not yet written.>>>

Objects

At the center of object-oriented programming is, appropriately enough, the object. An object is a single programmed entity that contains both data and the procedures that work with that data. For example, an object may contain dates and times for phases of the moon, solstices and equinoxes, and local high and low tides. It may also contain a procedure that looks for dates when a new moon rises on a solstice, and another procedure that looks for all dates and times when an ebb tide falls on a night with a full moon. These procedures are specifically written to work with the data provided by the object.

You can compare an object with non-object-oriented programming mechanisms used in other languages, mechanisms such as the data structure and the subroutine. A data structure can, like an object, store data in meaningful forms. And a subroutine, like an object, can work on data and then return (we hope) with meaningful results. Subroutines and data structures are, in fact, often used together in the same way that an object's procedures work with the object's data. So what's the difference? In a word, encapsulation.

An object's data (called its properties in the Telescript language) and its procedures (called its methods in Telescript) are encapsulated; that is, the object's properties can only be written or read by the object's methods, and the object's methods can work directly only with the object's own properties. The operations of one object can't directly read or write the properties of another object, which ensures that an object's data is always worked on in a meaningful way (at least if the methods are written in a meaningful way). For example, consider the tide and moon object: The dates and times of tides in the object can't be overwritten by a method that calculates daily prime interest rates in Argentina. And the tide object's methods can't be turned loose on outside data say, for example, a table containing the daily average rainfall in Kalamazoo.

Figure 2-1 : Encapsulation ensures that the methods of an object will only work with properties of the same object, not with properties of another object.

An object's properties and methods give the object unique characteristics. Its properties set its state (if you change the value of a property, you've changed the state of the object), and its methods control the object's operating characteristics. Properties and methods working together give the object a sense of entity, and make it easy to create objects that model real-world objects. That's why you can expect a "directory-assistance object" to accept names and return phone numbers, and a "tax-return" object to ask embarrassing financial questions and suggest the amount of money you owe to the government.

Communicating Between Objects

Consider a world of objects that are completely self-contained: each object has methods that work only with the object's own properties. These objects become, in effect, separate programs that may execute simultaneously but have nothing to do with each other--much like simple multitasking. Such a world has no direct benefit over a traditional programming environment. To perform useful work, objects must work together to exchange information, ask each other to do work, and return results.

Encapsulation, at first glance, seems to prevent this kind of communication. It insulates objects from each other by ensuring that the methods of one object can't work directly with the properties of a second object. It does not, however, prohibit the method of one object from executing the method of a second object. The second object's method may affect the second object's own properties, a fact that allows the first object to work indirectly with the properties of the second object. Say, for example (referring back to Figure 2-1), that a method in Object A includes an instruction to execute a method in Object B that changes one of Object B's properties. In this way, Object A has indirectly worked on a property of Object B. This form of inter-object communication--where the method of one object executes the method of a second object--is accomplished by calling an operation.

Operations

An operation is the public face of an object's method, the mechanism by which one object offers the services of one of its methods to other objects. An operation's definition (which is stored within the object) gives the operation a name and determines the number of arguments the operation requires (one or more, or none at all), and whether or not the operation returns a result. The operation is directly associated with a corresponding method that belongs to the same object; that method is hidden from any objects calling the operation.

When the method of one object calls an operation on a second object, it calls the operation by name, and it must satisfy the operation's argument requirements: it passes along the specified arguments. The operation may return a result to the method that called it, or it may return no result at all. These arguments and results allow objects to exchange information.

In the relationship just described, where one object calls an operation on a second object, the first object is called the requester because it requests an operation. The second object is called the responder because it responds to that request. Because an operation hides its method, a requester sees no farther than the operation--what arguments it takes, what result it returns. The operation is a black box into which a requester may pass arguments and out of which it may receive a result, a box that hides its inner workings.

Figure 2-2 : A method of the requester object calls an operation on the responder object.

Consider, for example, a Calendar object that keeps an appointment calendar. The object offers the operation findFreeHour. The operation requires a starting date and time and an ending date and time; it returns the date and time of a free hour within the specified interval. An object calling findFreeHour on the Calendar object need only know the name of the operation, that it must supply starting and ending times as arguments, and that the operation returns a time value specifying a free hour in the calendar. The requester doesn't need to know how the responder stores its appointment data or how the method associated with findFreeHour searches for a free hour.

Attributes

Encapsulation ensures that a method of one object can't directly read or write the property of another object. But it's a very common occurrence that a requester wants to read or change a property of a responder. To allow this, the responder must offer an operation tied to a method that reads or writes the property; the requester can then call that operation to work with the responder's property.

Because this is such a common occurrence, the Telescript language offers a simple way to do this: a feature called an attribute. An attribute of one object acts like a property that may be directly manipulated by another object. As you'll read in later chapters, a method can work with an attribute almost as if the attribute were a simple variable. The requester can assign values to an attribute or read an attribute's current value. For example, an attribute named wheels can be assigned a value with the simple expression wheels = 2. It doesn't require a special operation, and it can work across objects; one object can read or change an attribute of a second object.

The fact that an attribute acts as if it were a directly-manipulated property is an illusion of the Telescript language. An attribute is, in reality, either one operation or two operations paired together. These operations are typically linked to a property. If the attribute is a read/write attribute (that is, a method can read its value or write a new value to it), the attribute has two operations: one called the getter, the other called the setter. The getter accepts no arguments and typically returns the current value of its property. The setter accepts a single argument and typically writes that argument into its property. If the attribute is a read-only attribute (that is, a method can only read the attribute's value), it has no setter--only a getter.

Figure 2-3 : A read/write attribute has a getter and a setter; a read-only attribute has only a getter. These operations are typically associated with a single property.

An attribute's getter and setter operations are invisible to other objects. They can't be called directly; Telescript calls them automatically whenever a method assigns a value to the attribute or reads an attribute's value. This creates the illusion that an attribute is a property that may be directly manipulated by other objects.

Attributes are, in almost all cases, a property accompanied by simple read or write operations that act as the getter and setter. This is how Telescript creates a default attribute. There are rare occasions, however, when an attribute is created using a custom getter or setter that does more than simply read or write a property. A getter may, for example, perform a calculation to return a value instead of reading a property. Consider a read-only attribute named daysElapsed that, when read, checks a hardware clock and calculates how many days have gone by since a fixed date. This advanced aspect of attributes allows you to create your own custom attributes that behave in ways other than Telescript's default attributes.

The Structure of an Object

Now that you've been introduced to the constituent parts of an object, an overall view shows you how those parts fit together. Figure 2-4 shows a typical object with properties, methods, attributes, and operations. Those parts are divided into two layers: the implementation--those parts that aren't directly accessible to other objects; and the interface--those parts that other objects can ask to use.

Figure 2-4 : The components of an object are divided into its interface (the outer ring) and its implementation (the inner circle).

An object's implementation is its properties and methods. Each property stores a value; the values of all of the properties together set the object's state. Each method is a sequence of Telescript instructions that, when executed, perform a specific task for the object. An object's methods determine the behavior of the object. An object's method may read or write directly to the object's properties because encapsulation allows an object to work directly with its own properties. Other objects have no direct access to properties or methods.

An object's interface is its operations and attributes that are available to other objects. These operations and attributes are called the object's features. An operation is always associated with a method and, when called, executes that method. The operation provides a way for a requester to pass arguments to the method being executed; it also provides a way back to the requester for a result generated by the method's execution.

An attribute may be a read/write attribute, in which case it is a pair of special operations: a getter and a setter. Or it may be a read-only attribute, in which case it is a single operation: a getter. An attribute is almost always associated with one of the object's properties, in which case the getter and setter read and write that property. In rare cases, an attribute has no associated property, but is calculated instead.

Other objects may use an object's features by calling an operation or by using an attribute; the object may, in fact, use its own interface by calling its own operations or using its own attributes. The Telescript language allows an object to control use of its features through public and private features. If an object declares one of its features to be a public feature, other objects can use that feature, as can the object itself. If an object declares one of its features to be a private feature, only the object itself can use that feature--it's not available to other objects. Private operations are a useful mechanism for creating modules of code within an object. A private operation can be called at any time within any of the object's methods--a useful convenience.

(A note for C++ programmers: The C++ language uses the terms private and public in different ways than Telescript, so take care that you don't get confused. What C++ calls private is, in the Telescript world, a property. What C++ calls protected is, in Telescript, a private feature. You can take some relief in the fact that what C++ calls public is also public in Telescript.)

Object Nesting

It's an important fact that Telescript is deeply object oriented--that nothing exists on a Telescript engine except objects. Simple integers, as you'll learn in later chapters, are objects; characters are objects; strings of characters are objects; lists of strings are objects. An important implication of this fact is that an object is often made up of other objects--objects that are used as its properties. For example, a widget object may have one property that is an integer storing the number of widgets in stock, another property that is an integer storing the number of widgets on order, and a third property that is an integer storing the number of widgets sold. That widget object may be a property of a larger object that also includes similar objects used as properties--a gizmo object and doodad object, perhaps--to keep track of other items in a mail-order store. And the mail-order store object may in turn be the property of an even larger object, the multi-national-corporation object.

The largest object in this chain contains all the objects it uses as its properties. It also contains all the objects that are properties of its property objects, all the objects that are properties of the properties of its property objects, and--you get the picture--so on down to the simplest objects that have no properties of their own. The mail-order store object, for example, contains the widget, gizmo, and doodad objects, as well as the integers used as properties in each of those objects. Objects contained by another object are said to be nested in that object; the object itself, along with all objects contained within the object, are said to be in the object's closure.

Figure 2-5 : Objects nested within another object are said to be within that object's closure. All the objects in this figure are in the closure of the multi-national-corporation object--including the multi-national-corporation object itself.

It's important to note that although we talk of objects nesting one within another, and of a complex object containing other objects, every object exists as an individual entity within a Telescript engine. If one object is nested within a second object, the Telescript engine maintains the two objects separately, but keeps a reference from the containing object to the nested object so that it knows one object is nested in the other. References allow a single object to be contained by two different outside objects: each outside object has a separate reference to the contained object.

One other important facet of object nesting: nesting is transparent to other objects. An object's properties are part of its implementation and are therefore not directly available to other objects. When a requester works with a responder, the requester simply uses the object's features and lets the Telescript engine take care of connections between the responder and the other objects in its closure.

Classes

Many objects on a Telescript engine share the same structure--they may have the same methods, property types, operations, and attributes. For example, consider the mail-order object we just discussed. As properties it uses a widget object, a gizmo object, and a doodad object. These three objects are identical except for one thing--their state. That is, their operations and methods used for inventory are identical, and their attributes used to store and read inventory values are identical, but each object has a different set of values stored in its properties: the widget object stores data about widgets, the gizmo object stores data about gizmos, and so on.

The class is a mechanism that defines objects that are identical except for state. A class defines and stores a set of methods, operations, and attributes for a group of objects; it also defines the types of properties the objects use. This definition makes it easy to create objects that share the same characteristics. Each object created from a class is called an instance of the class; the process of creating an instance of a class is called instantiation. All objects in the Telescript language are instances of a class, so all objects are defined by a class--their parent class. The parent class of an object is the class that defines the object's interface and implementation.

Classes promote efficient programming; once you define object characteristics in a class, you can simply instantiate the class to get as many instances of that type of object as you want. Each object behaves in the same way but has its own unique state, so each object can be put to a different use.

To see how a class works effectively, consider the objects used to store inventory information in the mail-order store example. These objects could all be instances of a class; let's call it InventoryItem. This class defines three attributes: inStock, onOrder, and sold. Each of these attributes either gets or sets an integer representing the number of items in stock, the number of items on order, and the number of items sold, respectively.

When InventoryItem is first instantiated, it creates an inventory-item object that we use to keep track of widgets. The next instance of InventoryItem is an object we use to keep track of gizmos, and the third instance is an object we use to keep track of doodads. Although these three objects are all instantiated from the same class, they are completely independent objects. If you increase the inStock attribute of the widget object, the inStock attributes of the gizmo and doodad objects aren't affected. And if you need a new inventory-item object (to keep track of thingamabobs, for example), you can simply instantiate InventoryItem again without working to define a new object.

Classes also promote Telescript engine efficiency. A class contains all of the interface and methods necessary for an object. Whenever an object is instantiated from the class, the engine need only set up and maintain the properties for that object. It doesn't have to create a new interface and set of methods for the object.

The engine maintains a reference from each object to the object's parent class. Whenever something uses an attribute or calls an operation on an object, the engine runs the method stored with the class; that method affects the properties of the object. For example, if an object sets a new onOrder value for the doodad object, the engine executes the setter method that is a part of the parent class--InventoryItem. That setter sets the appropriate property in the doodad object, so the object behaves appropriately as an instance of its class, but affects only its own state.

Inheritance

Many of the classes that define Telescript objects share key characteristics with each other. They may have many of the same methods, operations, attributes, and property types--and vary only in additional features or in some implementation details. Inheritance is a mechanism that makes it easy for classes to share characteristics, makes it simple for programmers to create new classes based on older classes, and makes it efficient for the Telescript engine to store characteristics that are shared among classes.

Superclasses and Subclasses

Simply put, inheritance allows one class to pass its characteristics on to a second class. In an inheritance relationship, the class that passes on these characteristics is called the superclass; the class that inherits from the superclass is called the subclass.

A superclass can have one or more subclasses; each of its subclasses inherits the superclass's interface (its features) and its implementation (its methods and property types). Each subclass typically elaborates on those inherited characteristics, making a distinct class of itself by adding new operations or attributes. It can also alter one (and only one) part of the characteristics it inherits: it can completely redefine one or more of the inherited methods that underlie the inherited operations. This means that a subclass is guaranteed to have the interface--the operations and attributes--of its superclass, but its internal behavior may be completely different when those features are used because it has changed the method that's executed when a feature is used.

Subclasses can have their own subclasses, those subclasses can have their own subclasses, and so on, creating many levels of inherited characteristics. Inheritance ensures that a superclass passes all of its characteristics on to all of its subclasses. Those subclasses can in turn pass their own characteristics (which include the original superclass's characteristics) on to their subclasses, and so on down the line. Inheritance ensures that a superclass's characteristics are present in all of its subclasses, no matter how many levels of inheritance a subclass is removed from the superclass.

To see how inheritance works, consider an example class called DataRecorder. It defines an object with operations that--among other things--accept daily information and store and retrieve the information by date. DataRecorder is used as a superclass for a set of subclasses that all work with daily information: SeismicRecords, which records seismic activity each day; CensusRecords, which records changes in population of an area throughout each day; WeatherRecords, which records temperatures, wind, and rainfall throughout each day; and FinancialRecords, which records daily price data.

Each of these subclasses has all of the features of DataRecorder--operations that accept, store, and retrieve information by date--and all of the methods and property types of DataRecorder. Each subclass goes on to add new features appropriate to the kind of data it records--an operation in WeatherRecords, for example, that calculates and stores the current day's rainfall total, or an operation in CensusRecords that records the intended destination of a departing individual. Each subclass may also override the method for an inherited operation or attribute if it wants; remember that inherited methods may be overridden with new methods.

The subclasses can have subclasses themselves. Consider CensusRecords, which may be subclassed to create classes more specifically adapted to different kinds of populations: BirdRecords to record the number of a species of birds in an acre of forest, for example, or AntRecords to record the number of a caste of ants in an anthill. These sub-subclasses inherit all the characteristics of DataRecorder; they also inherit all the characteristics of their direct superclass, CensusRecords. They go on to embellish those characteristics with characteristics of their own. They may even supply new methods for inherited operations. For example, the BirdRecords class may supply a new method for the intended-destination operation of CensusRecords that can recognize directions (north, south, east, west) in addition to those normally recognized (Chicago, Alabama, Brazil, and so on).

A class tree is a useful convention for showing inheritance relationships among classes. Figure 2-6 shows a class tree that defines the relationships of the example classes we just discussed. Each box is a class; a line between two classes shows that the class above is the superclass of the class below.

Figure 2-6 : A class tree shows inheritance relationships among classes.

At the top of the class tree is the root class. This is the superclass at the top of the inheritance hierarchy; all of its characteristics are inherited by every other class in the class tree. If you look at any other class in the tree, its characteristics are all inherited by any and all classes that are connected below it. For example, the characteristics of CensusRecords are inherited by its subclasses BirdRecords and AntRecords; they're also inherited by the sub-subclasses GroundbirdRecords and FlightbirdRecords.

Throughout this book, you'll see class trees represented typographically--a representation used for convenience and efficient use of space. These typographic class trees show the same relationships as a graphical class tree, but use different conventions. A typographic class tree shows superclasses to the left and subclasses to the right, unlike a graphical tree that typically shows superclasses above and subclasses below. Each level of indentation (shown with a single bullet for each level) is a deeper level of subclass. And any classes indented below another class are all subclasses of that class. Consider, for example, the typographical class tree shown in Figure 2-7. DataRecorder is the root class; SeismicRecords, CensusRecords, WeatherRecords, and FinancialRecords are its direct subclasses. CensusRecords and FinancialRecords have their own subclasses, which are sub-subclasses of DataRecorder.

DataRecorder

* SeismicRecords
* CensusRecords
* * BirdRecords
* * * GroundbirdRecords
* * * FlightbirdRecords
* * AntRecords
* WeatherRecords
* FinancialRecords
* * StocksRecords
* * MFundsRecords
* * BondsRecords

Figure 2-7 : A typographical class tree shows subclasses indented beneath superclasses.

Instances and Members

When you create an object using a class, that object is an instance of that class--its parent class--and no other. For example, if you create an object using the AntRecords class in Figure 2-7, that object is an instance of AntRecords. It's not an instance of any of AntRecord's superclasses such as CensusRecords or DataRecorder. The object is, however, a member of both its own class and its class's superclasses. So the AntRecords object in this example is a member of DataRecorder, a member of CensusRecords, and a member of AntRecords. It is an instance only of AntRecords.

The concept of instance and member is important whenever you consider the features of an object. If you know that an object is an instance of a class, you know that the object has exactly the class's features and no more. If you know that an object is a member of a class, you know that the object has the class's features but that it may have extra features or different methods for the operations because the object may be an instance of a subclass, not an instance of the class itself.

Mix-in Classes

A class is a convenient mechanism for grouping features and then passing those features on to subclasses. In the description of inheritance you just read, every subclass has a single superclass. In practice, it's convenient for a class to inherit features from more than one superclass. To make this possible, the Telescript language provides mix-in classes.

A mix-in class (mix-in for short) is a special class that can't be instantiated; it's simply a collection of characteristics meant to be mixed into the inheritance of a subclass. Classes that aren't mix-in classes (in other words, classes as they've been described up to this point in this chapter) are called flavor classes (flavors for short). Only flavors can be instantiated to create objects. Every flavor has one--and only one--flavor superclass. A flavor class can have as many mix-in superclasses as desired, however, which provide secondary sources of inherited characteristics. Mix-ins can also have superclasses, which must be mix-ins--a mix-in can't have a flavor superclass.

When you define new classes, mix-ins allow you to create a set of common characteristics that you can add to classes that have different superclasses. They also allow you to add characteristics to some of a class's subclasses without adding those characteristics to all of the class's subclasses. To see how this works, let's go back to the class tree of the last example and add some mix-ins.

We have two example mix-in classes to use. The first, Measurement, is a mix-in that includes operations that accept measurements in English or metric units, and convert from one unit to another as necessary. These features are handy for data recorders that accept information measured in feet, centimeters, hectares, and so on. The second, Currency, is a mix-in that includes operations that accept amounts measured in monetary units--such as dollars, yen, pounds, francs, and marks--and converts amounts among them as necessary.

When we look at DataRecorder's four subclasses, two of them use measurements that might be entered in English or metric units: SeismicRecords and WeatherRecords. They are prime candidates for the Measurement mix-in. A third subclass, FinancialRecords, would benefit from the Currency mix-in, which allows it to tally and convert amounts of money.

Mix-ins and Class Trees

The class tree shown in Figure 2-8 shows the mix-ins we just described as they might be added to the tree. The mix-ins appear in dashed boxes to distinguish them from flavors, which appear in lined boxes. The mix-in Measurement has been added to the inheritance of the classes SeismicRecords and WeatherRecords; the mix-in Currency has been added to the inheritance of the class FinancialRecords.

Figure 2-8 : Mix-ins appear in a graphical class tree as dashed boxes.

Now consider how a mix-in works by looking at the FinancialRecords class. It inherits all the characteristics of its primary superclass, DataRecorder--the operations that accept information and then store and retrieve it by date. It also inherits all the characteristics of the mix-in Currency--the operations that can accept and convert values in monetary units. All of these inherited features, from both the primary superclass and the mix-in, are passed on to FinancialRecords' subclasses: StocksRecords, MFundsRecords, and BondsRecords.

Notice in the class tree that the mix-in Measurement has been added to SeismicRecords and WeatherRecords, but not to CensusRecords and FinancialRecords, which don't need the features offered by Measurement. If these features had been included as part of the DataRecorder definition, all of the subclasses would have had measurement features whether they needed them or not. By putting the features in a mix-in, those features can be applied as necessary within a class tree without affecting all subclasses.

In typographical class trees, a mix-in is represented by a class name in parentheses. It follows the name of the class to which it has been added. The class tree shown in Figure 2-9 is the typographical form of the class tree shown in Figure 2-8. Notice that the mix-ins Measurement and Currency appear in parentheses just after the classes into which they've been mixed. If you want to determine a class's inheritance, you look first at the mix-ins that follow it, and then look above it for its primary superclass. For example, WeatherRecords inherits from Measurement (the mix-in to its right) and from DataRecorder (the primary superclass above it). The class BondsRecords inherits from FinancialRecords, its flavor superclass; from Currency, the mix-in of its flavor superclass; and from DataRecorder, the flavor superclass of its flavor superclass.

DataRecorder

* SeismicRecords (Measurement)
* CensusRecords
* * BirdRecords
* * * GroundbirdRecords
* * * FlightbirdRecords
* * AntRecords
* WeatherRecords (Measurement)
* FinancialRecords (Currency)
* * StocksRecords
* * MFundsRecords
* * BondsRecords
Figure 2-9 : Mix-ins appear in parentheses in a typographical class tree.

Mix-ins From Mix-ins

Just so we can convolute the class trees a little more, note that in the Telescript language, a mix-in can inherit from one or more other mix-ins. A mix-in can't inherit from a flavor. For example, the Measurement mix-in might inherit some of its characteristics from two other mix-ins: Metric, which supplies conversions between metric units of measurement; and English, which supplies conversions between English units of measurments. You'll learn much more about the way mix-ins are created in Chapter <<<yet to be written>>>. Just keep in mind that the features of any class include those of its primary superclass and its superclasses, and of its mix-in classes and their superclasses. In the Telescript society, breeding is everything.

Sealed and Abstract Classes

Inheritance allows a full transfer of characteristics from superclass to subclass, and from subclass to objects instantiated from that subclass. There are times, however, when it's useful to limit this transfer of characteristics--to prohibit new subclasses with those characteristics or to prohibit a class from being instantiated. The Telescript language provides two mechanisms for this purpose: sealed classes and abstract classes.

Sealed Classes

A sealed class is a class that has been defined to allow no user-defined subclasses. It effectively clips a class tree at the sealed class, ensuring no future growth from that branch of the tree.

Sealed classes provide security; you know that there can be no members of a sealed class except instances of the class itself. An object that depends on other objects that are members of a sealed class can be assured that no subclasses exist whose objects may behave differently than expected. For example, if the StockRecords class of the last example were sealed, there could be no subclasses that add new operations or that redefine the way the inherited operations work. If an accounting object accepted StockRecords objects as arguments, it would know that it would not get objects instantiated from subclasses, objects that might override the behavior it expects.

It's important to note that there are many Telescript predefined classes that are sealed but also have subclasses. Predefined classes are the only case where a sealed class can be subclassed--by the designers of Telescript. This technique is used to create a node of a class tree that can't take on additional branches created by subclassing with custom classes.

Abstract Classes

An abstract class is a class that has been defined to allow no instances of itself. It's typically used strictly as a superclass, a class that defines a set of characteristics passed on to subclasses. Those characteristics are insufficient by themselves to create an instance--they need to be supplemented by characteristics added or changed in subclasses.

For example, consider the class DataRecorder used in the last class tree. It's a root class whose features accept and store data, but aren't adapted to accepting the specific kinds of data that might come in from the real world. If you instantiate DataRecorder, the DataRecorder object you get won't be useful. This class is a perfect candidate for an abstract class. By making it abstract, you ensure that no one will try to create an object from it, but you make its characteristics available to subclasses that can in turn be instantiated. The instances of the subclasses then have the characteristics of DataRecorder along with the other characteristics necessary to operate practically on real-world data.

Class Families

Some classes may be parallel in structure. Their operations and attributes may be just the same except for one or more systematic differences--one class works strictly with integers, for example, while another class works strictly with real numbers. At first glance, this situation seems perfect for inheritance. Simply define the common characteristics of these classes in a superclass, then pass those characteristics on to subclasses that tweak those characteristics to create a group of closely related parallel classes.

On closer examination, however, inheritance won't work to define a group of parallel classes. That's because a subclass can only differentiate itself from its superclass by adding new features or by supplying new methods for inherited operations. A subclass must not (and cannot) change its inherited operations, attributes, and property types: inherited operations must accept the same types of arguments and return the same type of result as their superclass equivalents, inherited attributes must get and set the same type of object as their superclass equivalents, and inherited properties must be the same type of object as their superclass equivalent.

To see how this works, consider two example classes: IntegerArray and RealArray. Both of these classes keep a two-dimensional array of values and provide operations that store and retrieve values from the array. The only difference is that IntegerArray keeps an array of integer values and defines its operations to accept and return only integer values, while RealArray keeps an array of real-number values and defines its operations to accept and return only real numbers.

The interface and implementation of both of these classes must be defined using specific types of values--integers in one case, real numbers in the other case. It's impossible to define a superclass with features common to both IntegerArray and RealArray because the operations and attributes must specify either integer arguments and results or real-number arguments and results. If the superclass defines its features using integers, then RealArray can't use those features; if it defines its features using real-number values, then IntegerArray can't use those features. IntegerArray and RealArray must define their features individually without depending on inheritance.

The Telescript language provides a mechanism to accommodate parallel classes like the two just described: the class family. Simply put, a class family is a class-producing function--something like a class template into which you plug classes to create a new class. It is not a class itself, although it seems like a class in many ways.

A class family is defined very much like a class. It has operations, attributes, methods, and properties. The main difference between a class family and a class is that a class family uses formals to hold the place of classes in its definition. Think of a formal as a class variable, a named placeholder that says "This can be any class" or "This can be any of a defined set of classes." In the array class examples we just discussed, we could create a class family named Array. The class family uses a formal named "Number" wherever an operation or attribute would specify Integer or Real. The formal could be constrained to be either an integer or a real number.

The class family can't be used as a class itself--it can only create a class. That class can then be instantiated or subclassed as you see fit. To create a class using a class family, you satisfy the family's formals with actuals. An actual is a class that replaces a formal throughout the class family definition. The result is a class that is defined using the supplied actuals. For example, the class family Array has a formal named Number. If you supply Integer as an actual to satisfy the Number formal, the class family creates an array class that handles integers. If you supply Real to the class family, the family creates an array class that handles real numbers. The new class would be named Array[Integer] or Array[Real] depending on the actual you supply. You can then instantiate the new class to create array objects.

* * *

Now that you've been introduced to class families, you've reached the end of this chapter on important Telescript concepts. The next chapter, which starts a new section on objects, takes you into the nuts-and-bolts world of fundamental Telescript syntax.

2 - The Telescript Object-Oriented Environment
Objects
Communicating Between Objects
Operations
Attributes
The Structure of an Object
Object Nesting
Classes
Inheritance
Superclasses and Subclasses
Instances and Members
Mix-in Classes
Mix-ins and Class Trees
Mix-ins From Mix-ins
Sealed and Abstract Classes
Sealed Classes
Abstract Classes
Class Families

TS Ref - 26 JUN 1996

Generated with Harlequin WebMaker