Assigning Attributes and Behavior

Once you've identified a class, the next task is to determine what responsibilities it has. A class's responsibilities fall into two categories:

The information that an object of that class must maintain. (“What does an object of this class know?”)

The operations that an object can perform or that can be performed on it. (“What can this object do?”)

Every class has attributes, which are the properties or characteristics that describe it. For example, a Rectangle class could have height and width attributes, a GraphCursor class could have a shape (arrow, crosshairs, etc.), and a File class could have a name, access mode, and current position. Every instance of a class has a state that it must remember. An object's state consists of the current values of all its attributes. For example, a File object could have the name FOO.TXT, the access mode “read only,” and the position “twelve bytes from the beginning of the file.” Some attributes may never change value, while others may change frequently. An attribute's value can be stored as a data member, or it can be computed each time it is needed.

It is important not to confuse attributes and classes; you should not define a class to describe an attribute. A Rectangle class is useful, but Height and Width classes probably are not. Deciding whether to have a Shape class is not so easy. When a shape is used only to describe a cursor's state, it is an attribute. If a shape has attributes that can have different values, and a set of operations that can be performed on it, then it should be a class in itself. Moreover, even if a program needs a Shape class, other classes may still have shape as an attribute. The Shape objects that a program manipulates are unrelated to the shape of a GraphCursor object.

Every class also has behavior, which is how an object interacts with other objects and how its state changes during those interactions. There is a wide variety of possible behaviors for classes. For example, a Time object can display its current state without changing it. A user can push or pop elements off a Stack object, which does change its internal state. One Polygon object can intersect with another, producing a third.

When deciding what a class should know and what it can do, you must examine it in the context of the program. What role does the class play? The program as a whole has information that makes up its state, and behavior that it performs, and all of those responsibilities must be assigned to one class or another. If there is information that no class is keeping, or operations that no class is performing, a new class may be needed. It is also important that the program's work be distributed fairly evenly among classes. If one class contains most of the program, you should probably split it up. Conversely, if a class does nothing, you should probably discard it.

The act of assigning attributes and behaviors gives you a much clearer idea of what constitutes a useful class. If a class's responsibilities are hard to identify, it probably does not represent a well-defined entity in the program. Many of the candidate classes found in the first step may be discarded after this step. If certain attributes and behavior are repeated in a number of classes, they may describe a useful abstraction not previously recognized. It may be worthwhile to create a new class containing just those characteristics, for other classes to use.

One common mistake among programmers new to object-oriented programming is to design classes that are nothing more than encapsulated processes. Instead of representing types of objects, these classes represent the functions found by a procedural decomposition. These false classes can be identified during this stage of the design by their lack of attributes. Such a class doesn't store any state information; it just has behavior. If, when describing a class's responsibilities, you say something like, “This class takes an integer, squares it, and returns the result,” you have a function. Another characteristic of such classes is an interface consisting of just one member function.

Once you've identified the attributes and behavior of a class, you have some candidate member functions for the class's interface. The behavior you've identified usually implies member functions. Some attributes require member functions to query or set their state. Other attributes are only apparent in the class's behavior.

The specific member functions and their parameters and return types won't be finalized until the end of the design process. Furthermore, implementation issues play only a small role in the design process at this point. These include questions like deciding whether attributes should be stored or computed, what type of representation should be used, and how to implement the member functions.