Chapter 8. Class-Specific Data and Behavior

Introduction to Class-Specific Versus Object-Specific Data and Behavior

When teaching an object-oriented design course, I often ask the question, “Is an object required when you wish to send a message?” Invariably, the response is “Of course, messages are sent to objects.” Since the constructor of a class is one of its messages, how does one build the first object? It is clear that the constructor is a different sort of message. It does not require an object before it can be sent. (In the case of C++, the constructor is considered a normal message sent to an object that the standard allocator has created. For the purpose of this discussion, I consider my use of the term “constructor” to include standard allocation and initialization).

Another example of this phenomenon can be found in the design of an Invoice class. What pieces of data does each invoice object require? Some common data members of invoices include the billing address, the return address, line items, the total value, etc. Each invoice object must get its own copy of this data. In addition, each invoice has the behaviors of adding items, deleting items, and printing. These three operations require a target invoice object in order to execute. These data and behaviors are considered object-specific, since each object gets a unique copy of the data and only objects have access to the behaviors.

There is another piece of data that each invoice object must possess: a unique invoice number. Again, each invoice object must have its own copy. Who decides the value of this invoice number? There must be some counter that keeps track of the last invoice number. When an invoice object is constructed, this counter is incremented and the invoice number of the object is assigned the new value. Who owns this counter? Certainly not the invoice object, since there is only one invoice counter for all of the objects. The obvious choice is the Invoice class. In fact, whenever there is bookkeeping to be performed on the objects of the class, then that class should store the necessary data. In addition, we may want to know what the next invoice number is going to be. This should not be an object-specific behavior, since there may be no invoice objects at the time we wish to examine the next invoice number. It would be inconvenient, at best, to build an invoice object just to ask it the value of the next invoice number. Of course, there is also the problem that the action of getting that number will waste an invoice. The obvious entity to ask is the Invoice class. It knows the value of the next invoice number whether or not any invoice objects currently exist in the system.

The invoice counter is an example of class-specific data. A piece of class-specific data is often called a class variable. Class variables are used to store information concerning the objects, not an individual object, of the class. Whenever a developer is using global data within the methods of a class, he or she should determine if a class variable would be more appropriate.

The constructor and the GetNextInvoiceNum-type methods of the Invoice class are examples of class-specific behaviors. A class-specific behavior is often called a class method. A class method is used whenever behavior accesses only class variables within a class. If no object-specific data is being accessed, a class designer should ask himself if the operation really belongs to the class itself, as opposed to the objects of the class.

Heuristic 8.1

  • Do not use global data or functions to perform bookkeeping information on the objects of a class. Class variables or methods should be used instead.

Using Metaclasses to Capture Class-Specific Data and Behavior

Languages use two main methods to implement the notion of class-specific versus object-specific data and functionality. The first and, in my opinion, the cleanest implementation is to keep these different types of data and behavior completely separate. In pure object-oriented languages (e.g., SmallTalk), everything is considered an object. Even a class is an object. If classes are objects, then what is the class for those objects? A class of a class is called a metaclass. The basic idea is that object-specific data and behavior should be placed in the class definition, while the class-specific data and behavior should be placed in the class's associated metaclass. This provides a clear separation of concerns. When talking about a specific object of the class, we examine the class definition. When talking about all of the objects instantiated by the class, we examine the metaclass. This separation of concerns can be best illustrated by Figure 8.1, which shows the Invoice class and its associated metaclass.

The Invoice class and its metaclass.

Figure 8.1. The Invoice class and its metaclass.

Using Language-Level Keywords to Implement Class-Versus Object-Specific Data and Behavior

In some multiparadigm languages such as C++, the implementation is to throw the class- and object-specific data and behavior in one bundle, namely, the class definition. There is no notion of a metaclass. The class-specific data and behavior are distinguished from the object-specific data and behavior via a language keyword. In the case of C++, the keyword is Static. This implementation does not offer the same lexical separation that is apparent in the metaclass solution, yet it serves the necessary purpose of implementing class-specific data and behavior (see Figure 8.2).

The Invoice class and metaclass implemented with keyword.

Figure 8.2. The Invoice class and metaclass implemented with keyword.

Metaclasses à la C++

The object-oriented designer may hear the occasional C++ programmer mutter something about metaclasses. I have stated that the C++ language does not support the notion of metaclass, so what is he or she discussing? Again, in the object-oriented community, one must be very careful with vocabulary. When C++ programmers use the term “metaclass,” they are referring to the template construct in C++. The relationship between the template construct in C++ and metaclasses can be best summarized in the following statement.

All C++ templates are metaclasses, but not all metaclasses are C++ templates.

