There are also three minor elements which are desirable and useful, although few languages support all three:
Typing
Concurrency
Persistence
A language that implements the four major elements as discussed below may be described as Object Oriented.
Abstraction
This is the process of design which allows us to ignore details, commonly resulting in a top-down approach. To quote Grady Booch (Object Oriented Analysis and Design):
"An abstraction denotes the essential characteristics of an object that distinguish it from all other kinds of objects and thus provides crisply defined conceptual boundaries, relative to the perspective of the viewer."
Concentrating on the essential characteristics of an object which distinguish it from all other objects allows implementation to be ignored. In other words abstraction allows questions such as ' What can it do? ' rather than ' How is it going to do this?'. The essence, then, is to define an object in terms of those features which are unique to it, or which simply summarise its nature or function.
Other languages talk of the PROTOCOL or INTERFACE. C++ uses Public Member Functions. Also, when discussing typing, C++ is particularly good as the use of virtual functions and late binding allow the programmer to forget the details of data being operated on.
There are many kinds of light bulb, but they all share the common function of providing a source of light. This view concentrates on the light bulb's essential function, whilst ignoring its implementation. Regardless of the nature of the light bulb - incandescent, fluorescent, or gas vapour - the main features are that you can turn it on and light is produced, or turn it off and the light ceases.
This could be shown with the following (incomplete) class:
class LightSource
{
public:
LightSource();
~LightSource();
void TurnOn (void);
void TurnOff (void);
int IsOn (void);}
};
The class LightSource declares an abstraction of a light source, declaring the operators which may be performed on classes derived from it, namely turning the light on and off and determining its current state. At this stage the mechanisms for doing this are ignored.
Encapsulation
Also known as INFORMATION HIDING. This is complementary to abstraction and is used to describe the process of defining the code and data which form an object. (Many programmers erroneously interpret this to mean data hiding. It isn't: we hide both data and code). Within the object, some data and code may be accessible directly as the interface of the object, while other parts of the object remain private. In fact it is usual for all data to be private, access to values being through the return of (public) member functions.
C++ provides three levels of privacy: public member functions, which effectively implement abstraction; private member functions and data, which implement the Encapsulation, and protected member functions and data, which are somewhere between the two, and are of more concern when considering INHERITANCE. There is also the Friend mechanism, where co-operating classes can have access to each other's private member functions and data.
Generally, therefore, it is a goal of encapsulation to ensure that all information and processes associated with an object are contained within its definition. It should not be possible to affect the state of an object other than through its public interface.
We can now expand the declaration (and definition: we will use in-line functions) of the LightSource class:
class LightSource
{
private:
int lightState;
void Light(void) = 0;
void Dark (void) = 0;
public:
LightSource() {lightState = 0;}
~LightSource();
void TurnOn (void) { if(!lightState) Light(); lightState = 1;}
void TurnOff (void) { if(lightState) Dark(); lightState = 0;}
int IsOn(void) {return (lightState != 0);}
};
Encapsulation has occurred:
- The state of the light source is hidden from clients via the private variable lightState and may only be read or altered via the published interface
- All of the operations which may be performed upon a light source are defined by the three public functions TurnOn, TurnOff and IsOn
It is fair to say that encapsulation can lead to complexity: it is not in itself a guarantee of simplicity and due care must be exercised.
Modularity
This is about the physical structure of the program. Many languages, and C++ is no exception, allow the separate compilation of modules which are then linked together to form the executable code. The basic conventions of C still apply, but may be a little more formally expressed: Header (.h) files are effectively the interfaces of the different modules, and the .c or .cpp files contain the implementation. As to what should constitute a module, much depends on physical constraints. For a small, relatively simple project it may be quite satisfactory to include all classes and objects in one module, whereas in a large system life will be more difficult.
In many senses modularity almost comes for free. A general rule is to group logically related classes and objects in the same module, setting up interfaces only to those parts other modules must see. Remember that the goal is twofold: the simplifying of documentation under the divide and rule principle, and the elimination of unnecessary compilation. The two ideals are COHESION, that is, groups of logically related abstractions; and LOOSE COUPLING, that is, minimal dependencies between modules.
Once, then, the key abstractions have been identified, it is a relatively simple step to divide physically the implementation into coherent modules. Abstraction combined with encapsulation ensures that a given module will include all relevant functions and data. Encapsulation ensures that the modules are unaware of, and hence unaffected by, changes to the implementation details in other modules. This is a major advantage. Regardless of good intentions, in any large system programmers are often led to make coding decisions which depend on the internal implementation of another module; perhaps even relying on side-effects.
Modularity may of course be affected by other needs: separate processors in a multi-processor system; segment size limits; dynamic calling behaviour in virtual memory systems (consequent on the need for late binding); the building of libraries where reusability is the main objective; and work allocation in large project teams. In any event it is important to realise that modularity is purely about the physical design: it is the use of classes and objects that form the logical basis of the project.
Continued...