4.3 Defining the Implementation


General Concepts

Implementing a class consists of defining:

  • data - the encapsulated (hidden, private) variables that record the current state of the object, and
  • code - the member functions (operations, methods) that perform actions using the data and their own input parameters.

The data is often referred to as the "state" variables or the "instance" variables of the class. The term state reflects the point of view that an object moves from one state to another in time as directed by the execution of its methods. For example, when the MoveTo method is applied to a Frame object, the object is moved from a state in which it is at one location to a state in which it is at a different location. The term instance denotes the fact that each object is an instance of the class; each instance of a given class being distinct from all other instances and possessing its own private data.

The encapsulated data in a class is accessible to the member functions of that class. Such access is allowed, of course, because the member functions and the data are parts of the same implementation: to be written, the writer of the code must know the details of the data. The data is, however, completely inaccessible to all non-member functions.

A Simple Example

The Location class illustrates the syntax and placement of the the data and the code for a class. The interface and the implementation for the Location class are shown separately in the following two figures. Together, these two figures contain the complete code for the Location class.


Interface of the Location Class
class Location { 
   private:

     int  currentX, currentY;

   public:

         Location(int x, int y);
         Location(); 
     int Xcoord(); 
     int Ycoord();
        ~Location(); 
   };


Implementation of the Location Class
   Location::Location(int x, int y) 
      { currentX = x; currentY = y; }

    Location::Location () 
      { currentX = -1; currentY = -1; }

int Location::Xcoord() 
      { return currentX; }

int Location::Ycoord() 
      { return currentY; }

    Location::~Location() {}

  

Notice that the data is placed in the private portion of the class definition. It is natural to wonder why, if the data is hidden, it is textually visible in the class definition: Why not make the data hidden both conceptually and visually? In fact, the placement of the data in the class definition is a compromise in the design of the overall system software that simplifies the job of the compiler and linker at the expense of exposing to view (but not to access) the variables declared in the private section of the class definition.

The general syntax of the implementation of a member function is:

  ReturnType  ClassName::memberFunctionName ( ArgumentList ) { Statements }

where

ReturnType
is the type of the value returned by the member function (e.g, int),
ClassName
is the name of the class of which this function is a member (e.g., Location),
memberFunctionName
is the name of the member function (e.g., Xcoord),
ArgumentList
is a list of the argument for this member function (e.g., x and y for the first constructor); the list is empty if there are no arguments.
Statements
the code that defines what the member function does when it is invoked.

The ClassName must be repeated on each member function. This is necessary because, with overloaded member functions, it is possible for different classes to have member functions with the same name and arguments; only their class names distinguish these different member functions. Notice also that the complete argument list must be given even though it may already have been given earlier in the class definition.

A More Complicated Example

The Counter class provides another example of how a class is implemented. The interface of the Counter class is shown below. The private data of the Counter class consists of several integers that define the state of the Counter.


Private Data of the Counter Class
class Counter 
{ 
  private: 
    int initial;
    int value; 
    Message* message; 

  public: 
    Counter(int start = 0); 
    void ConnectTo(Message& msg); 
    void Next(); 
    void Reset(); 
   ~Counter(); 
 }; 

The Counter constructor allows the Counter to be initialized to a starting value that by default is zero. The starting value is both the value of the Counter immediately after construction and also the initial value to which the Counter's value will be set by a Reset() operation. The Next() method increases the value of the Counter by one. In the constructor, the Message pointer is set to null. Notice that the syntax for assigning a null pointer in this case is:

        message = (Message*)0

which type casts the value of zero, normally an int type, to be of type "pointer to Message."


Implementation of the Counter Class
Counter::Counter (int start) 
{ initial = start; 
  value   = start; 
  message = (Message*)0; 
 } 

 void Counter::ConnectTo(Message& msg) 
 { message =&msg; } 

 void Counter::Next() 
 { value = value + 1; 
   char asString[10]; 
   ostrstream convert(asString, 10);
   conver << value << '\0';
   if (message) message->SetText(asString); 
 } 

 void Counter::Reset() 
 { value = initial; } 

 Counter::~Counter() {} 

Also notice that the Next() method uses the string stream to convert the integer value to a string.

Encapsulation Reconsidered

To gain a deeper understanding of the concept of encapsulation, consider an extended Counter class that allows two Counter objects to be compared. It is typical that two Counter objects need to be compared to determine if they represent the same value; that is, whether the encapsulated integer data values of the two objects are equal. With the existing Counter class interface the comparison cannot be made because there is no method that returns the Counter's value. While such an accessor method could be added to the public interface of the Counter class, it is often undesirable to expose the internal components of a class to examination from outside of the class.

The comparison method, named equal, can be defined and implemented as shown below, but note that only the relevant portions of the Counter class are shown here.


Adding the Equal Method to the Counter Class
class Counter{                         
   private:
     ...
     int  value;
     ...

   public:

         ...
     int Equal(Counter& other);  // test for equal values 
         ...
  };      
 // implementation follows
  int Counter::Equal(Counter& other)
   { if (value == other.value)
          return 1;  // equal
     else return 0;  // not equal
   }            

The parameter ("other") of the Equal method is passed by reference to avoid copying the Counter object to which the called object is being compared.

Because encapsulation is a class property, and not an object property, this implementation of the Equal method does not violate the encapsulation property. On the surface, the encapsulation appears to be violated because the value of the "other" object is accessed directly and not via a public accessor method. Specifically, the expression "other.value" directly accesses the supposedly encapsulated data of the "other" object. However, encapsulation must be understood from a class perspective: the private data defined within a class can only be accessed by methods (member functions) of that class. Since the Equal method is a member function of the Counter class it may access any of the private data members of that class in any Counter object to which it has access. This does not violate the notion of encapsulation because the benefit of encapsulation derives from the limitation on which methods, not which objects, can access the private data of a class. To underscore this idea, suppose that the name or type of the internal value in the Counter were changed. The only code that would have to be altered to accommodate this change is code in the Counter class itself. Also notice that because a method of a class can access the data of all objects of a class, the need to introduce possibly unwanted accessor methods is avoided.

Tasks

  1. Show how to define and implement an Equal method in the Location class that determines if the x and y coordinates of two Location objects are the same. Implement this method using the accessor methods Xcoord and Ycoord of the Location class.

  2. Show how to define and implement an Equal method in the Location class that determines if the x and y coordinates of two Location objects are the same. Implement this method without using the accessor methods Xcoord and Ycoord of the Location class.

  3. Show how to define and implement a LessThan method in the Counter class that determines if the value of one Counter object is strictly less than the value of another Counter object.

  4. Without looking at the software distribution, show how to implement the Shape class.

  5. Define and implement an extension to the Location class that adds a method MoveBy(int dx, int dy) that changes the coordinates of the Location object to which it is applied by the amount given by the two parameters. For example, if a Location object has the coordinates (100,100), applying the operation MoveBy(20, -10) changes the coordinates of the object to be (120, 90).


  6. Design, implement, and test a class to represent simple fractions. For example, the class might be used as follows:
      Fraction half(1,2);
    					
    

    Fraction quarter(1,4);

    Fraction sum = half.plus(quarter);

    Add other interesting and useful methods to the class.

 




©1998 Prentice-Hall, Inc.
A Simon & Schuster Company
Upper Saddle River, New Jersey 07458

Legal Statement

 

 

 

ÿ