This page contains an archived post to the Design Forum (formerly called the Flexible Java Forum) made prior to February 25, 2002.
If you wish to participate in discussions, please visit the new
Artima Forums.
Message:
Designing with Interfaces - (Read this one, skip next one)
Posted by Lois Goldthwaite on 02 Dec 1998, 8:35 PM
Due to a former "feature" of my CGI script, my previous attempt to post Lois's e-mail lost a lot of open and close angle brackets. So I am trying again (with a slightly altered CGI script). I received this e-mail from Lois Goldthwaite and thought all of you who read the forum might be interested. I'm posting it here (again) with her permission. - Bill Venners Dear Bill, I enjoyed reading your article about how Java interfaces provide "more polymorphism" for designs. And I would agree that multiple inheritance in C++, even with completely abstract base classes to avoid the "burden" of multiple inheritance of implementation, is a feature best used sparingly. But I think Java devotees make too much of the dreaded diamond inheritance problem. Multiple inheritance is an elegant expression of a situation where a class logically inherits features from two different domains. Multiple inheritance from classes in the same domain should probably prompt a re-think of the design. But I am not writing you to defend the necessity for support of multiple inheritance in an OO language; after all, Smalltalk manages without it also. Instead, I think you should have pointed out that C++ has an excellent mechanism for providing non-inheritance-based polymorphism, through the use of templates. The C++ idiom to express the Talkative interface discussed in your article would look something like this: template <class T> class Talkative { T& t; public: Talkative( T& obj ) : t( obj ) { } void talk() { t.talk(); } };
This template enables any class which defines a talk() method to be used where a Talkative type is needed: Talkative<Dog> td( aDog ); td.talk();Talkative<CuckooClock> tcc( aCuckooClock ); tcc.talk();
Better yet, even classes which *don't* have a talk() method, but which provide equivalent functionality, can be made Talkative through user-defined specialization: template<> void Talkative<BigBenClock>::talk() { t.playBongs(); }
Also through specialization, missing functionality can be added, without affecting the original class code: template<> void Talkative<SilentClock>::talk() { cout << "tick tock" << endl; }
The Java approach of using an interface type as the type of a function argument to achieve flexible, non-inheritance-based polymorphism can be modeled by relying on the C++ compiler's ability to deduce template arguments from an appropriate adapter function: template <class T> void makeItTalk( Talkative<T> t ) { t.talk(); }template <class T> Talkative<T> makeTalkative( T& obj ) { return Talkative<T>( obj ); }
Thus: makeTalkative( aDog ).talk(); makeTalkative( aCuckooClock ).talk(); makeItTalk( makeTalkative( aBigBenClock ) );
and so forth. A small complete program is appended to the end of this message. It compiles with Visual C++ 5.0 and Borland C++ 5.02 and produces the following output: woof woof cuckoo cuckoo woof woof cuckoo cuckoo bing bong bing bong tick tock You could argue that adding "implements Talkative" to the class definition is useful for documentation purposes. But you could also argue that it is intrusive on the design of domain classes, and modifying source code which you do not "own" or which others share is sometimes undesirable or impossible for various reasons. The C++ template approach has the advantage that run-time polymorphism can be attached to a class object without requiring any change to that class's code. Even creating a new Java subclass to add Talkative-ness to some domain object can be impossible if most classes are declared final (as is recommended by another article in JavaWorld). If an unrelated (possibly unnoticed because private) talk() function is defined by some class in the hierarchy, overriding that function to implement the Talkative interface could break existing code. And there is also the political issue of whether the Talkative subclass should be a member of the same package as its base, or of some other package which you "own." This are issues which become more important as Java is used in larger, real-world projects. To drive into the ground the argument that C++ templates provide even more flexibility than Java interfaces, template classes can also define member variables and additional functionality not related to the class of the template argument. They are like interfaces which can be attached to classes for limited purposes and periods of time. I'm writing to you not to be critical of Java or to start a flame war over religious issues, but simply to point out that there are mechanisms in C++ which work differently from Java, but still work. I think it's more interesting and educational to study the differences between the best mechanisms of different languages, than to look at comparisons where one of the elements is artificial or unidiomatic. Thanks for your attention, Lois ------------------------------------------------------------------- Here's the complete program: // talkativ.cpp#include <iostream> using std::cout; using std::endl; // some domain objects class Dog { public: void talk() { cout << "woof woof" << endl; } }; class CuckooClock { public: void talk() { cout << "cuckoo cuckoo" << endl; } }; class BigBenClock { public: void talk() { cout << "take a long tea-break" << endl; } void playBongs() { cout << "bing bong bing bong" << endl; } }; class SilentClock { // doesn't talk }; // generic template to provide non-inheritance-based polymorphism
template <class T> class Talkative { T& t; public: Talkative( T& obj ) : t( obj ) { } void talk() { t.talk(); } }; // specialization to adapt functionality template <> class Talkative<BigBenClock> { BigBenClock& t; public: Talkative( BigBenClock& obj ) : t( obj ) { } void talk() { t.playBongs(); } }; // specialization to add missing functionality template <> class Talkative<SilentClock> { SilentClock& t; public: Talkative( SilentClock& obj ) : t( obj ) { } void talk() { cout << "tick tock" << endl; } }; // adapter function to simplify syntax in usage template <class T> Talkative<T> makeTalkative( T& obj ) { return Talkative<T>( obj ); } // function to use an object which implements the // Talkative interface, C++ style template <class T> void makeItTalk( Talkative<T> t ) { t.talk(); } int main() { Dog aDog; CuckooClock aCuckooClock; BigBenClock aBigBenClock; SilentClock aSilentClock; Talkative<Dog> td( aDog ); td.talk(); Talkative<CuckooClock> tcc( aCuckooClock ); tcc.talk(); makeTalkative( aDog ).talk(); makeTalkative( aCuckooClock ).talk(); makeItTalk( makeTalkative( aBigBenClock ) ); makeItTalk( makeTalkative( aSilentClock ) ); return 0; }
Replies:
|