Object oriented programming with C++


To enable students to:

  1. understand Object Oriented Programming (OOP)

  2. design programs

  3. develop programs

  4. document programs

  5. test and debug programs

Understand OOP

Traditional (procedural) programming while highly efficient if properly designed and written is often difficult to maintain, upgrade and re-use. In such systems code and data structures are kept separate, with several pieces of code operating on the same data. Unfortunately their relationship is not always obvious leading to a change in one area having adverse effects on others in an unpredictable way.

To help with this problem there needs to be some way to group data and the code that operates on it into one logical place. Object oriented program (OOP) achieves this by bundling (encapsulating) into one entity (object) a piece of data and the code to operate on it. OOPs robustness comes from the fact that the data and code in the object are not handled directly by the program but by the object itself, upon being told to do so. A true OOP does not support any global data items.

OOP is based on a model of reality. Procedural programming assumes a static world - it can not cope with changes in its environment. OOP permits the real world to be modelled closely, permitting easy revisions to reflect the world as it evolves. The real world can be described in terms of verbs (actions) and nouns (objects upon which the actions are performed). Objects posses state which represents the values it currently holds (for example a red ball). A problem to be solved by a program is broken down (abstracted) into objects with all their associated actions and values. Consequently OOP is a method of implementation where programs are organised as co-operative collections of objects - not too dissimilar to real life.

Object co-operation in OOP is based around message passing, no data is passed. The objects in a system keep their data to themselves and perform operations on that data according to the messages received. The sender of a message has no knowledge on how a recipient organises itself internally, only that it responds to a particular message in a well defined way.

Objects which respond to messages in the same way are grouped together into a class. In C++ a class is a data type, just like an int or char, which specifies how an instance of it (an object) behaves and which properties it supports. As an analogy, an object is an instance of a class as a cod is an instance of a fish. Each class defines the data members known to an object, i.e. the data it operates on, and the member functions used to manipulate that data. Member functions are used to implement the message passing mechanism in OOP, i.e. a conceptual message flow is in reality the execution of a member function. For example object A will get object B to perform some operation by calling one of object B's member functions.

Classification of real world objects often reveal similarities between them. Everyone, as well as being a distinct person also breathers, eats and sleeps - these commonalties can be split into base classes which are shared (inherited) by their derived classes. Inheritance prevents unnecessary code duplication and permits common behaviours to be updated, the changes being felt by all associated objects in the system.

Inherited behaviours often differ between derived classes. For example, cars, boats and planes are all means of transport. They all accelerate and change direction, but the implementation is different in each case. The passing of a message to an object and it interpreting it in a way appropriate to itself is termed polymorphism. In biology polymorphism describes related organisms that can assume a variety of forms.

Each class can have multiple instances, for example a program may declare two cars, three boats and a place. Each instance of a class is treated separately by the program, messages sent to one and it relevant reactions do not effect other objects of that class.

OOP adds three key features; classes, inheritance and polymorphism to structured programming.

Design programs

Computer programs are often used to solve extremely complicated problems in real life. What may appear a simple task (such as payroll processing in a medium business) may in fact have many subtleties - different PAYE bands, full time, part time and contract workers, sabbaticals, payment in kind, etc.

All these, plus room for future expansion must be catered for by any solution. To achieve this end a great deal of thought must go into designing the program before any start on coding is made (in large systems this may be a split of 80:20).

It is assumed students have covered design techniques previously. Although traditional design techniques are applicable to object oriented systems they may need t be applied in subtly different ways. When designing object oriented systems attention is paid to representing the problem as a series of interdependent entities sending messages to each other, designs for traditional systems are more likely to be based around the flow of data.

Before design can take place it is necessary to specify the problem to be solved. Without a specification it is impossible to know if the design and eventual solution meet the requirements. Example specifications have been met in the practical programming assignments for elementary course (7261-229).

Develop Programs

Besides function prototypes and constant definitions, C++ header files (.h) also contain class declarations. Class declarations contain information of interest to a user of a class, not just the person implementing it.

By placing class declarations into header files it is possible to use a class in one source file, but define it elsewhere. Good coding practice suggests implementing each class in a .cpp file of its own.

The object oriented approach to program development permits systems to evolve. Programs can be developed in an iterative fashion, initially the constituent objects can be implemented in a very rudimentary way (no error checking, poor user interface, etc.), once working individual classes may be improved or replaced without impacting other portions of the system.

