Journal Articles
Browse in : |
All
> Journals
> Overload
> 40
(5)
All > Topics > Design (236) Any of these categories - All of these categories |
Note: when you create a new publication type, the articles module will automatically use the templates user-display-[publicationtype].xt and user-summary-[publicationtype].xt. If those templates do not exist when you try to preview or display a new article, you'll get this warning :-) Please place your own templates in themes/yourtheme/modules/articles . The templates will get the extension .xt there.
Title: Programming with Interfaces in C++
Author: Administrator
Date: 26 December 2000 16:46:04 +00:00 or Tue, 26 December 2000 16:46:04 +00:00
Summary:
Body:
This article grew out of an email exchange concerning an article in Bill Venners's column in the online magazine Javaworld. You can read the full article at www.javaworld.com/ javaworld/jw-12-1998/jw-12-techniques.html, though I will summarise its main points here. Venners also maintains a discussion group for his articles, and the follow-up postings can be found at www.artima.com/flexiblejava/fjf/interfaces/index.html.
The gist of the article - and by all means go read the original, because I do not want to twist Bill's words - is that Java, with its concept of interfaces as a major language feature, leads to better design and implementation than C++.
In the beginning, says Venners, he looked on Java interfaces as a halfway version of multiple inheritance. Although he was sure the Java solution was superior, he could not really say why:
"Prior to the advent of Java, I spent five years programming in C++, and in all that time I had never once used multiple inheritance. Multiple inheritance wasn't against my religion exactly, I just never encountered a C++ design situation where I felt it made sense. When I started working with Java, what first jumped out at me about interfaces was how often they were useful to me. In contrast to multiple inheritance in C++, which in five years I never used, I was using Java's interfaces all the time.
"So given how often I found interfaces useful when I began working with Java, I knew something was going on. But what, exactly? Could Java's interface be solving an inherent problem in traditional multiple inheritance? Was multiple inheritance of interface somehow intrinsically better than plain, old multiple inheritance?"
The article then launches into a detailed explanation of the 'diamond problem', which arises when two classes inherit from the same base class, and then a fourth class inherits from both of the derived classes. The strange hierarchy below relates to the movie Jurassic Park, where frog DNA was used to fill out incomplete dinosaur DNA:
abstract class Animal { abstract void talk(); } class Frog extends Animal { void talk() { System.out.println("Ribit, ribit."); } } class Dinosaur extends Animal { void talk() { System.out.println( "Oh I'm a dinosaur and I'm OK..."); } } // (This won't compile, of course - // Java only supports single // inheritance.) class Frogosaur extends Frog, Dinosaur { }
The diamond problem rears its ugly head when someone tries to invoke talk() on a Frogosaur object from an Animal reference, as in:
Animal animal = new Frogosaur(); animal.talk(); //[ambiguous call - lg]
This was the first argument that annoyed me. 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. The diamond situation rarely arises, and when it does, it should prompt a re-think of the design. But too many Java-centric articles imply that the mere existence of multiple inheritance in C++ must inevitably lead to clumsy design and kludgy implementations.
But to get back to the Venners article, he goes on to describe what he sees as the real advantage of interfaces in Java:
"As time went by I began to believe that the key insight into the interface was not so much about multiple inheritance as it was about polymorphism ... Java's interface gives you more polymorphism than you can get with singly inherited families of classes, without the 'burden' of multiple inheritance of implementation."
Polymorphism, of course, refers to the ability of any object of a derived class to function as a substitute for its parent class, and do so in an appropriate way. All Animals have certain common behaviours, although they may perform them differently - a dog will bark, a cat will meow, a hamster will squeak, and so forth. The appropriate behaviour is determined at runtime through the magic of dynamic binding, based on the actual type of the object involved.
interface Talkative { void talk(); } abstract class Animal implements Talkative { abstract public void talk(); } class Dog extends Animal { public void talk() { System.out.println("Woof!"); } } class Cat extends Animal { public void talk() { System.out.println("Meow."); } } class Interrogator { static void makeItTalk(Talkative subject) { subject.talk(); } }
"Given this set of classes and interfaces, later you can add a new class to a completely different family of classes and still pass instances of the new class to makeItTalk()."
class CuckooClock implements Talkative { public void talk() { System.out.println("Cuckoo,cuckoo!"); } } class Example4 { public static void main(String[] args) { CuckooClock cc = new CuckooClock(); Interrogator.makeItTalk(cc); } }
"With single inheritance only, you'd either have to somehow fit CuckooClock into the Animal family, or not use polymorphism. With interfaces, any class in any family can implement Talkative and be passed to makeItTalk(). This is why I say interfaces give you more polymorphism than you can get with singly inherited families of classes."
In later messages to the discussion group, Venners adds that his experience in using Java interfaces would now lead him to make greater use of abstract base classes, if he were writing C++. Sigh. So many authors refer to C++ as it was more than five years ago. They overlook that modern 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 Venners's article would look something like this:
template <class T> class Talkative { T const & t; public: Talkative(T const & obj) : t(obj) { } void talk() const { 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 specialisation:
template<> void Talkative<BigBenClock>::talk() { t.playBongs(); }
Also through specialisation, missing functionality can be added, without affecting the original class code:
template<> void Talkative<SilentClock>::talk() { cout << "tick tock" << endl; }
There is one way in which Java interfaces are somewhat more convenient than C++ template classes. You can use interfaces as formal parameters to a function, and any class which implements that interface can be passed as an argument. Because there is no implicit relationship between instantiated templates in C++, you either have to use a template function (like makeItTalk), or derive the Talkative template from a non-template base class, and use that as the parameter type. However, flexibility can be achieved 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(); makeItTalk(makeTalkative(aBigBenClock));
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.
Creating a new Java subclass to add Talkative-ness to some domain object can be impossible if most classes are declared final (as was recommended by another article in JavaWorld). If an unrelated talk() function is defined by some class in the hierarchy, overriding that function to implement the Talkative interface could break existing code. These are issues that become more important as Java is used in larger, real-world projects. The C++ template approach has the advantage that polymorphism can be attached to a class object without requiring any change to that class's code.
As an additional advantage, template classes can define member variables and functionality not related to the class of the template argument. They are like interfaces that can be attached to objects for limited purposes and periods of time. And they add no overhead to the size of the object in memory, as abstract virtual base classes would.
There are proposals going through the Java Community Process to add support for "notions of genericity based on parametric polymorphism" (translation: some form of templates) to the Java language. You can read more about this proposal at java.sun.com/aboutJava/communityprocess/jsr/jsr_014_gener.html.
Here is the example program:
// talkativ.cpp #include <iostream> using std::cout; using std::endl; // some domain objects class Dog { public: void talk() const { cout << "woof woof" << endl; } }; class CuckooClock { public: void talk() const { cout << "cuckoo cuckoo" << endl; } }; class BigBenClock { public: void talk() const { cout<<"take a long tea-break"<<endl; } void playBongs() const { 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 const & t; public: Talkative(T const & obj) : t(obj) { } void talk() const { t.talk(); } }; // specialization to adapt functionality template <> class Talkative<BigBenClock> { BigBenClock const & t; public: Talkative(BigBenClock& obj) : t(obj){} void talk() const { t.playBongs(); } }; // specialization to add missing // functionality template <> class Talkative<SilentClock> { SilentClock const & t; public: Talkative(SilentClock& obj): t(obj) {} void talk() const { 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();} // test program 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; }
and its output:
woof woof cuckoo cuckoo woof woof cuckoo cuckoo bing bong bing bong tick tock
Notes:
More fields may be available via dynamicdata ..