Previous Section
 < Free Open Study > 
Next Section


2.4 Object-Oriented Programming

In Chapter 1, we said that functional design results in a hierarchy of tasks and that object-oriented design results in a hierarchy of objects. Structured programming is the implementation of a functional design, and object-oriented programming (OOP) is the implementation of an object-oriented design. However, these approaches are not entirely distinct: The implementation of an operation on an object often requires a functional design of the algorithm. In this section, we examine object-oriented programming in more depth.

Concepts

The vocabulary of object-oriented programming has its roots in the programming languages Simula and Smalltalk. It can be very bewildering. Such terms and phrases as "sending a message to," "methods," and "instance variables" are sprinkled throughout the OOP literature. Although this vocabulary can seem daunting, don't panic. There is a straightforward translation between these terms and familiar C++ constructs.

An object is a class object or class instance-that is, an instance of a class type. A method is a public member function, and an instance variable is a private data member. Sending a message means calling a public member function. In the rest of this book, we tend to mix object-oriented terms with their C++ counterparts.

Inheritance Inheritance is a mechanism whereby a hierarchy of classes is constructed such that each descendant class inherits the properties (data and operations) of its ancestor class. In the world at large, it is often possible to arrange concepts into an inheritance hierarchy-a hierarchy in which each concept inherits the properties of the concept immediately above it in the hierarchy. For example, we might classify different kinds of vehicles according to the inheritance hierarchy in Figure 2.8. Moving down the hierarchy, each kind of vehicle is both more specialized than its parent (and all of its ancestors) and more general than its children (and all of its descendants). A wheeled vehicle inherits properties common to all vehicles (it holds one or more people and carries them from place to place) but has an additional property that makes it more specialized (it has wheels). A car inherits properties common to all wheeled vehicles, but has additional, more specialized properties (four wheels, an engine, a body, and so forth). The inheritance relationship can be viewed as an is-a relationship. In this relationship, the objects become more specialized the lower in the hierarchy you go.

Click To expand
Figure 2.8: Inheritance hierarchy

Inheritance A mechanism used with a hierarchy of classes in which each descendant class inherits the properties (data and operations) of its ancestor class

Base class The class being inherited from

Object-oriented languages provide a way for creating inheritance relationships among classes. You can take an existing class (called the base class) and create a new class from it (called the derived class). The derived class inherits all the properties of its base class. In particular, the data and operations defined for the base class are now defined for the derived class. Note the is-a relationship-every object of a derived class is also an object of the base class.

Derived class The class that inherits

Polymorphism The ability to determine which of several operations with the same name is appropriate; a combination of static and dynamic binding

Overloading Giving the same name to more than one function or using the same operator symbol for more than one operation; usually associated with static binding

Binding time The time at which a name or symbol is bound to the appropriate code

Static binding The compile-time determination of which implementation of an operation is appropriate

Dynamic binding The run-time determination of which implementation of an operation is appropriate

Polymorphism Polymorphism is the ability to determine either statically or dynamically which of several methods with the same name (within the class hierarchy) should be invoked. Overloading means giving the same name to several different functions (or using the same operator symbol for different operations). You have already worked with overloaded operators in C++. The arithmetic operators are overloaded because they can be applied to integral values or floating-point values, and the compiler selects the correct operation based on the operand types. The time at which a function name or symbol is associated with code is called binding time (the name is bound to the code). With overloading, the determination of which particular implementation to use occurs statically (at compile time). Determining which implementation to use at compile time is called static binding.

Dynamic binding, on the other hand, is the ability to postpone the decision of which operation is appropriate until run time. Many programming languages support overloading; only a few, including C++, support dynamic binding. Polymorphism involves a combination of both static and dynamic binding.

Encapsulation, inheritance, and polymorphism are the three necessary constructs in an object-oriented programming language.

C++ Constructs for OOP

In an object-oriented design of a program, classes typically exhibit one of the following relationships: They are independent of one another, they are related by composition, or they are related by inheritance.

Composition Composition (or containment) is the relationship in which a class contains a data member that is an object of another class type. C++ does not need any special language notation for composition. You simply declare a data member of one class to be of another class type.

Composition (containment) A mechanism by which an internal data member of one class is defined to be an object of another class type

For example, we can define a class PersonType that has a data member birth-date of class DateType.

#include <string>
class PersonType
{
public:
  void Initialize(string, DateType);
  string NameIs() const;
  DateType BirthdateIs() const;
private:
  string name;
  DateType birthdate;
};

Deriving One Class from Another Let's use the class MoneyType introduced in a sidebar in Chapter 1 as the base class and derive a class that has a field that contains the name of the currency.

#include <string>
class MoneyType
{
public:
  void  Initialize(long, long);
  long  DollarsAre() const;
  long  CentsAre() const;
private:
  long  dollars;
  long  cents;
};

class ExtMoneyType : public MoneyType
{
public:
  string  CurrencyIs() const;
  void  Initialize(long, long, string);
private:
  string  currency;
};

ExtMoneyType  extMoney;

The colon followed by the words public and MoneyType (a class identifier) says that the new class being defined (ExtMoneyType) inherits the members of the class MoneyType. MoneyType is called the base class or superclass and ExtMoneyType is called the derived class or subclass.

extMoney has three member variables: one of its own (currency) and two that it inherits from MoneyType (dollars and cents). extMoney has five member functions: two of its own (Initialize and CurrencyIs) and three that it inherits from Money-Type (Initialize, DollarsAre, and CentsAre). Although extMoney inherits the private member variables from its base class, it does not have direct access to them. extMoney must use the public member functions of MoneyType to access its inherited member variables.

void  ExtMoneyType::Initialize
  (long newDollars, long newCents, string newCurrency)
{
  currency = newCurrency;
  MoneyType::Initialize(newDollars, newCents);
}

string ExtMoneyType::CurrencyIs() const
{
  return currency;
}

Notice that the scope resolution operator (::) appears between the type MoneyType and the member function Initialize in the definition of the member function. Because two member functions named Initialize now exist, you must indicate the class in which the one you mean is defined. (That's why it's called the scope resolution operator.) If you fail to do so explicitly, the compiler assumes you mean the most recently defined one.

The C++ rule for passing parameters is that the actual parameter and its corresponding formal parameter must be of an identical type. With inheritance, however, C++ relaxes this rule somewhat. That is, the type of the actual parameter may be an object of a derived class of the formal parameter.

Note that inheritance is a logical issue, not an implementation one. A class inherits the behavior of another class and enhances it in some way. Inheritance does not mean inheriting access to another class's private variables. Although some languages do allow access to the base class's private members, such access often defeats the concepts of encapsulation and information hiding. With C++, access to the private data members of the base class is not allowed. Neither external client code nor derived class code can directly access the private members of the base class.

Virtual Functions In the previous section we defined two member functions with the same name, Initialize. The statements

money.Initialize(20, 66);
extMoney.Initialize(20, 66, "francs");

are not ambiguous, because the compiler can determine which Initialize to use by examining the type of the object to which it is applied. Sometimes, however, the compiler cannot determine which member function is intended, so that the decision must be made at run time. If the decision is left until run time, the word virtual must precede the member function heading in the base class definition. We will have more to say about how C++ implements polymorphism when we use this mechanism later in the book.



Previous Section
 < Free Open Study > 
Next Section
Converted from CHM to HTML with chm2web Pro 2.85 (unicode)