Path: utzoo!utgpu!jarvis.csri.toronto.edu!mailrus!cwjcc!tut.cis.ohio-state.edu!pt.cs.cmu.edu!sei!ajpo!eberard From: eberard@ajpo.sei.cmu.edu (Edward Berard) Newsgroups: comp.lang.c++ Subject: Re: Objectifying incoming messages? Summary: Still More Interesting Issues Message-ID: <537@ajpo.sei.cmu.edu> Date: 5 Aug 89 23:16:05 GMT References: <444@cimshop.UUCP> <110003@gore.com> Lines: 244 In article <110003@gore.com>, jacob@gore.com (Jacob Gore) writes: > / comp.lang.c++ / eberard@ajpo.sei.cmu.edu (Edward Berard) / Aug 4, 1989 / > > Our course of action is simple. We first remove the composite > > operation (i.e., "Display") from the interface of the counter class > > and replace it with a selector operation which returns the current > > value of a counter object. We have now decoupled the counter class > > from the output object class using selector decoupling. > > This appears to imply that there is an intermediate representation for the > object's value that can be returned by the source object's selector and the > destination object's constructor. In this case, it's easy: a number object > can represent the counter's value. > > What do you do if the value is quite complex? Encode it in some > agreed-upon representation, such as in a structure or a character string? You have just touched on quite a number of interesting issues, e.g.: - The difference between primitive, simple, and composite objects and classes - The concept of parameterizing classes (thus creating metaclasses) - Designing object-oriented systems such that you minimize coupling and maximize reusability Lets start with the differences between primitive, simple, and composite classes: - primitive objects and classes are those supplied by the environment, e.g., typically by the programming language. They have two main uses: creation of user-defined objects and classes and low-level communication among user defined objects (i.e., they are the "lingua franca," the "common language understood by all entities in the system"). Although available primitive objects and classes vary from environment to environment (e.g., from programming language to programming language), common examples of primitive objects and classes are: integer, character, string, and real numbers. - simple objects and classes are user-defined objects and classes which, from an _external_ viewpoint, _conceptually_ have no discernible structure, i.e., they are monolithic entities. Although we might suspect that the _internal_ structure of a simple object or class might be quite complex, if we cannot unambiguously describe what that structure is, given only an outside view, then the object or class is a simple object or class. Examples of simple objects and classes include: a temperature sensor, a month, and a counter. - composite objects and classes are (for the most part) user-defined objects and classes which, from an _external_ viewpoint, _conceptually_ have a discernible structure. Those outside the object or class, may not know the precise underlying implementation of the composite object or class, but the _conceptual_ structure is known. There are two types of composite object and classes: heterogeneous composite object and classes and homogeneous composite objects and classes. Homogeneous composite objects and classes are _conceptually_ composed of other objects and classes which are _conceptually_ all of the same type. Examples of homogeneous composite objects and classes include: a list of names, a mailbox, and a priority queue. Heterogeneous composite objects and classes are _conceptually_ composed of other objects and classes which are _conceptually_ of potentially different types. Examples of heterogeneous composite objects and classes include: a date (composed of a month, a day, and a year), a purchase order, and (in a communications system) a message. Sometimes the primitive objects and classes provided to us include composite objects and classes. The class "string" is an example of a primitive homogeneous composite class. The above distinctions are quite useful, for example, in systematically designing resuable objects and classes, avoiding object coupling, and in overall object-oriented development. (Yes, there are guidelines, rules, techniques, and methods which involve the above.) Next, we move to the discussion of parameterizing classes. Suppose I wish to create a "date class." I know that I want this class to conceptually involve a month, a day, and a year. I have a number of options available to me: - "Hardcode" some primitive class, or primitive classes in the underlying implementation. This is not flexible. Suppose, for example, I wish to change how months are represented, or I wish to extend the range for the years. - I could "hardwire" a specific "day class," a specific "month class," and a specific "year class." This again doesn't sound terribly flexible. Suppose, however, I decided to parameterize the date class. Specifically, I designed the date class in a "generic" manner, i.e., I treated the day class, month class, and year class as abstractions. I stipulated that users of the "generic date class" would have to supply their own month class, day class, year class, and any other necessary associated operations and other appropriate items. As you might have guessed, this "generic date class" is no longer a conventional class, i.e., it is a metaclass. A metaclass is a class whose instances are themselves classes. To use the generic date class, I supply the necessary parameters, and create an instance, i.e., a specific date class. This "specific date class" can then be used to create objects, i.e., date objects. Now, let's answer Jacob's specific question with an example. Suppose I want to create a simple application which will read in, and print out, dates. I decide that I will need the following classes: - the aforementioned "generic date class" - a month class - a day class - a year class - an input object class - an output object class - for purposes of this example, a primitive class, i.e., string In the process of creating this application, I realize that I will need something to coordinate all of these classes, and their associated objects. We will call this intelligent ether "the glue" The glue is itself an object (actually, a "system of objects" -- see, e.g., my recent posting on comp.lang.smalltalk). It is the glue which knows about all of the objects and classes in the application. Now let us look at the generic date class. We find that there are six operations in its interface: - given a specific date object, and a specific month object, set the value of the month in the date object to the value of the month object - given a specific date object, return a month object containing the value of the month for the specific date - a pair of similar operations for the day - a pair of similar operations for the year Notice that the date object has no conception of what a month, a day, or a year look like. It is as if each date object was a black box with three bins, i.e., one each fro a month object, a day object, and a year object. (Yes, there are ways to create an "intelligent date class," but we don't have the space to discuss it here.) We do know that if we wish to set the value of, say, the year for a given date object, we had better show up with a black box called a year object. Further, if we ask for the value of a day for a given date, we will be handed a black box called a day object. On the other side, we have our input object and our output object, which are instances of the input object class, and the output object class respectively. We notice that each has only one operation in its interface (please, this is a simple example): - The input object has an operation called "get" in its interface which returns instances of the primitive homogeneous composite class "string." It knows nothing about dates, months, days, years, output objects, and glue. It just spews out strings. - The output object has an operation called "put" in its interface which requires input in the form of instances of the primitive class string. Like the input object, it knows about no other objects or classes, except string. So here we sit. The input and output objects only know about strings, and the date objects only know about month, day and year objects (from a strictly external viewpoint at that). We must now examine the month class, day class, and year class. Since we will most likely do the same thing to all three classes, let's pick the year class as an example: In the interface to the year class, we will find two operations: - a constructor operation we will call "from_string," whose purpose is to take a string object, and return an instance of the year class, i.e., a year object. - a selector operation we will call "to_string," whose purpose is to take a year object, and return its value as an instance of the string class. Note that the underlying implementation of a year could be anything, e.g., a string, an integer, an enumerated type, or an encrypted value. In the interest of polymorphism, we will have operations with identical names and purposes in the month class and the day class. Since I am trying to make this discussion programming-language-independent, we will now discuss how the overall application will work in general terms. Remember that it is the "glue" which contains all the application-specific information. We will start from the point where all objects have been declared.: 1. The "glue" gives a prompt, in the form of a string object, to the output object. 2. The glue waits for a string object from the input object. 3. The glue sends the sting object to the appropriated class (i.e., month, day, or year) requesting the from_string operation. Assuming all goes well, an object of the appropriate class is returned. 4. The glue sends the month, day, or year object to the date object requesting that the appropriate operation (set_month, set_day, or set_year) be accomplished. 5. Steps 1-4 are repeated until we have one, or more, date objects with values for each of its component objects. 6. The glue then requests a month, day, or year object from a given date object. 7. The glue then sends the month, day, or year object requesting the to_string operation. Assuming all goes well, an object of the class string is returned. 8. The glue then sends a prompt in the form of an instance of class string to the output object. Then, the glue sends the string object representation for the month, day, or year object to the output object. 9. Steps 6-8 are repeated until all the desired dates are printed (displayed, or whatever). Notice that knowledge of the primitive class string was only required by the input object class, the output object class, and the month, day, and year classes. In true polymorphic form, the date class dealt with other objects only as black boxes, and in a uniform manner. If we had had a "list metaclass," we could have constructed a list of dates. I have said way too much already. I have to go. Thanks for listening. -- Ed Berard (301) 353-9652