Here is an example which shows the use of the preceding rule. It yields a technique of wide applicability (a "design pattern"): handles.
In building our Vision library for platform-independent graphics we were faced with the problem of how to account for platform dependencies. The first solution used multiple inheritance in the following way: a typical class, such as the one describing windows, would have a parent describing the platform-independent properties of the corresponding abstraction, and another providing the platform-specific elements.
class WINDOW inherit GENERAL_WINDOW; PLATFORM_WINDOW feature ... end -- class WINDOW
Class GENERAL_WINDOW and similar ones (GENERAL_BUTTON and so on) are deferred: they express all that can be said about the corresponding graphical objects and the applicable operations without reference to a particular graphical platform. Classes such as PLATFORM_WINDOW provide the link to a graphical platform such as Windows, OS/2-Presentation-Manager or Unix-Motif; they give access to the platform-specific mechanisms (encapsulated through a library such as WEL or MEL).
A class such as WINDOW will then combine its two parents through features which effect (implement) the deferred features of GENERAL_WINDOW by using the implementation mechanisms provided by PLATFORM_WINDOW.
PLATFORM_WINDOW of course needs several variants, one for each platform. These identically named classes will be stored in different directories; the Ace for a compilation (the control file) will select the appropriate one.
This solution works, but it has the drawback of tying the notion of WINDOW closely to the chosen platform. To transpose an earlier comment about inheritance: once a Motif window, always a Motif window. This may not be too bad, as it is hard to imagine a Unix window which, suddenly seized by middle-age anxiety, decides to become an OS/2 window. The picture becomes less absurd if we expand our definition of "platform" to include formats such as Postscript or HTML; then a graphical object could change representation for purposes of printing or inclusion in a Web document.
The observation that we might need a looser connection between GUI objects such as a window and the underlying toolkit suggests trying the client relation. An inheritance link will remain, between WINDOW and GENERAL_WINDOW; but the platform dependency will be represented by a client link to a class TOOLKIT representing the graphical platform (also called a toolkit).
An interesting aspect of this solution is that it recognizes the notion of platform (toolkit) as a full-fledged abstraction, represented by a deferred class TOOLKIT. Each specific platform is then represented by an effective descendant of TOOLKIT such as MOTIF or MS_WINDOWS.
Here how it works. Each class describing graphical objects, such as WINDOW, has an attribute providing access to the underlying platform:
This will yield a field in each instance of the class. It is possible to change the handle:
set_handle (new: TOOLKIT) is -- Make new the new handle for this object. do handle := new end
A typical operation inherited from GENERAL_WINDOW in deferred form will be effected through a call to the platform's mechanism:
display is -- Display the window on the screen. do handlelwindow_display (Current) end
Through the handle, the graphical object asks the platform to perform the required operation. A feature such as window_display is deferred in class TOOLKIT and effected variously for its various descendants such as MOTIF.
Note that it would be inappropriate to draw from this example the conclusion "Aha! Another case in which inheritance was overused, and the final version stays away from it." The initial version was not wrong; in fact it works quite well, but is less flexible than the second one. And that second version fundamentally relies on inheritance and the consequent techniques of polymorphism and dynamic binding, which it combines with the client relation. Without the TOOLKIT-rooted inheritance hierarchy, the polymorphic entity handle, and dynamic binding on features such as window_display, it would not work. So far from being a rejection of inheritance, this technique illustrates a more sophisticated form of inheritance.
The handle technique
is widely applicable to the development of libraries supporting multi-platform
compatibility. Besides the Vision graphical library, we have applied it
to the Store database library, where the notion of platform covers
various SQL-based relational database interfaces such as Oracle, Ingres, Sybase