3.9. 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 engineering—separating 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 differently—some 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'll 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.11–3.13 separates class
GradeBook's interface from its
implementation by splitting the class definition of Fig.
3.9 into two files—the 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 programmer—we'll
explain this figure in detail.
Fig. 3.11. GradeBook
class definition containing function prototypes that specify the interface of
the class.
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 <string> // 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
|
Fig. 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 <iostream>
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\n" << getCourseName()
33 << "!" << endl;
34 } // end function displayMessage
|
Fig. 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 <iostream>
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 << "\ngradeBook2 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++
|
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 9–18). 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 12–15) 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.11) 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.
Common Programming Error 3.8
|
Forgetting the
semicolon at the end of a function prototype is a syntax
error. |
Good Programming Practice
3.5
|
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
GradeBook.cpp (Fig. 3.12) defines class
GradeBook's member functions, which were
declared in lines 12–15 of Fig. 3.11. The
member-function definitions appear in lines 11–34 and are nearly identical to
the member-function definitions in lines 15–38 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
(Fig. 3.11), 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 GradeBook—the 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 function—the 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
-
the first line of each member
function (lines 11, 17, 23 and 29) matches its prototype in the
GradeBook.h file—for example, the compiler ensures that
getCourseName accepts no parameters and returns a string, and
that
-
each member function knows about the
class's data members and other member functions—for 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).
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.
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 together—that is, the member-function calls
in the client code need to be tied to the implementations of the class's member
functions—a 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
client code that uses the class. 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.]
A class-implementation programmer
responsible for creating a reusable GradeBook class creates the header
file GradeBook.h and the source-code file GradeBook.cpp that
#includes the header file, then compiles the source-code file to create
GradeBook's object code. To hide class GradeBook's member-function implementation details, 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.cpp, 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. We provide
links to various C++ compilers in our C++ Resource Center at www.deitel.com/cplusplus/.