Metaclasses are traditionally a place where class-specific data and behavior are declared/defined/stored. The notion of templates in C++ came about due to a problem often encountered in strongly typed object-oriented languages. Consider the case where a designer wants a list of dogs called x, a list of airplanes called y, and a list of meals called z. What are the differences between the classes Doglist, Airplane-list, and Meallist? Upon inspection, the three classes differ only on the type of data stored in the list. The algorithms of the three lists are exactly the same. If fact, the code is identical except for the name of the type. Unfortunately, in a strongly typed language, the different type name is enough to require a whole new class definition along with all of its methods. Many developers considered this a waste of code and looked for a better solution. A common approach was to create one list class, called ListOfAnything. They then made the Dog, Airplane, and Meal classes inherit from the class Anything so that they would be allowed in the list (see Figure 8.3). This effectively turned C++ into a weakly typed language for that portion of their application.

A generic List class.

Figure 8.3. A generic List class.

While this solution will work, not all applications can use weak type checking at this point in the application. Our original requirements wanted the object x to be a list of dogs, y to be a list of airplanes, and z to be a list of meals. In this solution, the objects x, y, and z are all lists of anything, but by convention x has only dogs in it, y only airplanes, and z only meals. The obvious danger of programming by convention means that it is possible to accidentally put a dog onto the list object y. The control tower then accidentally tries to fly the dog off of a runway. Likewise, people may order a meal at a restaurant and accidentally get an airplane or dog on their plate. The whole point of strong type checking is to catch these errors at compile time, not at runtime.

The developers unhappy with the weak type-checking solution turned to C++'s preprocessor and created elaborate schemes to use it for creating the notion of a parameterized type. They were thwarted by the sheer ugliness of a macro spanning several hundred lines as well as many preprocessor's annoying habit of limiting the macro buffer size to some value like 4K. This implied that a class, with all of its messages and method definitions, had to be described in fewer than 4,000 characters. This was certainly not a good solution either.

Stroustrup's answer to the demand for easy parameterization of classes, maintaining the strong type checking inherent in C++, was the template mechanism. Templates are a language-level facility that uses one description to describe a family of classes. The relationship between the classes being captured in the template abstraction is that they differ on some data type or types that can be provided at compile time by the developer. Our solution to the list of dogs, airplanes, and meals problem posed above is to create a template called “list of your favorite data type.” The list template is described with a formal parameter that takes the place of the data to be stored in the list (see Figure 8.4). The corresponding actual parameters are provided at compile time by the developer defining the objects x, y, and z. The template instantiates a new class whenever a new actual parameter is introduced. Since templates instantiate classes, they capture class-specific data and behaviors and therefore are technically metaclasses. However, the true notion of metaclass extends beyond the notion of templates to imply the capture of any class-specific data and behavior, not just the abstractions that templates cover.

A list template.

Figure 8.4. A list template.

A Useful Abstract Class That Is Not a Base Class?

Recall in Chapter 5 that we discussed Heuristic 5.6, which states, “All abstract classes must be base classes in order to be useful.” At that time, we observed that for all intents and purposes this was true, but we mentioned an anomalous case whose discussion we postponed. Consider a class composed of all class-specific data and methods. In our keyword implementation of class-specific data and behavior, this would amount to all static data and function members (a la C++). Such a class could be abstract, given that we would not define a constructor, yet the class would have some use in the system. This technically shows an abstract class from which no one inherits yet it is still useful. Why would anyone desire a class with no object-specific methods or data? I had never seen a need for such a class, but someone had mentioned this example in a design course long ago and I passed it along for the purists to ponder. Robert Martin, in the course of reviewing this book, provided a practical example for such a class wherein it is used to encapsulate and limit the scope for a collection of global data. This allows for better understandability of the application as well as the avoidance of namespace pollution (i.e., the cluttering of an application's global name space with duplicate names, causing link errors). I pass along his example for your review (see Figure 8.5).

Example of a useful abstract class that lacks derived classes.

Figure 8.5. Example of a useful abstract class that lacks derived classes.

Glossary

Class method

A method that is attached to a class as opposed to the objects of a class. Class methods cannot operate on object-specific data or invoke object-specific methods.

Class variable

A piece of class-specific data.

Metaclass

A class whose instances are classes.

Namespace pollution

The collision of names in the global name space of an executable file, often resulting in link errors.

Object data

The data of a class of which each object is to receive a unique copy.

Object method

A method that is attached to the objects of a class, that is, it requires an object of the associated class in order to execute.

Template

A C++ language mechanism that allows for the encapsulation of common code where the only distinction between the code modules is the name of a data or function member. Also called a generic or a parameterized class.

Heuristics Summary

Heuristic 8.1Do not use global data or functions to perform bookkeeping information on the objects of a class. Class variables or methods should be used instead.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset