Separating Interface from Implementation

In the preceding section, we showed how to promote software reusability by separating a class definition from the client code (e.g., function main) that uses the class. We now introduce another fundamental principle of good software engineeringseparating interface from implementation.

Interface of a Class

Interfaces define and standardize the ways in which things such as people and systems interact with one another. For example, a radio's controls serve as an interface between the radio's users and its internal components. The controls allow users to perform a limited set of operations (such as changing the station, adjusting the volume, and choosing between AM and FM stations). Various radios may implement these operations differentlysome provide push buttons, some provide dials and some support voice commands. The interface specifies what operations a radio permits users to perform but does not specify how the operations are implemented inside the radio.

Similarly, the interface of a class describes what services a class's clients can use and how to request those services, but not how the class carries out the services. A class's interface consists of the class's public member functions (also known as the class's public services). For example, class GradeBook's interface (Fig. 3.9) contains a constructor and member functions setCourseName, getCourseName and displayMessage. GradeBook's clients (e.g., main in Fig. 3.10) use these functions to request the class's services. As you will soon see, you can specify a class's interface by writing a class definition that lists only the member function names, return types and parameter types.

Separating the Interface from the Implementation

In our prior examples, each class definition contained the complete definitions of the class's public member functions and the declarations of its private data members. However, it is better software engineering to define member functions outside the class definition, so that their implementation details can be hidden from the client code. This practice ensures that programmers do not write client code that depends on the class's implementation details. If they were to do so, the client code would be more likely to "break" if the class's implementation changed.

The program of Figs. 3.113.13 separates class GradeBook's interface from its implementation by splitting the class definition of Fig. 3.9 into two filesthe header file GradeBook.h (Fig. 3.11) in which class GradeBook is defined, and the source-code file GradeBook.cpp (Fig. 3.12) in which GradeBook's member functions are defined. By convention, member-function definitions are placed in a source-code file of the same base name (e.g., GradeBook) as the class's header file but with a .cpp filename extension. The source-code file fig03_13.cpp (Fig. 3.13) defines function main (the client code). The code and output of Fig. 3.13 are identical to that of Fig. 3.10. Figure 3.14 shows how this three-file program is compiled from the perspectives of the GradeBook class programmer and the client-code programmerwe will explain this figure in detail.


GradeBook.h: Defining a Class's Interface with Function Prototypes

Header file GradeBook.h (Fig. 3.11) contains another version of GradeBook's class definition (lines 918). This version is similar to the one in Fig. 3.9, but the function definitions in Fig. 3.9 are replaced here with function prototypes (lines 1215) that describe the class's public interface without revealing the class's member function implementations. A function prototype is a declaration of a function that tells the compiler the function's name, its return type and the types of its parameters. Note that the header file still specifies the class's private data member (line 17) as well. Again, the compiler must know the data members of the class to determine how much memory to reserve for each object of the class. Including the header file GradeBook.h in the client code (line 8 of Fig. 3.13) provides the compiler with the information it needs to ensure that the client code calls the member functions of class GradeBook correctly.

The function prototype in line 12 (Fig. 3.12) indicates that the constructor requires one string parameter. Recall that constructors do not have return types, so no return type appears in the function prototype. Member function setCourseName's function prototype (line 13) indicates that setCourseName requires a string parameter and does not return a value (i.e., its return type is void). Member function getCourseName's function prototype (line 14) indicates that the function does not require parameters and returns a string. Finally, member function displayMessage's function prototype (line 15) specifies that displayMessage does not require parameters and does not return a value. These function prototypes are the same as the corresponding function headers in Fig. 3.9, except that the parameter names (which are optional in prototypes) are not included and each function prototype must end with a semicolon.


Figure 3.11. GradeBook class definition containing function prototypes that specify the interface of the class.

(This item is displayed on page 100 in the print version)

