An Introduction to Object Orientation
Author: Robert Manthey
Last Revised: April 2001
Author's notes on how to use this page:
This site includes links to code examples in multiple languages. Once you get to the code links, I recommend that you window this page so that it uses the upper half of the screen and when you open the examples, move the code windows to the lower left and the output windows to the lower right.
I have chosen to format it this way (rather than use frames) so that you can move the windows around to suit yourself and look at the code in alternate languages (for language comparison), plus you can save or print the code easily from the windows.
Note: if you have your browser locked-down, it may initially refuse to pop up the windows. You should be able to reconfigure your browser to get around this.
If you load all the links in an example set it will get a bit crowded on the taskbar, but the next example re-uses the corresponding language windows.
I have attempted to keep the examples as similar as possible across the languages, however in some cases it was more relevant to expose peculiarities of a specific language.
1. Introduction to Object Orientation
1.1 Abstraction
Computer programming is basically about causing a computer to simulate some aspect of real life.
Consider a Flight Simulator program. Its objective is to provide an experience as similar to flying an aircraft as can be possible from a desk. This means including dials and gauges on the screen and of course a realistic view from the cockpit. It can extend to the use of a joystick or more complex controllers - like an actual cabin in the case of commercial pilot training simulators. In the case of joy-rides at amusement parks, the entire cabin is automated to simulate gravity orientation.
Similarly an accounting software package is written so as to simulate and record transactions in the same way that clerks would record cash details in ledgers and customer bank books, and then simulate the way that an accountant would audit and produce interim reports.
Even pure mathematic modelling programs are utilised to solve invisible (yet real life) problems. Graphs of the energy quantities at an array of points throughout an atom are still just representations of some aspect of real life, like the accounts package or the flight simulator.
That computer programs provide simulations of real life problems or situations is called "Abstraction", ie: the program is an abstract form of part, or all of, the real situation. It may be argued that some forms of free art creation using computers may not actually simulate real life, however even the use of a graphics program to create artwork clearly simulates use of an easel and paintbrush. Perhaps you can think of a form of computer programming that has no aspect of abstraction within it at all? Let us know!
1.2 Solution Space and Problem Space
Since programming is about abstracting real life situations, then it can be thought that, for any particular problem and solution, the abstraction operates within the computer and can be called the "Solution Space" whereas the real problem exists in a place in the real world termed the "Problem Space"[3].
In the case of the program modelling the atom, the activity of the program in the solution space has no effect at all on the atom that exists in the problem space. On the other hand the activity within the solution space of the accounting software package almost complete determines the outcomes in the problem space - unless someone picks up an error and makes a manual correction. Likewise the behaviours in the problem space directly affect the activity in the solution space - eg: when a customer makes a purchase and the transaction is recorded. The same can be considered of the flight simulator to varying degrees, however consider a flight control program for an actual passenger jet. The activity in the solution space (the program) has absolute control over the results in the problem space (the jet flies or crashes!)
So, depending on the application, the solution space and the problem space may interact to varying degrees, requiring the programmer's consideration as to how they interact. An activity in the solution space may take over from an activity previously undertaken in the problem space, or it may have an entirely new function.
1.3 Procedural Programming versus Object Orientated Programming
All computer programs are object oriented from one very awkward point of view - that is that the entire procedural program is a self standing unit of code that behaves in a particular way (has functions) and contains specific defined information (has data). This is the core definition of an object common to all object oriented languages: "The essence of object oriented programming is to treat the data and procedures that act upon the data as a single 'object' - a self-contained entity with an identity and certain characteristics of its own".[1]
Let's get that point clear first within a procedural language context. Any given program starts and stops at your command (one way or another!) It will respond to specific command while it is running (if defined to do so). It has certain information or data contained in various ways within its boundary of operation either in RAM or ROM or on various kinds of disk drive. So an entire program of any kind is, in a way, a single object.
However, what is inside the procedural program is a set of program language instructions packed together with varying degrees of skill and assorted methodologies, such that the inners of the program in the solution space is not necessarily similar in structure to the inner structure of the problem space being modelled. The way that the procedural flight simulator program handles the fuel usage would possibly be via a set of code that takes the engine speed and simply multiplies it by a factor and subtracts that from the fuel tank volume. The fuel gauge would then read the tank volume from a register and display it on screen.
Easy? Sure! And that's fine for a domestic game on a PC. But what about for a leak detection program in an actual passenger jet control program (not in a simulator!). This would involve the same problem - modelling the expected fuel usage from a fuel tank - but this time to compare it against measured fuel usage to detect potentially catastrophic fuel line leakage! The need for absolute reliability and accuracy within the solution space model is vital - if it is inaccurate the "leak alert" will be forever false alarming and indiscernible from a real fault!
To solve problems like this, the procedural approach requires incredible care to program operation and internal interaction, and perhaps the program would - in some ways - attempt to internally mirror the problem space.
The Object Oriented approach is to solve all problems by analysing the problem space's component "objects" and directly modelling those components with similarly behaving program "objects" within the solution space. The fuel tank is a single object in the problem space and so it is treated as a single object in the solution space; so are the engines, the wings, the plane's body, and perhaps the air around the plane.
The point is: the program representation (object) of the fuel tank should be as neat a mirror of the form and behaviour or the real item as necessary to effectively and efficiently succeed in its appointed task:
- The fuel tank has stored information: how much fuel is contained, and perhaps what temperature it is at, and perhaps even a low level alarm switch.
- The fuel tank also has set behaviours: depending on how many engines operate and at what speed and altitude, the fuel level is reduced (a behaviour acting upon internal data based on external data). Also if it is heated beyond a certain temperature it will explode or if cooled too low the fuel lines will freeze (behaviours based on internal data acting externally). If an outside source requests the fuel level or temperature, it will return the information in a set format.
"Object Oriented means looking at a problem in terms of the objects involved with that problem."[2] This can certainly be done within a procedural language, however object oriented languages provide programming structures and formations that allow for slick and consistent use of the object oriented approach.
In the case of Microsoft Visual Basic (version 6) and assorted vendors' versions of C++ the use of the object oriented structures is actually optional within the language. Programs may be written procedurally, object oriented, or both. With Java, object orientation is mandatory at the highest level, and highly advisory at all levels below. Still, it is possible to make Java become a procedural language by ignoring the object structures and resorting to putting all the code for the data and functions in one lump within a single object. In fact all objects are just small sets of procedural code wrapped in nice tidy object "frames".
Object oriented programming is about three things:
- Learning to think in terms of translating real life objects (ie: fuel tank, general ledger, Mike Jones' savings account, an electron) into directly similar program objects in code, according to the purpose of the program.
- Learning the new concepts (and terms) that are common to the field of object oriented programming, regardless of which programming language.
- Learning the specific structures within the various languages that provide for object behaviours.
So far this tutorial has focussed on the first aspect - and this will become easier once you start (and the more you continue) working within this form of programming.
The rest of this tutorial will help you with the second aspect - the new concepts that are introduced to describe the behaviour of objects within any program language. Then you'll be ready for the third part - learning and applying the specific structures within Visual Basic, C++, Python, C#, Delphi and Java!
2. Object Orientation Concepts
2.1 Instantiation and Types / Classes
In the first tutorial you learnt that, in a very rough way, all programs are actually objects. They are a single independent operating unit, they can contain information or data, and they can have pre-defined behaviours or functions.
When you buy a copy of Netscape, for example, you buy a set of code that is in effect a dead framework. It has a predefined set of behaviours and can contain specific data. It is the identical piece of code that is provided to millions of other computer users. However, it does nothing until you "run" it. What actually happens when you "run" it is that a single copy of the dead program code is loaded into RAM and the CPU is told to include that section of memory in its execution list. A single occurrence or instance of the Netscape program is now live within the computer and it will carry out its designed behaviours and keep its own set of data current. If you then go back to the start menu and click on the Netscape entry again (try it!) a second copy of the Netscape code is loaded into another area of RAM and the CPU is instructed to add this to its execution list. A second instance of Netscape has been brought into being or instantiated and this second copy - though originally identical - is now free to behave differently (though only the same pre-defined behaviour set is available!) and hold different values for the predefined data.
(Note: not all common programs will do this! Some versions of commercial word processors will look for an existing instance in memory first, and if it finds one it will pass the document requested on to the instance already running instead of instantiating another copy!)
Object oriented programming propagates this characteristic of instantiating objects throughout the internal operation of the program. For a bank account program, a set of code is written that mimics the behaviour and information contained in a bank's cheque account. This is written in the program as a "template" or "model" and is commonly called a type or class within different languages - it is like the general information known by the bank as to how a cheque account operates. There is no actual cheque account for any specific customer yet, it is just a model or class ready to use. When a customer opens an account with the bank a single account is started which will now exhibit set behaviours (adding interest on a regular basis) and contain data (owners details, balance), and it is clearly identifiable from another customers account. In the solution space an instance of the class is instantiated and it can now mimic the paper version of the cheque account. In fact it can replace the paper version and become the actual cheque account itself, if the objective is to replace the paperised systems.
The thing to keep in mind is that the class itself is not active until it is instantiated, and then it is a single instance of perhaps many. Hence for our fuel tank example, you may have four fuel tanks instantiated for a plane, each behaving independently and containing individual characteristics, and there are likewise four instances (objects) of the same set of code (class) in RAM being processed by the CPU independently. Objects of one class may also instantiate objects of other classes if that is within their task - a "Plane" object may instantiate four "Fuel Tank" objects as part of its start-up procedure.
2.2 Object Lifetime
To create living objects from otherwise dead classes, object oriented languages use a specific structure to instantiate the object known as a Constructor. This differs in form in each language but the concept is common. Once the constructor invokes the object it has no other task to complete, it is simply responsible for giving birth to the object.
Constructor Example:
View example code in: Java ... C++ ... Python ... C#
- runtime output from: Java ... C++ ... Python ... C#
The counterpart to the constructor is the Destructor. This is an important program concept in object oriented programming - not least being that it is a key difference between the languages, specifically C++ which has one and Java which doesn't have a manual destructor. Java's destructor is not programmed manually, but is a built in feature called the Garbage Collector. Nevertheless, once objects are instantiated and their use is completed, they need to be removed from RAM, and the memory reallocated as free space. In C++ you need to write destructors manually and intelligently or the disused objects will fill up the computer's RAM and eventually (or soon!) stall the computer. In Java it's a bit nicer, in that Java internally busies itself looking for disused objects and removes (destroys) them, freeing up the RAM.
The construction and destruction of objects therefore define the object lifetime.
2.3 Encapsulation and Data Hiding
As mentioned before, within any specific class (which, in summary, is an object definition and which will become an object when instantiated) there are set behaviours (which are simply methods or procedures), and a predefined data set (which may be variables, constants, arrays or otherwise). The data may contain initial values, but generally it only becomes interesting once the object is active and the data changes for that specific object, making it unique. The functions must have both definition (the parameters they receive when called - also called the function signature) and implementation (the set of command or instructions that the function carries out when called) for the host class to be able to be instantiated into an object.
Included in the data in a class may be three levels of information:
- Data that you want to make available to other objects or outsiders.
- Data that is purely about the internal workings of the program and really not of interest to other objects or outsiders.
- Data that needs to be kept secret from prying eyes and mustn't be made available to others.
Likewise the procedures or methods may need to be either made available to other objects or the public, or be kept hidden. This is similar on a small scale to what goes on with existing procedural programs - they make some data and functions available to other programs and users but hide other data and methods, to varying degrees (called Data Hiding).
Within object oriented programming this is applied not only at the outside boundary of the entire program (as in procedural programs), but from object to object within the program's internals. This is a key principle of object oriented programming called Encapsulation. Selected data and procedures are made available to outside access and others are kept hidden. As noted before similar behaviour can be mimicked in procedural languages using scope specifiers such as static and extern in C, however encapsulation in object oriented languages is an express issue rather than an optional inclusion.
Encapsulation Example:
View example code in: Java ... C++ ... Python ... C#
- runtime output from: Java ... C++ ... Python ... C#
2.4 Inheritance
Now consider that you might have defined a particular object with a set of fairly general properties and behaviours (data and functions). Then you decide to define another object that is an example of the first object but more specific, perhaps with added functions or data. Performing this in an object oriented language is called Implementation Inheritance. "A new type can be declared that is an extension of an existing type."[1] The basic object has behaviours and data which are inherited by the subsequent "derived or inherited or sub or child"[3] class. This is performed in most object oriented languages without any need to repeat the definitions of the methods. Visual Basic 6 is an exception, and does not correctly support implementation inheritance[2]. Visual Basic 6 uses delegation (explained later) to make up for this absence.
Inheritance Example:
View example code in: Java ... C++ ... Python ... C#
- runtime output from: Java ... C++ ... Python ... C#
"Safe" object oriented languages such as Java and C# insist that any one object can only inherit from a single other object. Although a class "Car" may be conceptually derived from a class "Vehicle" and at the same time derived from a class "Company Asset", Java and C# are not set up to perform this task by multiple inheritance. Once a class is derived from one class, it cannot be additionally derived from another on top of that. According to a C# author: "Multiple base classes usually add more problems than they solve. That's why C# (and Java -ed) allows only one base class. If you feel the need for multiple inheritance, you can implement interfaces."[4] C++, Python and ATL allow multiple inheritance, however the implementation from one compiler to another can be unpredictable.
2.5 Interfaces
An Interface is basically a class written with all its methods (and data) declared or defined, but without any actual implementation of those methods (ie: there are only empty function definitions - no code is written within the methods). Objects cannot be instantiated directly from interfaces - the interface needs to be implemented by inheritance into a sub-class first, after which it can be instantiated into an object or objects. The most notable use of interfaces is that of multiple inheritance. Whereas a class cannot inherit from multiple classes, it can most certainly inherit from multiple interfaces.
In this way the class "Car" may be derived from the class "Vehicle" (which has a set of defined and implemented methods), and then also implement the interface "Company Asset", and must add the actual code for (implementing) the methods defined by the "Company Asset" interface.
Interface Example:
View example code in: Java ... C++ ... Python ... C#
- runtime output from: Java ... C++ ... Python ... C#
2.6 Polymorphism
Inheritance and interface implementation gives rise to another key concept within object oriented programming - Polymorphism. Poly (many) morph (form) - hence this is "having many forms". Consider a program that has "Car" and "Bike" as sub-classes of "Vehicle" and that the class "Vehicle" has a defined method of "Park". When classes "Car" and "Bike" are defined they may have individual implementations of the method "Park", and hence they behave differently when instructed to "Park".
The key concept is that the section of program calling the "Park" function from an instantiated object of type "Car" is no different to that which calls for "Park" on a "Bike" object - the calling program just instructs the "Vehicle" (whether "Car" or "Bike") to "Park". Same function call, same intention, different implementation. "Car" and "Bike" are polymorphs of "Vehicle".
"Vehicle" may however have other functions such as "Steer" which are fully implemented in "Vehicle" and hence are inherited by both the "Car" and "Bike" extensions of the class"Vehicle". When a class of "Car" is instantiated as an object (eg: "Green Utility #3") then the computer will take the code within "Vehicle" plus the code extensions within "Car", combine them, load the result into RAM and designate it as "Green Utility #3". "Green Utility #3" can then be "Park"ed.
The implementation of "Park" is different for class "Bike" and this is the key to Polymorphism - defining functions in a root class and then implementing them differently within sub-classes causes them to be polymorphs of the root class.
Polymorphism Example:
View example code in: Java ... C++ ... Python ... C#
- runtime output from: Java ... C++ ... Python ... C#
The power of polymorphism is in the abililty of a collection of objects of the polymorph sub-classes to be addressed as a array of the super-class type. Hence you may have hundreds of objects which are various sub-classes of one super-class and the entire array may be pushed though a loop, driving a function defined the super-class, but implemented individually in each sub-class.
2.7 Overriding
When a sub-class extends a class, as described, the sub-class may:
- Inherit defined implemented functions from the root class,
- Implement defined (but otherwise unimplemented) functions from the root class or interface, and/or
- Re-implement defined functions from the root class that were already implemented but need to be changed to suit the new sub-class. This means that the sub-class's code includes rewrites of already written functions; this is called "Overriding".
"When a derived class creates a function with the same return type and signature as a member function in the base class, but with a new implementation, it is said to be overriding that method."[1]
The code sample for Polymorphism above also included a demonstration of Overriding a method, as seen with the change made to the Park method. Hence when the Bike is "Parked" a different message is produced to that of the base class's version of "Park".
One would write code which overrides when the root class still largely suits the purposes of the programmer but needs minor modifications, or if the root class is out of the control of the programmer (ie: it belongs to someone else!) Modifying the root class would mean taking consideration to the changes passed to all sub-classes that have already been written around that root class. This may be an impossible task if the code has been released for public use, hence overriding a useful class may be a valuable technique instead of either rewriting it and hunting out all its sub-classes or writing a whole new class.
2.8 Overloading
We commonly like to use simple yet meaningful names for our functions. Hence it may be logical to have a function such as "Display" defined to receive a number of different parameters (and/or objects) and correctly react to each one. However, because of the strong type-checking in most object oriented languages, parameters passed to a function must be of the correct type.
Hence a new feature is provided. Where in procedural languages such as C it is illegal to have two functions of the same name within a single scope, it is legal in most object oriented languages to have multiple functions of the same name within a single scope, provided they have different signatures! A class may include a set of functions of the same name but with different signatures and implementations - "When you overload a method you create more than one method with the same name but with different signatures."[1]
Overloading Example:
View example code in: Java ... C++ ... Python ... C#
- runtime output from: Java ... C++ ... Python ... C#
A problem occurs in some object oriented languages (ie: C++) where a single member of an overloaded function set in a base class is overridden in a sub-class. If the overridden function is called in a resulting program with parameters that match one of the non-overridden overloads, a compile-time error will result because the request may be ambiguous. Hence when overriding overloaded methods, a programmer should take care to override each of the overloaded methods, not just one of them.
2.9 Delegation
"A fundamental premise of object-oriented programming is that each object does one thing very well and delegates to other objects anything that is not its core mission."[1] The strength of this form of programming is in having a program which is highly functional, easy to add components to and easy to read and debug. Hence having a programmer waste time and code space by re-implementing a function that is better done by another class is not proficient. "Calling another routine to perform an operation is called Delegation"[2]. Delegation is used instead of rewriting the code for a particular task. To use delegation, the objects do not need to be related (like a sub-class and its root-class), the object simply calls the "public" function in another object which will result in the required activity taking place.
Summary of Object Orientation Concepts
Data - constants, variables, strings, arrays, etc
Method - a set of code in a program grouped together and given a name which other parts of the program can call and hence cause to run
Definition / Signature - the statement of which specific parameter types or classes are passed to a function when it is called
Implementation - the set of statements in a function which are executed when it is called
Object - a running fragment of code which has data, methods and a means to interact with it
Class - a set of code defining an object's data, methods, and means of outside interaction
Interface - what would be a class, except that the member functions have no implementation in them (just signatures)
Constructor - a code formation which instructs the CPU to make an object from a particular a class, put it in memory and run it
Instantiation - what a constructor does - making an object from a class
Destructor - a code formation that removes any object from RAM after its use is completed
Garbage Collector - an automatic destructor provided by some object oriented languages
Object Lifetime - the period of existence of the specific object in RAM, from instantiation to destruction
Encapsulation - concise containment of methods and data within an object
Data Hiding - using encapsulation to conceal data or methods
Extending - taking an existing class and adding more methods
Inheritance - the fact that information (definitions, methods and data) may be received by the sub class from the base class as a result of extending it
Implementation Inheritance - inheritance of methods and data from a base class
Interface Inheritance - inheritance of function signatures from an interface
Polymorphism - "many forms" - that a class can be extended in different ways to create varieties of objects from a base type
Overriding - when a sub-class includes a rewrite of the implementation of a defined member method of its base class
Overloading - when a class contains multiple methods with the same name, but with different signatures, so that a function call from another program will run a different method depending on what parameters are passed in the function call
Delegation - calling an existing function from another class rather than duplicating the task
References
- "Teach yourself C++ in 24 Hours" - Jesse Liberty (SAMS)
- "Doing Objects in Visual Basic 6" - Deborah Kurata (SAMS)
- "Thinking in Java" - Bruce Eckel (Prentice Hall)
- "Presenting C#" - Christoph Wille (SAMS)
- "C++ Primer" - Stanley Lippman (Addison Wesley)
- "Java2 and JavaScript" - Daconta, Saginich, Monk (Wiley)
- "Teach yourself Python in 24 Hours" - Ivan van Laningham (SAMS)
- "Web Programming" - Chris Bates (Wiley)
About the Author
Robert Manthey
BachEng Elec (1987) GradDip InfoTech (2001)
Member IEAust (2001) Member SEAQld (2001)
Robert has 12 years experience in the industrial fields of control systems programming and factory automation. Having completed graduate studies in software engineering at QUT, he is now working in commercial programming in the C derivative languages (Java, C/C++, C#).
|