The Artima Developer Community
Sponsored Link

Designing with Interfaces - (Read this one, skip next one)

Advertisement

Advertisement

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:

Sponsored Links



Google
  Web Artima.com   
Copyright © 1996-2009 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use - Advertise with Us