1 // Fig. 3.11: GradeBook.h 2 // GradeBook class definition. This file presents GradeBook's public 3 // interface without revealing the implementations of GradeBook's member 4 // functions, which are defined in GradeBook.cpp. 5 #include // class GradeBook uses C++ standard string class 6 using std::string; 7 8 // GradeBook class definition 9 class GradeBook 10 { 11 public: 12 GradeBook( string ); // constructor that initializes courseName 13 void setCourseName( string ); // function that sets the course name 14 string getCourseName(); // function that gets the course name 15 void displayMessage(); // function that displays a welcome message 16 private: 17 string courseName; // course name for this GradeBook 18 }; // end class GradeBook

Common Programming Error 3.8

Forgetting the semicolon at the end of a function prototype is a syntax error.

Good Programming Practice 3.7

Although parameter names in function prototypes are optional (they are ignored by the compiler), many programmers use these names for documentation purposes.

Error-Prevention Tip 3.4

Parameter names in a function prototype (which, again, are ignored by the compiler) can be misleading if wrong or confusing names are used. For this reason, many programmers create function prototypes by copying the first line of the corresponding function definitions (when the source code for the functions is available), then appending a semicolon to the end of each prototype.

 

GradeBook.cpp: Defining Member Functions in a Separate Source-Code File

Source-code file GradeBook.cpp (Fig. 3.12) defines class GradeBook's member functions, which were declared in lines 1215 of Fig. 3.11. The member-function definitions appear in lines 1134 and are nearly identical to the member-function definitions in lines 1538 of Fig. 3.9.

Notice that each member function name in the function headers (lines 11, 17, 23 and 29) is preceded by the class name and ::, which is known as the binary scope resolution operator. This "ties" each member function to the (now separate) GradeBook class definition, which declares the class's member functions and data members. Without "GradeBook::" preceding each function name, these functions would not be recognized by the compiler as member functions of class GradeBookthe compiler would consider them "free" or "loose" functions, like main. Such functions cannot access GradeBook's private data or call the class's member functions, without specifying an object. So, the compiler would not be able to compile these functions. For example, lines 19 and 25 that access variable courseName would cause compilation errors because courseName is not declared as a local variable in each functionthe compiler would not know that courseName is already declared as a data member of class GradeBook.

Common Programming Error 3.9

When defining a class's member functions outside that class, omitting the class name and binary scope resolution operator (::) preceding the function names causes compilation errors.

To indicate that the member functions in GradeBook.cpp are part of class GradeBook, we must first include the GradeBook.h header file (line 8 of Fig. 3.12). This allows us to access the class name GradeBook in the GradeBook.cpp file. When compiling GradeBook.cpp, the compiler uses the information in GradeBook.h to ensure that


  1. the first line of each member function (lines 11, 17, 23 and 29) matches its prototype in the GradeBook.h filefor example, the compiler ensures that getCourseName accepts no parameters and returns a string.
  2. each member function knows about the class's data members and other member functionsfor example, lines 19 and 25 can access variable courseName because it is declared in GradeBook.h as a data member of class GradeBook, and lines 13 and 32 can call functions setCourseName and getCourseName, respectively, because each is declared as a member function of the class in GradeBook.h (and because these calls conform with the corresponding prototypes).

Figure 3.12. GradeBook member-function definitions represent the implementation of class GradeBook.

1 // Fig. 3.12: GradeBook.cpp 2 // GradeBook member-function definitions. This file contains 3 // implementations of the member functions prototyped in GradeBook.h. 4 #include 5 using std::cout; 6 using std::endl; 7 8 #include "GradeBook.h" // include definition of class GradeBook 9 10 // constructor initializes courseName with string supplied as argument 11 GradeBook::GradeBook( string name ) 12 { 13 setCourseName( name ); // call set function to initialize courseName 14 } // end GradeBook constructor 15 16 // function to set the course name 17 void GradeBook::setCourseName( string name ) 18 { 19 courseName = name; // store the course name in the object 20 } // end function setCourseName 21 22 // function to get the course name 23 string GradeBook::getCourseName() 24 { 25 return courseName; // return object's courseName 26 } // end function getCourseName 27 28 // display a welcome message to the GradeBook user 29 void GradeBook::displayMessage() 30 { 31 // call getCourseName to get the courseName 32 cout << "Welcome to the grade book for " << getCourseName() 33 << "!" << endl; 34 } // end function displayMessage

