C++ Common Knowledge: Polymorphism
The topic of polymorphism is given mystical status in some programming texts and is ignored in others, but it's a simple, useful concept that the C++ language supports. According to the standard, a "polymorphic type" is a class type that has a virtual function. From the design perspective, a "polymorphic object" is an object with more than one type, and a "polymorphic base class" is a base class that is designed for use by polymorphic objects.
Consider a type of financial option, AmOption, as shown in Figure 1.
Figure 1 Polymorphic leveraging in a financial option hierarchy. An American option has four types.
An AmOption object has four types: It is simultaneously an AmOption, an Option, a Deal, and a Priceable. Because a type is a set of operations (see Data Abstraction [1, 1] and Capability Queries [27, 93]), an AmOption object can be manipulated through any one of its four interfaces. This means that an AmOption object can be manipulated by code that is written to the Deal, Priceable, and Option interfaces, thereby allowing the implementation of AmOption to leverage and reuse all that code. For a polymorphic type such as AmOption, the most important things inherited from its base classes are their interfaces, not their implementations. In fact, it's not uncommon, and is often desirable, for a base class to consist of nothing but interface (see Capability Queries [27, 93]).
Of course, there's a catch. For this leveraging to work, a properly designed polymorphic class must be substitutable for each of its base classes. In other words, if generic code written to the Option interface gets an AmOption object, that object had better behave like an Option!
This is not to say that an AmOption should behave identically to an Option. (For one thing, it may be the case that many of the Option base class's operations are pure virtual functions with no implementation.) Rather, it's profitable to think of a polymorphic base class like Option as a contract. The base class makes certain promises to users of its interface; these include firm syntactic promises that certain member functions can be called with certain types of arguments and less easily verifiable semantic promises concerning what will actually occur when a particular member function is called. Concrete derived classes like AmOption and EurOption are subcontractors that implement the contract Option has established with its clients, as shown in Figure 2.
Figure 2 A polymorphic contractor and its subcontractors. The Option base class specifies a contract.
For example, if Option has a pure virtual price member function that gives the present value of the Option, both AmOption and EurOption must implement this function. It obviously won't implement identical behavior for these two types of Option, but it should calculate and return a price, not make a telephone call or print a file.
On the other hand, if I were to call the price function of two different interfaces to the same object, I'd better get the same result. Essentially, either call should bind to the same function:
AmOption *d = new AmOption; Option *b = d; d->price(); // if this calls AmOption::price... b->price(); // ...so should this!
This makes sense. (It's surprising how much of advanced object-oriented programming is basic common sense surrounded by impenetrable syntax.) If I were to ask you, "What's the present value of that American option?" I'd expect to receive the same answer if I'd phrased my question as, "What's the present value of that option?"
The same reasoning applies, of course, to an object's nonvirtual functions:
b->update(); // if this calls Option::update... d->update(); // ...so should this!
The contract provided by the base class is what allows the "polymorphic" code written to the base class interface to work with specific options while promoting healthful ignorance of their existence. In other words, the polymorphic code may be manipulating AmOption and EurOption objects, but as far as it's concerned they're all just Options. Various concrete Option types can be added and removed without affecting the generic code that is aware only of the Option base class. If an AsianOption shows up at some point, the polymorphic code that knows only about Options will be able to manipulate it in blissful ignorance of its specific type, and if it should later disappear, it won't be missed.
By the same token, concrete option types such as AmOption and EurOption need to be aware only of the base classes whose contracts they implement and are independent of changes to the generic code. In principle, the base class can be ignorant of everything but itself. From a practical perspective, the design of its interface will take into account the requirements of its anticipated users, and it should be designed in such a way that derived classes can easily deduce and implement its contract (see Template Method [22, 77]). However, a base class should have no specific knowledge of any of the classes derived from it, because such knowledge inevitably makes it difficult to add or remove derived classes in the hierarchy.
In object-oriented design, as in life, ignorance is bliss (see also Virtual Constructors and Prototype [29, 99] and Factory Method [30, 103]).