In the OO topic Objects & Classes the concepts of objects and classes were introduced. We saw an illustration of the basic ideas by starting to develop a Track class. In Inheritance it was shown how classes could evolve by building more specific ones out of more general ones with the subclass designer choosing what features to retain from the base class, what features to modify and what new features to add. We took our Track class and evolved ChargedTrack and NeutralTrack from it. We decided to let NeutralTrack inherit the Propagate member function from Track but decided to replace (or overload) in ChargedTrack. Finally, in Virtual Functions & Polymorphism it was explained how to make embedded base class objects delegate responsibility for certain member functions to the subclass object in which they embedded. In this way functionality between cooperating classes can move up to subclasses (polymorphism) or down to base classes (inheritance). We saw this illustrated with Track and ChargedTrack. The Track class cooperated by designating Propagate as virtual which permits ChargedTrack to take control when the Propagate message was sent to a Track object embedded in it. We further saw the power of this concept, allowing us to write driver code to propagate tracks independently of the propagation code.
Its is this separation of the driver from the object, or in more general terms the client from the server that will examined further in this topic. When we decided to decided to create ChargedTrack and NeutralTrack, part of the reason was to allow two versions of the Propagate function. The decision to have a new version for ChargedTrack but not for NeutralTrack was, to be honest, a bit odd. In fact, to be very honest, it was contrived so we could examine both cases! It would have been far more natural to have provided a separate version for NeutralTrack. So we end up with 3 Propagate functions: Track::Propagate, ChargedTrack::Propagate and NeutralTrack::Propagate. Things look bleak for Track:Propagate! When we come to create real tracks they will either be charged or neutral, so the Track object will only exist within ChargedTrack or NeutralTrack objects, so Track::Propagate will always delegate its function and the code will fall into disuse. Anyone who maintains software will tell you that code that isn't used eventually goes bad, even if the decay mechanism is not fully understood! If its not used, it best to surgically remove it. So should Track be stripped of Propagate? Well, no it shouldn't. Recall how useful it was when writing driver code like this:-
Track *MyTrack = GetFirstTrack(); while ( ! MyTrack ) { MyTrack->Propagate(); MyTrack = GetNextTrack(); }It was the virtual function address table held inside the Track object that kept the driver separate from the Propagate function and allowed the decisions as to which function to call to be delayed until execution time. So we want to keep the table but drop the code. This may sound odd but C++ allows us to do just that, by declaring the Propagate function like this:-
virtual void Propagate() = 0;This means that the function body, i.e. its implementation does not exist. This is called a pure virtual function. Once a class has such a function it is no longer possible to directly create objects, as they are incomplete. Such classes are called abstract base classes and their objects only exist embedded within others.
When we have an abstract base class it means that we are dealing with concepts that are so generic, or abstract, that no single implementation that can do justice to the concept the classes embodies. The importance of such a class is not so much in the services it directly provides but the interface that decouples the client from the server, allowing each to evolve separately whilst retaining the relationship. This can be represented by this diagram:-
As has been said before, at its heart, OO is all about interfaces and here we see the just how powerful it can be in decoupling client and server software to maximise the potential for development and reuse.