When developing programs source code should follow programming standards. These will vary from site to site, but should be applied consistently throughout a project. For example

  1. use lower case for variable names and upper case for constants

  2. use braces around program blocks consistently

  3. ensure there are plenty of comments to define what each function does

  4. place any machine or compiler dependent code in separate source files

  5. write small, simple functions

  6. etc.

Obviously in a complicated program there may be many classes and consequently many separate source files (one for each class). To control all these source files many compilers support projects which allow many files to be grouped together to produce a single executable. In large developments it is not uncommon for several source files to be interdependent, i.e. when one changes all must be recompiled. For example a header file may define a constant as #define RED 7, if this was to change all the files that include that header file must be recompiled. Manually tracking these dependencies is both laborious and error prone, fortunately projects automate this process ensuring the executable is always built from up-to-date object files. Project support on compilers varies considerably, some use convoluted scripting languages while others support slick GUI interfaces.

Document Programs

All programs require documentation, but it must be targeted at the appropriate audience - it is no good informing end users how a particularly elegant sort routine was implemented (or even what language was used) while developers are not necessarily interested in a comparison with an existing system the new program is supposed to replace.

User guides should be brief, non-technical and provide a concise description of the program and its purpose. For example the following areas may be covered:

  1. loading and running software

  2. loading any data files

  3. methods to enter data

  4. saving information

  5. exiting

The user guide should enable computer illiterate users to operate the program it describes, guiding them through its features step-by-step.

Programmers guides must contain sufficient information to allow others to maintain the system. Details on all the classes should be provided together with how they tie together (i.e. message flows, responses to individual messages, etc.). Provide the information you would wish to be available if you were maintaining someone else's code, the thinking behind a certain function, where improvements could be made, known problems, etc.

Within the source each function definition should be described by comment blocks, for example:

/* */
/* Name: Multiply */
/* */
/* Function: Multiplies two numbers together, returning the result to the caller */
/* */
/* Parms In: iNumber1 Multiplier */
/* iNumber2 Multiplicand */
/* Parms Out: None */
/* */
/* Returns : integer Result of multiplying multiplicand by multiplier */
/* */

Indicate its purpose, date of creation, values taken in and passed back, etc.

Each class declaration should have a comment block providing similar information as for functions. It is not uncommon for this information to be replicated in the printed programmers documentation to serve as documentation of the classes implemented within the program. An example class comment block:

/* */
/* Class Name: CPoint */
/* */
/* Version: 1.0 */
/* */
/* Derived from: CBaseObject */
/* */
/* Purpose: Describes a point in space */
/* */
/* Data Members: */
/* */
/* Visibility Type Name Purpose */
/* */
/* private int m_ix Holds X co-ordinate */
/* private int m_iy Holds Y co-ordinate */
/* */
/* Member functions: */
/* */
/* Visibility Name Purpose Coded Tested */
/* */
/* public CPoint Constructor Y Y */
/* public offset Specifies offset for X and Y Y N */
/* */

Compilers provide many standard functions and classes. Full details on these are often provided with the compiler. This documentation is often a good template to use when providing developer documentation for your own programs.

Test and debug programs

Computer programs are often used to solve extremely complicated problems, which customers expect to behave correctly one hundred percent of the time. Unfortunately in large program this is frequently impossible, the sheer number of possible states the program can be in and the mechanisms used to change between them are too large to feasibly test.

This is particularly true of systems developed using procedural programming where manipulating data in one component can cause unexpected results in other areas. Testing has to be particularly rigorous to tease out such hidden interdependencies.

Programs developed using OOP techniques frequently have simpler testing requirements. Each individual class making up the system can be tested in isolation once complete. As individual classes are combined together into units of functionality the unit can be exercised. Finally, once the complete system has been assembled its behaviour can be tested and verified. By ensuring all classes are well tested prior to use it is possible to swap one class for another and only have to perform small amounts of testing on the entire product.

To test each class the following approach should be adopted.

  1. Identify all possible states the class can be in, as an example a light bulb may be on or off.

  2. Identify all methods supported by the class and what effect they have on its state.

  3. Devise test data that will exercise all methods and hence states of the class.

Test results should be recorded to form an audit trail. In this case if a system fails with a customer it is possible to track down where inadequate testing occurred and ensure it does not take place in the future.

Having recorded the results of testing a class they should be compared with what was expected. Any differences must be investigated.

Once the reason for the discrepancy has been identified and solved all the tests should be repeated until no mismatch occurs.