Computer Science - C++
Lesson 12: Program Design, Abstraction, Object-Oriented Design, Classes
INTRODUCTION: In this lesson we return to our study of programming methodology and examine the design phase of developing software. The overall strategy used in designing software is to divide and breakdown larger problems into smaller ones. This process is called stepwise refinement.
A very important concept in computer science is the idea of abstraction. This different paradigm (a way of looking at a problem) has led to the development of object-oriented programming languages such as C++. The C++ construct called the class is the major tool for implementing abstract data types. This lesson will introduce the concepts of objects, data abstraction, and classes.
The key topics for this lesson are:
VOCABULARY:
STEPWISE REFINEMENT DECOMPOSITION
ADAPTABILITY TRANSPARENCY
ABSTRACTION METHODS
DATA ABSTRACTION ABSTRACT DATA TYPE
INFORMATION HIDING PRIVATE
PUBLIC CLASS
MEMBER FUNCTIONS OBJECT-ORIENTED
PROGRAMMING IMPERATIVE PROGRAMMING
ENCAPSULATION
DISCUSSION:
A. Program Design
1. The design stage of programming methodology converts the specifications of the problem to be solved into a detailed outline of how the problem will be solved. The process of designing a program is called stepwise refinement.
2. Stepwise refinement involves breaking down a problem into smaller and smaller subproblems. This decomposition continues until the fundamental subproblems of the overall project have been determined.
3. As the larger problems are broken into smaller ones, we try to leave the details unsolved as long as possible. The lower-level details will be solved when the subproblems are broken down as small as possible.
4. This process focuses on what must be solved and not on how each problem will be solved. How each problem will be solved will be tackled later in the design stage and the actual implementation of code.
5. Thorough decomposition of the problem should ideally lead to the following type of code:
a. Each subprogram (function) should solve one fundamental task. Do not overload a function with too much work.
b. Subprograms should be self-contained and written independent of each other. For example, a program might involve sorting and searching of data. It is better to separate these tasks into different functions because a sorting algorithm could be replaced later with a better sorting algorithm. If sorting and searching were combined in one function it will be more difficult to modify the program in the future.
6. This separation of tasks in programming is accomplished using the concept of abstraction. This difficult but important technique is covered next.
B. Abstraction and Abstract Data Types
1. Abstraction is fundamentally the separation of the specifications of an object from the implementation of that object.
2. For example, programming languages support an implementation of integers. As we write code involving integers, we do not worry about how integers are implemented in binary or how the computer solves binary math. We use integers as an abstraction. We know the general characteristics of integers and write code without needing to know how they are implemented.
3. Abstraction promotes information hiding in a program. When a programmer declares and uses integers in a program, the lower-level code to store and implement such data is hidden. Information hiding minimizes the amount of information and detail a programmer needs to understand to solve a larger problem.
4. An abstract data type is essentially a data type. An ADT has two components, attributes and operations. An integer ADT consists of:
attributes: value and sign
operations: +, -, *, /, =, relational comparisons, etc.
5. Programming languages such as C++ support object-oriented programming which allows the programmer to extend the language and support new data types.
C. Object-Oriented Design
1. The world in which we live is filled with objects. In the computer world a familiar object is the window which helps display information. A window is an object which can be described in terms of attributes and behaviors. The attributes of a window are size, contents, location, color, etc. Its behaviors consists of movement, resizing, opening, closing, etc.
2. An object in programming is an abstraction for a real-world object. A window is an attempt to model the attributes and behaviors of a desktop or a manila folder in a file cabinet.
3. Imperative programming is concerned with commands and control structures. The techniques of structured programming are used with control structures and fundamental data types to develop algorithms. Pascal and C are imperative programming languages in which the programmer focuses on writing procedures or functions.
4. Object-oriented programming is concerned with modeling objects as user-defined data types. In C++, the programmer will design new data types using the class construct.
5. C++ programming supports two programming paradigms. You will still be writing functions to accomplish the behaviors of an object, but you can now package both attributes and behaviors using the class construct.
D. A Dice Class
1. The class is how programmers define user-defined types in C++. By using classes we can model more complicated real-world objects using the built-in data types provided in C++.
2. Before we write our first class here is a short program which simulates a pair of dice without the use of objects.
Program 12-1
// A version of dice.cpp without objects – Visual C++ version #include
<iostream.h> int die (int side); main () { int
die
(int
side) |
a. The function rand() is provided in stdlib.h. This function returns an integer value ranging from 0 to 1.
b. The call to srand() initializes the random number generator. It is initialized with timer so it has an initilization value which changes on each execution.
c. This program will print 10 numbers ranging in size from 2-12.
3. It would be very useful to construct a dice object to use in many programs. We now proceed to plan out a dice class.
4. A die is an object which has both attributes and behaviors. Its single attribute is the number of sides. Its single behavior is that each time the die is rolled, the probability of landing on any one side is the same as all the others. Each roll is an independent event.
See |
5. A dice class is provided in Handout, H.A.12.1. We will study the general syntax of a class by referring to this example. Not all of the syntax will be explained in this lesson. Keep two goals in mind as you study your first class: |
a. You should be able to read a class, to understand the attributes and behaviors of the object being implemented, and use the object successfully in another program.
b. You should be able to make simple modifications to a class, adding new private state variables and new member functions.
6. The attributes of an object are stored as private variables. These are sometimes referred to as state variables - they maintain information about the condition of an object. The number of sides of a dice object is stored in the private variable mySides.
7. The behaviors of an object are solved as functions. The object supports three operations which are called member functions: constructing the object, rolling the object, and returning the number of sides in the object.
8. The public and private access specifiers allow for different levels of access by a client program which uses the class. We refer to a client program as one which includes the class (#include <dice.h>) to declare and use objects defined in the class.
9. The client program is allowed to use the public member functions but the private data members and the implementation of the member functions are not available. An abstraction barrier has been raised between the private section of the class and the client program. A diagram will help.
Diagram 12-1
Everything
in the private area is not accessible by the client program.
10. The following program
will use the dice class to produce a similar output as the program in
section D.3.
#include
<iostream.h> main () { dice die1(6), die2(6); int loop, total; for (loop=1;loop<=10;loop++)
{
cout <<"#sidesondie1="<<die1.numSides()<<
endl; |
a. The variables (objects) die1 and die2 are initialized with 6 sides. The dice constructor takes the value 6 and stores it in the private variable mySides for each die object.
b. To use a member function requires the period notation. The member function call die1.roll() returns an integer random number from 1-6.
c. The client program does not have direct access to the private state variable mySides. The only access to this value is through the member function numSides().
1 The class encapsulates state and behavior of objects into one package. When an object is instantiated (declared), the object exists in memory and with it come the methods to manipulate the object.
1 A class constructor is a member function with the same name as the class. At this point in your study of classes you should know the following about a class constructor:
a. The constructor cannot have a return type.
b. A class can have more than one constructor. The dice class has only one constructor which requires an integer parameter. If the parameter is omitted when such objects are declared in the client program a syntax error is generated. Later on we will write classes with more than one type of constructor.
E. Syntax of Classes
1. There are two syntax issues regarding classes which need explanation:
a. The separation of a member function interface from its implementation. This will involve the use of the scope resolution operator (::).
b. The use of #ifndef, #define, and #endif statements in the header file.
2. The class definition consists of all the code between the starting brace ({) and ending brace (}).
class dice { public: ... member function prototypes ... private: ... private data members ... }; |
The code to implement each member function could be written in the public area of the class, but this would make the class long and awkward to read. The implementation of each member function is usually written outside of the class.
3. When coding a class we wish to separate the implementation from the interface of the class. This will eventually result in two files: an interface file and an implementation file. For now we will keep both sections in one file but most objects are implemented using a two file system.
4. The double colon :: used in the implementation of each member function is called the scope resolution operator. The dice::roll() in the code
int dice::roll() // postcondition: # of rolls updated, returns random die roll { return rand(mySides)+1; } |
tells the compiler that the code for function roll() really belongs inside of the dice class. The use of the scope resolution operator dice:: allows us to write the code for a member function outside the boundaries of the class.
5. When the client program includes a header file such as
#include <dice.h>
we need to avoid a double compilation of this file. This is prevented by using the #ifndef, #define, and #endif statements.
6. When the compiler compiles the file dice.h, it will encounter the line:
#ifndef DICE_H
If this is the first time the file has been compiled it will pass the test (DICE_H has not been defined) and proceed to the next line. The line
#define DICE_H
will tell the compiler to add this identifier to a running list of files it has compiled. If the compiler were instructed to #include <dice.h> again, the identifier DICE_H has already been noted and the file will not be compiled again.
7. The contents of the file to be compiled is bracketed between the #define DICE_H and the #endif at the bottom of the file.
8. It is unlikely that a programming situation would arise which would try to include dice.h twice. But there will be many occasions where a popular header file such as iostream.h is encountered multiple times. In Lesson 16 you will have client programs which use these #include statements:
#include <iostream.h>
#include <apvector.h>
The apvector.h will in turn #include a file called apvector.cpp, which in turn #includes iostream.h. Because iostream.h has already been compiled, this later attempt to include iostream.h will be ignored.
9. The choice of the identifier to use in the #ifndef and #define statements is up to you. A common convention is to use uppercase letters and to use an identifier similar to the name of the header file.
REVIEW/SUMMARY: The topics in this lesson are critical in your study of computer science. The concepts of abstraction and object-oriented programming (OOP) will continue to be developed in future lessons. Before you solve the lab exercise, you are encouraged to play with the dice class and implement objects having different numbers of sides.
ASSIGNMENT:
Worksheet, W.A.12.1, Objects and Classes
Lab Exercise, L.A.12.1, Dice