Software Design Using C++Objects and ClassesObject-Oriented ProgrammingObject-oriented programming places more emphasis on the data items (objects) than on the operations (functions) that manipulate them. The operations are closely bundled with the data. Languages such as Smalltalk
that are object-oriented have been around for years, whereas others (such as
C++ and Java) are newer. However, until recent years
most people used procedural programming, which emphasizes functions over data.
Consider our old friend the
abstract data type. It consists of some kind of data (perhaps fractions)
and operations on that data (such as adding or subtracting two fractions
and so on). If we used procedural C++ programming to implement fractions
we would probably make a fraction to be a structure with numerator and
denominator fields. Then we would have separate functions for add, subtract,
etc. With object-oriented C++ programming we can create fraction
objects, where each object contains the numerator and denominator fields as
well as the functions (add, subtract, etc.). Each fraction object is said
to belong to the fraction class. A class is essentially a user-defined
data type that implements the abstract data type (ADT). If EncapsulationAs just mentioned, a fraction object can encapsulate not just the data fields needed (such as numerator and denominator), but the functions that manipulate the data as well. Packaging may seem to be rather trivial, but good packaging makes the job of building an application program much easier by providing easy-to-use classes of objects. In addition, objects provide information hiding in that an application program using a fraction object can only access the data via the public functions; there is normally no direct access to private data (which is how the numerator and denominator fields would probably be set up). (In C++ one can make either a data field or a function to be either public or private.) InheritanceWith inheritance one can easily develop a new class that contains all of the data fields and functions of an existing class (or classes). Inheriting all of the features of a single class is called single inheritance. Multiple inheritance (where a new class inherits all of the features of two or more classes) will not be covered here.
Where might one use inheritance? For example, suppose that we have a class
named In fact, we might want to have various subclasses of vehicles, such as ones for land vehicles, boats, and planes. Then we might want to have subclasses of some of these. For example, we could have classes for cars and trucks that are subclasses of the land vehicles class. All of this can be pictured in an inheritance diagram as shown below. Note that an arrow from one class to a second means that the first class inherits from the second, that is, the first class is a subclass of the second.
PolymorphismThis refers to the fact that an operation, such as printing, can be implemented in different ways for different classes in a hierarchy of classes and subclasses. In the example mentioned above, printing for boats might be somewhat different than printing for a general vehicle, etc. The notation used would be something like this:
Note that the By the way, calling a function that belongs to a class always follows the syntax shown above. A function of a class is often called a class method. The call takes the form:
Run-time BindingThis is sometimes called late binding. In the above example the compiler can figure out ahead of time which version of the Print
function to use for each of the two calls of Print. However, it is
sometimes not possible to figure this kind of thing out until the program
is actually running. To handle this situation C++ uses virtual
member functions. The usual way that this problem arises is when we
access objects via pointers and don't know the class of object pointed
to until runtime. This can happen because pointers can sometimes be
dynamically changed to point to various types of objects. (For example,
we could have a pointer to an object of a class and change it to point to
an object of a subclass.) We will not look further at run-time binding here
as it is a fairly advanced topic.
ReusabilityCode reuse is great! There is no sense in coding again something that you already have. Well-designed classes of objects make reuse easier as objects of these classes can be incorporated into other application programs. In addition, inheritance can be used to derive new classes that are based on existing classes, again allowing us to reuse a lot of existing code. All of this can allow us to design new software faster and more easily, by building upon what we already have. A Simple Object-Oriented ExampleLet's write a program that creates a few simple objects and then prints out the contents of each. Perhaps these objects could belong to a Product class of products that our company has on hand to sell.
The first task is to design the class. Let's say that the data for a product will consist of a name, a year of manufacture, and the price. The only function that we said we needed above was a print function, but we will also need a special function, called a constructor, to create each new object of the class. The constructor function always has the same name as the class itself. Look at the following class declaration:
The three data fields are fairly obvious. Note that they are in a section
that is labelled private. This means that an application program has no
direct access to them and neither do objects of other classes. The only
way to access these private fields is via the public functions that class
The
The It is not always necessary to write your own constructor function for a class as there is an automatic constructor that simply allocates memory space for a new object of the class. If you don't need to initialize anything inside a new object, this automatic constructor may work just fine. If you do write your own constructor, note that the automatic constructor still gets called first, to allocate space for the object, and then the code for your constructor is executed.
How do we write a little test program that creates a few objects of this class and
calls the
The first three lines of this
How can one picture our little application program with its three objects?
The following drawing is one way to do this. The entire application is
drawn as a large box, with an opening for the user interface. The user
interface is where the user interacts with the program (by viewing
information that the program prints, by entering data that the program
prompts for, etc.). Inside the application box are three boxes, one for
each of the three
Now, surely we are missing something. Nowhere have we written any code for the two class functions (methods). Here it is, complete with comments:
The syntax for a function definition is the return type, the class name,
two colons, the function name, the parameter list, an optional The constructor simply copies its three parameters into the three data fields of the implicit object. The comments call it a default constructor. This means that this constructor can be used with no parameters, in our case because default values are provided for all three parameters. (Note that the default values are not listed again in the function definition.) One can have several constructors in a given class, but only one can be a default constructor. It is the one that says what to do when no parameters are given. Now, how do we put all of his together into one program? Although everything could be shoved into one file, it is common to put the class declaration (as well as constants and typedefs) into a header file, the code for the class functions into a .cpp file, and the application program (in this case consisting just of amain function) into another .cpp file.
Look at the following three files to see how this division has been done.
Adding to Our ExampleIt would be nice if our Product class provided more functionality.
Then we could do more in our application program. Well, let's add some more class
functions. It is common to use set and get functions,
often with names that start with set and get. A get function is used to get
information out of an object. A set function is used to place data into
the object. Thus the flow of data is reversed.
Since we have three data fields in any object of our class, we will write three get functions, one to get a copy of each data item. We could also write three set functions, but let's just write one to set a value into the Price field. It could be named SetPrice,
but we will name it ChangePrice. Take a look at out revised
project in the following files:
GetName function needs to return a string (a character array),
which cannot be returned in a function name. Thus we use a parameter instead.
The code for the function uses strcpy to copy the string from
the Name field into this parameter.
Note well that strcpy does not check to see if the destination
string has enough room for the data. This can open up one's software to a
buffer overflow attack, a well-known type
of security flaw. When writing professional software use a copy function that
does proper bounds checking instead. Follow the above link for further information
on this important topic.
The test program adds the code shown below in order to try out the new class
functions. Note the familiar syntax for each call: object name, dot, function
name, and parameter list. The functions that return values in the function
name are embedded in output statements as a quick way to print these numbers.
Refer to
The following drawing shows our application as the large outer box, and the three objects that it manipulates as the three inner boxes. The details are only shown for one of the three objects, but in reality all three would look the same. Note that the picture is the same as the picture for the previous program except that each object has more public functions that can be used to access the object.
A Program Using InheritanceA Software Engineering ExampleSuppose that we want a program that allows the user to interactively look up information about cars that we are selling that fit any price range that the user cares to specify. Let's again use the software development life cycle to assist us with this problem. AnalysisIn this step we need to get a clear handle on what the program should do. This is usually done by specifying the inputs, processing, and outputs. Let's think about input data first. Suppose that the car data isn't supplied as input at all, but is hard-coded into the program. This isn't very flexible, but is fine for a simple program. (If we wanted to design a better program, we could have it read the car data from a file. This would be a good exercise for the reader to try!) Suppose that we want the following data for each car: a description (make and model), the year the car was made, whether it is a 2-door or 4-door, the horsepower of the engine, and the price. The processing is simple enough. We want the user to be allowed to repeatedly look up the data on all cars that fit a price range that the user supplies. That means input: we need to input the price range, probably by having the user enter a minimum and a maximum price. The processing is a type of search, but has to produce all cars that fit the price range, not just the first car that can be found to fit the range. The output is also clear: for each car found that fits the range, print its data on the screen. DesignConsider the data that we must deal with. We have similar data about several cars. We might first think of using an array of records to hold this data. That would work, but then we remember that we have a Product
class already written that holds some of the data that we need. We could
use inheritance to derive a subclass Car that adds the extra
data and any new functions that we might need. Then we could use an array
of Car objects. Reusing existing code should allow us to
make fast progress on this problem. The only data fields that we need to
add would be ones for the horsepower and the number of doors on the car.
Now let's think about the class functions that are needed. Inheritance
will give us a function to print the data for a general
If we had a more complicated hierarchy of classes and subclasses, we might
draw an inheritance diagram at this point. In our case the diagram would
just consist of the word
At this point we might sketch out the class declaration. Let's use
a box diagram much like we used earlier. This gives a picture of a
In the drawing above we could also give return types and parameter lists for each of the functions, but that would rather clutter up the diagram. Instead, at this stage in the design, let's write out the class declaration as shown below:
Since this is our first example of how to derive one class
from another via inheritance, examine this class declaration carefully.
The first line says that the class
In the class declaration above, the added private fields
The first added function is obviously a constructor since it has the same
name as the class name
The first line says that when the Next, we turn to designing the new class functions in detail. One way to do this is to write out the comment section for the new functions. The constructor's job is pretty obvious as it just copies its five parameters into the five fields of the newly constructed object. Thus we move to the comment sections for the other new functions:
That about completes the design of the
We also need to design the application program itself. We already said that we needed a kind of search function to go through the array and print the data on all cars that fit a given range. This leads to the following stand-alone function (that is, a function which is not a member of a class):
PrototypingNext we would probably build a prototype, rather than attempt to code the whole project. One likely way to do this would be to write the code for the class functions, since that looks to be fairly simple, but to use a stub for the Lookup function. For example, this stub could
just print a "function incomplete" message and not
really search through the array at all. The main function
would be written out. Then the prototype could be tested and debugged.
These details are not shown here in the interest of saving space. The
reader is encouraged to create this prototype as a practice exercise.
CodingWe need to code the class functions as well as the stand-alone Lookup function. Let's do the Lookup function
first. Assuming that the local variables have been suitably declared, the
code would look something like this:
The
The
We start with
Finally, we code the new class functions. This is fairly easy here. The
code for the new
For a look at the complete project, check through the following files. All five of these files would be part of the C++ project. Testing and DebuggingSince this program uses hard-coded data, it is pretty straightforward to test it. The list is not long. We can try various price ranges and check with our own eyes that the correct cars are printed. Also try specifying a range that no cars fit to see that no cars are printed. It appears that everything is working OK. MaintenanceHowever, it is possible that we have missed something above. After the program is put in use, someone might notice an error. Then we would have to fix it. A user might also request that a new feature be added to the program, such as allowing it to look up all cars that match a particular make and model, or all cars that match a particular year. However, if we were really going to do a lot of this type of thing what we really would want to do would be to set up a database using relational database software, as it already has built into it the code to do any type of query (lookup) imaginable. DocumentationWe have created a large amount of documentation above. We have written descriptions for most of the stages, a drawing of a Car object, the internal documentation (program comments),
probably a record of testing, etc. A user's manual might even be written
although this program is pretty simple to use.
Related Items
|