First | Next | Previous | Last | Glossary | About |
So far your experience in implementing programs has been to combine two distinctive approaches, one analyses the processes involved, the other analyses the data structures involved. Using a structured approach you identify the processes and data structures for the various program entities and combine the process and data models to build a complete program or suite of programs.
There are other ways of course and in recent years we have seen the growth of the object-oriented approach. This approach doesn't view programming solutions as separate process and data models but as an object model. The object is the processes and data structures.
There is a natural progression from a structured programming approach to building programs to an object-oriented approach.
In this introduction I will follow that progression and review program implementation in structured programming terms, and then introduce the notion of the object. This way you should be able to see that the progression from process-oriented and data-oriented modelling to object-oriented modelling is a natural and logical progression.
First we will review some programming material so that we can see OOD from the perspective of things you should have done before. We start with:
In computer programming a data type declaration is effectively a template or plan for variables.
In C and C++, apart from using the existing simple and structured types, a types can be declared with the typedef reserved word:
typedef int Integer_Array[10];
this type can now be used by variable declarations:
Integer_Array x,y,z;
A common use of type declarations is in the specification of complex, structured data types. These are data types based on simple data types or other structured data types:
#include <fstream> #include <string> |
|
typedef char TNameStr[20]; typedef char TAddressStr[50]; typedef char TCourseStr[4]; typedef char TFileNameStr[127]; typedef struct { TNameStr Name1,Name2; TAddressStr Address; TCourseStr Course; } TStudent; |
The word typedef means that type declarations follow. TStudent is the type of the record; Name1, Name2, Address and Course are fields. |
main () { TStudent NewStudent, OldStudent; ifstream StudentFile; TFileNameStr StFileName; strcpy(StFileName,"STUDENTS.DAT"); StudentFile.open(StFileName); // .. do some work StudentFile.close(); } |
NewStudent and OldStudent are instances of the type. Open a file containing student records, do some work and close the file. |
We can also declare records of records:
typedef struct { TNameStr EmployerName; TAddressStr EmployerAddress; } TEmployee; typedef struct { TStudent StudentData; TEmployee EmployeeData; } TTrainee; |
In this example TTrainee inherits the fields of TStudent and TEmployer. |
We could sketch the student structure and program as shown in Fig 1. It consists of the student record structure, the program (things to do with the student structure) and the data file.
We haven't yet defined what can be done with the student record and will look at that next. The sketch introduces the notion of modelling a program without necessarily using flowcharts, Nassi-Schneiderman charts, structure charts etc.
Although a data type has been declared for students we still need to determine and specify what can be done with student records ie with instances of the type TStudent. The kinds of things we can do might include:
and similarly we need to specify what can be legitimately done with the fields of a student record. For instance we don't want to permit student names to contain invalid characters and we might use validation rules here.
The combination of the data type and the operations that can be performed on or with the data type is known as an ADT or Abstract Data Type. It is called abstract because it is an attempt to model a real-world entity and the behaviours that are associated with that entity, and, it should be independent of any specific implementation. We can look at this in terms of something other than a student record, for example a TV set.
The TV set we are modelling has the attributes:
Now that we have some properties we can sketch our model TV. The top of the diagram is a title for the model. The centre section shows the data fields and the lower section the behaviours or processes that operate on the data. We will use this diagram and variations on it quite often to depict the ADT. For now to continue the practical view we should look at a program which implements the TV ADT.
Our TV data model might be something like the structure shown to the right. It consists of a simple struct containing some of the kinds of properties that a TV would usually. |
typedef enum toggleswitch {OFF = 0, ON = 1}; typedef struct { toggleswitch OnOffSwitch; int VolumeLevel, ChannelNumber, Colour, Brightness, Contrast; } TTelevision; |
Here is a simple program which demonstrates how to use the TV ADT.
#include <iostream> #define MAXLEVEL 100 #define MINLEVEL 0 |
|
typedef enum toggleswitch{OFF = 0, ON = 1}; typedef struct { toggleswitch OnOffSwitch; int VolumeLevel, ChannelNumber, Colour, Brightness, Contrast; } TTelevision; |
This is the TV structure. |
void OnOff(TTelevision &); toggleswitch IsOn(TTelevision &); void InitTV(TTelevision &); void AdjustVolume(TTelevision &, int); int GetVolume(TTelevision &); |
These are the TV functions. The structure and functions together make up the ADT. |
int main() { TTelevision Sony; int count = 0; InitTV(Sony); OnOff(Sony); if (IsOn(Sony)) cout << "Sony TV is now ON\n"; else cout << "Sony TV is now OFF\n"; cout << "volume level is " << GetVolume(Sony) << endl; AdjustVolume(Sony, 5); cout << "volume level is " << GetVolume(Sony) << endl; OnOff(Sony); if (IsOn(Sony)) cout << "Sony TV is now ON\n"; else cout << "Sony TV is now OFF\n"; return 0; } |
The main function has one instance of TTelevision. This is initialised to a known state, the TV is then turned on and its on/off state and volume level are displayed. The volume is set to 5 and displayed again. Finally the TV is turned off, there was nothing worth watching. |
void InitTV(TTelevision &TV) { TV.OnOffSwitch = OFF; TV.VolumeLevel = 0; TV.ChannelNumber = 0; TV.Colour = 0; TV.Brightness = 0; TV.Contrast = 0; } |
An important operation is to initialise our model TV to some known state, in this case OFF and all controls set to 0. Hand over a reference to a TV and set the initial state. |
void OnOff(TTelevision &TV) { if (TV.OnOffSwitch == ON) TV.OnOffSwitch = OFF; else if (TV.OnOffSwitch == OFF) TV.OnOffSwitch = ON; } |
Hand over a reference to a TV and if the TV is ON turn it OFF, if it is OFF then turn it ON. |
toggleswitch IsOn(TTelevision &TV) { return TV.OnOffSwitch; } |
Hand over a reference to a TV and return the current on/off state. We need this so that we can avoid attempting to do something with the model when it is OFF. |
void AdjustVolume(TTelevision &TV, int Level) { if (IsOn(TV)) if ((Level < MAXLEVEL) && (Level > MINLEVEL)) TV.VolumeLevel = Level; } |
Hand over a reference to a TV and a new level for the volume control. Set the volume to the new level. |
int GetVolume(TTelevision &TV) { return TV.VolumeLevel; } |
Accepts a reference to a TV and returns the current volume level. |
Most structured programming languages provide the kind of support that is necessary for ADT's, ie:
There are some problems tho' with ADT's defined in this way. What do we do when we have a TV which has additional controls, e.g. TV/AV, Stereo, Sound Muting? We would either have to modify the original data type or create a new data type. If we create a new data type then all the operations for the old type will have to be duplicated for the new, we can't use the old operations since the parameters will be different, our ADT isn't readily reusable. There is also a modelling problem: In the model the operations exist independently of the structure and this doesn't reflect the real world. The separation of data and operations also leads to some problems with the coupling of operations, that is how operations access and use data. We may find that for a given ADT some operations share global data and this is a weakness since one operation may inadvertently affect another operation. A second problem that occurs in languages which impose rules of scope is that an identifier declared with the name X in a procedure can render a global identifier X inaccessible until the procedure is finished. If we attempt to avoid the use of global data, which we should, and pass all data as parameters or parameter lists we face other difficulties. Assume that we want to minimise the number of variables being used in a program. We might decide to modify the procedure OnOff and rather than pass the whole record as a parameter we just pass the relevant field. We have reduced the amount of data and therefore any risks it might be exposed to by passing one field instead of six but weakened the ADT by weakening the close cohesion between the data and the operations.
The answer to many of these difficulties is simple and elegant, the data type and the operations are combined or encapsulated into a single thing. Now the ADT can almost seamlessly model the real world, the ADT enters the world of Object Oriented Programming. We can take our TV example and make it look something like this:
Example - using a struct | ||||||
---|---|---|---|---|---|---|
typedef enum toggleswitch {OFF = 0, ON = 1}; typedef struct { toggleswitch OnOffSwitch; int Volumuages. Now if we need to extend our TV ADT we have a number of options. We could simply add data fields and procedures/functions to it, at least there is only one thing to modify; or we could create another ADT which inherited the characteristics of the original in much the same that the TTrainee record inherited the fields of TStudent and TEmployer.
Classes and ObjectsClasses and objects along with encapsulation and inheritance are part of the language of OOP systems. What do these words mean? A class has properties or members and these can consist of data fields and methods. A class is to object oriented programming as a record type is to non-object oriented programming, what we can call procedural programming. An object is to OOP what a record variable is to PP. Just as a record variable is an instance of a record type so an object is an instance of a class. The difference between a class and a record type is that the class not only contains the data fields for an object but also the operations or methods for the object. The class is a template for an object. ![]() EncapsulationEncapsulation offers more than just lumping methods and data together, encapsulation also makes it possible to make some properties of the class inaccessible to the outside world and other properties accessible. By making some parts of the class inaccessible, that is by hiding them we can make the class more robust, the private part of the class is only accessible via the member functions. A benefit that is claimed for a well-designed class is that it offers a higher degree of utility or re-usability than traditional functions written in procedural languages. Along with encapsulation, the second great strength of classes is inheritance, that is, the ability to define a class in terms of other classes and it is from inheritance that classes gain their greater utility. A class can inherit the data fields and methods of a parent class and then add some of it's own. ![]() InheritanceThe base class is also known as a parent class or a superclass, the descendent class is also know as a child class or a subclass. A chain of classes has ancestor classes and descendent classes. A parent class can have many different child classes each of which is different. A child class can have more than one parent class, called multiple inheritance, and classes generally exist as class hierarchies. Base classes are also known as server classes and subclasses as client classes. ![]() The diagrams here show some of these characteristics of class inheritance arrangements. Inheritance and PolymorphismPolymorphism is a very useful feature of classes by which a base class enables subclasses to each carry out an apparently similar method in different ways. Here is an example: ![]() Assume that we are designing a sytem that models different shapes and our system can deal with circles, squares, rectangles and triangles. Each shape has an area but the way an area is calculated differs according to shape. The base class, TShape, has a data field called area and a method called calc_area. We then design subclasses of TShape, called TCircle, TSquare, TRectangle and TTriangle and each of these has a method called calc_area. If the base class and the subclass both have methods with the same name it might appear that there will be difficulties resolving which calc_area will actually be used by an instance of one of the subclasses. It turns out that object-oriented systems take care of this by providing virtual methods and you will see more of these later. Be aware for now that we can deal with circumstances like those shown in the figure. MessagesWe make an object do something by sending it a message and, essentially, message is a call to one of the member functions or methods of the object.
Copyright © 1999 - 2001
David Beech
|