"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" (Rumbaugh et al., 1991, p. 1). Rumbaugh's first sentence underscores one of the many strengths of the object-oriented paradigm: it is both a way of looking at software problems and a way of implementing software solutions to those problems. Today, object-oriented practitioners typically call the data structure and behavior attributes and operations, respectively. The language of object-orientation forms a bridge that spans the entire software development process, joining together analysis, design, and programming (often abbreviated OOA, OOD, and OOP, respectively.)
The object-oriented paradigm or model is based on three essential concepts: encapsulation, inheritance, and polymorphism. We'll study the latter two in subsequent chapters. But encapsulation is just a synonym for an object, and Rumbaugh's second sentence provides our initial definition:
Encapsulation
"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" (Wirfs-Brock & Wilkerson, 1989, p. 71).
Encapsulation combines attributes and operations (i.e., data structure and behavior) into an entity we call an object. Furthermore, objects isolate and protect the data from the application or client program (the code that uses the object). Objects allow the application to access or use its attributes (data) through a stable, controlled public interface formed by the class's public operations (functions).Classes describe objects with 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 each cookie's size, shape, and decoration. 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 do the work in a program. Rumbaugh et al. (1991) collectively refer to attributes and operations as features. Encapsulation, the first defining characteristic of the object-oriented paradigm, combines attributes and operations into an object that controls the visibility and access to those features.
C++ programmers call attributes and operations member1 variables and functions, respectively. Like all variables, member variables have a type and a name. Similarly, member functions have a name, a parameter list (that might be empty), and (except constructor functions) a return type.
Attributes are the values saved in an object. Ideally, each attribute is a quality or characteristic inherent in something - some aspect whose value helps distinguish between different class instances. For example, a class named Person might have the attributes:
Operations represent the services an object can provide to an object-oriented program. Operations correspond to member functions in a C++ program. Recall that C++ is a hybrid language, meaning it supports "regular" functions unrelated to classes and functions that are part of a class. Fortunately, everything we have learned about functions applies to member functions: they have a header (the return value type, the function name, and the argument list) and a body. Of course, member functions also have properties related to object orientation, which we will discover in this chapter.
Classes can define four kinds of operations (or functions in C++)
Ward Cunningham introduced CRC cards (class-responsibility-collaboration) in the early days of industrial object-oriented software development. (See Fowler, 2004, pp. 62-63.) CRC cards consist of index cards on which the developers write the name of a class, its responsibilities, and the names of other classes with which it collaborates. Moving the cards around while discussing various ways a program can use a class helps the developers determine where (in which class) to place attributes and operations. By placing an attribute or operation in a class, developers make the class responsible for maintaining the data or providing the operation or service. Thinking about program organization in terms of "responsibilities" becomes increasingly helpful as the number of classes in a program and their interconnections grow.
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.
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:
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 over-simplifies object assignment, but clarification and detail must wait for later chapters.
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) |
1 You may see some variation in terminology between different authors. Historically, authors called the variables declared inside a class either a member or a static
variable (covered in detail later in the chapter). Alternatively, some authors call all variables declared inside a class 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.
Fowler, M. (2004). UML distilled: A brief guide to the standard object modeling language. Boston: Addison-Wesley.
Rumbaugh, J., Blaha, M., Premerlani, W., Eddy, F., & Lorensen, W. (1991). Object-Oriented Modeling and Design. Englewood Cliffs, NJ: Prentice Hall.
Wirfs-Brock, R.; Wilkerson, B. (1989). "Object-oriented design: a responsibility-driven approach." Conference Proceedings on Object-Oriented Programming Systems, Languages and Applications. OOPSLA 1989 Proceedings, October, 1-6.