Rumbaugh et al.1 succinctly summarize two ways that the object-oriented paradigm benefits software development:
Object-oriented modeling and design is a way of thinking about problems using models organized around real-world concepts. The fundamental construct is the object, which combines both data structure and behavior in a single entity (p. 1).
Software developers view problems through an object-oriented lens, matching the problem's real-world components to corresponding programming features characterized with object-oriented terminology. Object-orientation is both a way of approaching problems and of implementing their solutions, with objects serving as the fundamental construct. Today, object-oriented practitioners typically refer to the data structure and behavior as attributes and operations, respectively.
Object-orientation forms a bridge that spans the entire software development process, joining analysis, design, and programming. Unlike earlier software development processes that suffered paradigm shifts (abrupt changes in how developers represent the problem) between phases, object-orientation maintains a consistent object-based vocabulary throughout the development process.
The object-oriented paradigm encompasses three essential concepts: encapsulation, inheritance, and polymorphism. The textbook presents inheritance and polymorphism in subsequent chapters. However, encapsulation is the hallmark of objects and the ideal place to begin.
Object-oriented programming languages support encapsulation, thereby improving the ability of software to be reused, refined, tested, maintained, and extended. The full benefit of this support can only be realized if encapsulation is maximized during the design process (p. 71).
A class can describe many objects having the same attributes and operations. A common metaphor is to think of a class as a cookie-cutter and the individual objects as the cookies. The cookie cutter defines the size, shape, and decoration of each cookie. We can use the cookie cutter to stamp out as many cookies as we want, and each cookie will have the same size, shape, and decorations as the others. However, we eat the cookies, but not the cookie cutter; similarly, objects, not classes, do the work in a program. C++ programmers call attributes and operations member3 variables and functions, respectively. Like all variables, member variables have a type and a name. Similarly, member functions have a name, a parameter list (which may be empty), and (except for constructor and destructor functions) a return type. Rumbaugh et al.1 collectively refer to attributes and operations as features, a practice the text continues.
Attributes are the values saved in an object. Ideally, each attribute is a quality or characteristic inherent in the entity the class represents - some aspect whose value helps distinguish between different instances or objects of the class.
Operations represent the services an object can provide to an object-oriented client or application program. Recall that C++ is a hybrid language, meaning it supports "regular" functions, unrelated to classes, and functions that are class members. In C++, class member functions correspond to object-oriented operations. Most member functions follow the same syntax used in the preceding chapters: they have a header (the return value type, the function name, and the argument list) and a body.
Ward Cunningham (Fowler4, pp. 62-63) introduced class-responsibility-collaboration (CRC) cards in the early days of industrial object-oriented software development. CRC cards consist of physical index cards on which developers write the name of a class, its responsibilities, and the names of other classes with which it collaborates. Rearranging the cards while discussing various ways a program can use the class helps developers determine which class to make responsible for maintaining various data items and providing the services a program needs to solve a given problem. The determination is relatively simple when a program uses few classes, but it becomes more challenging as the number of classes and their collaborations increase. For example, imagine two classes: Contractor and Project. Furthermore, imagine that the application tracks the contractor's pay rate and the number of hours worked. Which is responsible for maintaining the data? It's possible to create scenarios favoring each choice, and the text returns to this problem in a subsequent chapter, presenting various implementation options.
C++, like Java, controls the visibility of class features with a set of keywords (in order of increasing visibility): private, protected, and public. Although the C++ syntax is a little different than Java, the meanings of the keywords are the same in both languages, as summarized by the following Venn diagram.
private keyword. The private features of objects instantiated from different classes are mutually inaccessible, while objects instantiated from the same class may access each other's private features unrestricted. See the Time and Fraction demonstrations later in this chapter for examples.
private keyword restricts visibility and access to variables and functions to the members of the class.protected can't be described or appreciated until we study inheritance in the next chapter.public keyword allows feature access through an object throughout a program.C++ classes differ in two important ways from Java classes. First is the obvious difference in how they use the public and private (and later the protected) keywords. The features in a Java class are individually declared as public or private. Alternatively, in a C++ program, regions within the classes are labeled as public or private. Second, Java always includes method bodies inside the class. C++ programs can define short member functions inside of a class (and doing so makes them inline functions, without the need to use the inline keyword), but programmers should only prototype larger functions in the class and write the bodies in a separate compilation unit (i.e., another .cpp file). For example:
class Time
{
private:
int hours;
int minutes;
int seconds;
public:
Time();
Time(int h, int m, int s);
Time(int s);
Time add(Time t2);
Time* add(Time* t2);
void print();
void read();
};
Although the above class is a simple example, there are several important observations we can make:
Time start; Time* end = new Time;In this context, we can also call start and end objects (which implies that an object is just a variable created from a class or structure). When we create a new object as illustrated above, we are said to have instantiated the class, so an instance is a synonym for an object.
Time temp = start;copies start, byte-by-byte, to the new object temp. This example oversimplifies object assignment, but later chapters revisit the operation, clarifying it and adding detail.
Time start; Time end; start.hours = 60; cout << start.minutes << out; end.read(); Time total = start.add(end); total.print(); |
Time* start = new Time; Time* end = new Time; start->hours = 60; cout << start->minutes << out; end->read(); Time* total = start->add(end); total->print(); |
| (a) | (b) |
static variable (covered in detail later in the chapter). Alternatively, some authors refer to all variables declared inside a class as member variables and further categorize them as either instance variables or class variables (also detailed later). I learned and used the first terminology as a professional software engineer. Furthermore, static or class variables are uncommon. Consequently, I use the first terminology throughout the text but explicitly state when I'm discussing static or class variables. The variations also apply to functions, although less commonly.