Testing Class GradeBook

Figure 3.13 performs the same GradeBook object manipulations as Fig. 3.10. Separating GradeBook's interface from the implementation of its member functions does not affect the way that this client code uses the class. It affects only how the program is compiled and linked, which we discuss in detail shortly.


Figure 3.13. GradeBook class demonstration after separating its interface from its implementation.

1 // Fig. 3.13: fig03_13.cpp 2 // GradeBook class demonstration after separating 3 // its interface from its implementation. 4 #include 5 using std::cout; 6 using std::endl; 7 8 #include "GradeBook.h" // include definition of class GradeBook 9 10 // function main begins program execution 11 int main() 12 { 13 // create two GradeBook objects 14 GradeBook gradeBook1( "CS101 Introduction to C++ Programming" ); 15 GradeBook gradeBook2( "CS102 Data Structures in C++" ); 16 17 // display initial value of courseName for each GradeBook 18 cout << "gradeBook1 created for course: " << gradeBook1.getCourseName() 19 << " gradeBook2 created for course: " << gradeBook2.getCourseName() 20 << endl; 21 return 0; // indicate successful termination 22 } // end main  

gradeBook1 created for course: CS101 Introduction to C++ Programming gradeBook2 created for course: CS102 Data Structures in C++  

As in Fig. 3.10, line 8 of Fig. 3.13 includes the GradeBook.h header file so that the compiler can ensure that GradeBook objects are created and manipulated correctly in the client code. Before executing this program, the source-code files in Fig. 3.12 and Fig. 3.13 must both be compiled, then linked togetherthat is, the member-function calls in the client code need to be tied to the implementations of the class's member functionsa job performed by the linker.

The Compilation and Linking Process

The diagram in Fig. 3.14 shows the compilation and linking process that results in an executable GradeBook application that can be used by instructors. Often a class's interface and implementation will be created and compiled by one programmer and used by a separate programmer who implements the class's client code. So, the diagram shows what is required by both the class-implementation programmer and the client-code programmer. The dashed lines in the diagram show the pieces required by the class-implementation programmer, the client-code programmer and the GradeBook application user, respectively. [Note: Figure 3.14 is not a UML diagram.]

Figure 3.14. Compilation and linking process that produces an executable application.

(This item is displayed on page 104 in the print version)

A class-implementation programmer responsible for creating a reusable GradeBook class creates the header file GradeBook.h and source-code file GradeBook.cpp that #includes the header file, then compiles the source-code file to create GradeBook's object code. To hide the implementation details of GradeBook's member functions, the class-implementation programmer would provide the client-code programmer with the header file GradeBook.h (which specifies the class's interface and data members) and the object code for class GradeBook which contains the machine-language instructions that represent GradeBook's member functions. The client-code programmer is not given GradeBook's source-code file, so the client remains unaware of how GradeBook's member functions are implemented.



The client code needs to know only GradeBook's interface to use the class and must be able to link its object code. Since the interface of the class is part of the class definition in the GradeBook.h header file, the client-code programmer must have access to this file and #include it in the client's source-code file. When the client code is compiled, the compiler uses the class definition in GradeBook.h to ensure that the main function creates and manipulates objects of class GradeBook correctly.

To create the executable GradeBook application to be used by instructors, the last step is to link

  1. the object code for the main function (i.e., the client code)
  2. the object code for class GradeBook's member function implementations
  3. the C++ Standard Library object code for the C++ classes (e.g., string) used by the class implementation programmer and the client-code programmer.

The linker's output is the executable GradeBook application that instructors can use to manage their students' grades.

For further information on compiling multiple-source-file programs, see your compiler's documentation or study the Dive-Into™ publications that we provide for various C++ compilers at www.deitel.com/books/cpphtp5.

Категории