Download "diamond problem" Interfaces, multiple inheritance, no problem in

Survey
yes no Was this document useful for you?
   Thank you for your participation!

* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project

Document related concepts

Polymorphism (biology) wikipedia , lookup

Transcript
"diamond problem" Interfaces, multiple inheritance, no problem in JAVA
One justification of interfaces that I had heard early on was that they solved the "diamond problem" of traditional multiple
inheritance.
The diamond problem is an ambiguity that can occur when a class multiply inherits from two classes that both descend
from a common superclass.
For example, in Michael Crichton's novel Jurassic Park, scientists combine dinosaur DNA with DNA from modern frogs to get
an animal that resembled a dinosaur but in some ways acted like a frog. At the end of the novel, the heros of the story
stumble on dinosaur eggs. The dinosaurs, which were all created female to prevent fraternization in the wild, were
reproducing. Chrichton attributed this miracle of love to the snippets of frog DNA the scientists had used to fill in missing
pieces of the dinosaur DNA. In frog populations dominated by one sex, Chrichton says, some frogs of the dominant sex may
spontaneously change their sex. (Although this seems like a good thing for the survival of the frog species, it must be terribly
confusing for the individual frogs involved.) The dinosaurs in Jurassic Park had inadvertently inherited this spontaneous sexchange behavior from their frog ancestry, with tragic consequences.
This Jurassic Park scenario potentially could be represented by the following inheritance hierarchy:
Java =
Multiple Interface
Multiple Inheritance
YES
NO
Figure 1. Multiple inheritance in Jurassic Park
The diamond problem can arise in inheritance hierarchies like the one shown in Figure 1. In fact, the diamond problem
gets its name from the diamond shape of such an inheritance hierarchy.
One way the diamond problem can arise in the Jurassic Park hierarchy is if both Dinosaur and Frog, but not Frogosaur,
override a method declared in Animal. Here's what the code might look like if Java supported traditional multiple
inheritance:
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, because
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();
Because of the ambiguity caused by the diamond problem, it isn't clear whether the runtime system should invoke Frog's
or Dinosaur's implementation of talk(). Will a Frogosaur croak "Ribbit, Ribbit." or sing "Oh, I'm a dinosaur and I'm
okay..."?
The diamond problem would also arise if Animal had declared a public instance variable, which Frogosaur would then have
inherited from both Dinosaur and Frog. When referring to this variable in a Frogosaur object, which copy of the variable -Frog's or Dinosaur's -- would be selected? Or, perhaps, would there be only one copy of the variable in a Frogosaur object?
In Java, interfaces solve all these ambiguities caused by the diamond problem. Through interfaces, Java allows
multiple inheritance of interface but not of implementation. Implementation, which includes instance variables and
method implementations, is always singly inherited. As a result, confusion will never arise in Java over which inherited
instance variable or method implementation to use.
Interfaces and polymorphism
In my quest to understand the interface, the diamond problem explanation made some sense to me, but it didn't really
satisfy me. Sure, the interface represented Java's way of dealing with the diamond problem, but was that the key insight
into the interface? And how did this explanation help me understand how to use interfaces in my programs and
designs?
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 (see the explanation of this term below). The interface lets you take greater advantage of
polymorphism in your designs, which in turn helps you make your software more flexible.
Ultimately, I decided that the "point" of the interface was:
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
Polymorphism means using a superclass variable to refer to a subclass object. For example, consider this simple
inheritance hierarchy and code:
abstract class Animal {
abstract void talk();
}
class Dog extends Animal {
void talk() {
System.out.println("Woof!");
}
}
class Cat extends Animal {
void talk() {
System.out.println("Meow.");
}
}
Given this inheritance hierarchy, polymorphism allows you to hold a reference to a Dog object in a variable of type Animal,
as in:
Animal animal = new Dog();
The word polymorphism is based on Greek roots that mean "many shapes." Here, a class has many forms: that of the
class and any of its subclasses. An Animal, for example, can look like a Dog or a Cat or any other subclass of Animal.
Polymorphism in Java is made possible by dynamic binding, the mechanism by which the Java virtual machine (JVM)
selects a method implementation to invoke based on the method descriptor (the method's name and the number and
types of its arguments) and the class of the object upon which the method was invoked. For example, the
makeItTalk() method shown below accepts an Animal reference as a parameter and invokes talk() on that
reference:
class Interrogator {
static void makeItTalk(Animal subject) {
subject.talk();
}
}
At compile time, the compiler doesn't know exactly which class of object will be passed to makeItTalk() at runtime. It
only knows that the object will be some subclass of Animal. Furthermore, the compiler doesn't know exactly which
implementation of talk() should be invoked at runtime.
As mentioned above, dynamic binding means the JVM will decide at runtime which method to invoke based on the class
of the object. If the object is a Dog, the JVM will invoke Dog's implementation of the method, which says, "Woof!". If the
object is a Cat, the JVM will invoke Cat's implementation of the method, which says, "Meow!". Dynamic binding is the
mechanism that makes polymorphism, the "subsitutability" of a subclass for a superclass, possible.
Polymorphism helps make programs more flexible, because at some future time, you can add another subclass to the
Animal family, and the makeItTalk() method will still work. If, for example, you later add a Bird class:
class Bird extends Animal {
void talk() {
System.out.println("Tweet, tweet!");
}
}
you can pass a
Bird
object to the unchanged makeItTalk() method, and it will say, "Tweet,
tweet!".
Getting more polymorphism
Interfaces give you more polymorphism than singly inherited families of classes, because with interfaces you don't
have to make everything fit into one family of classes. For example:
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(). For example, imagine you add a new CuckooClock class to an
already existing Clock family:
class Clock {
}
class CuckooClock implements Talkative {
public void talk() {
System.out.println("Cuckoo, cuckoo!");
}
}
Because CuckooClock implements the Talkative interface, you can pass a CuckooClock object to the makeItTalk()
method:
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.
The 'burden' of implementation inheritance
Okay, my "more polymorphism" claim above is fairly straightforward and was probably obvious to many readers, but what
do I mean by, "without the burden of multiple inheritance of implementation?" In particular, exactly how is multiple
inheritance of implementation a burden?
As I see it, the burden of multiple inheritance of implementation is basically inflexibility. And this inflexibility maps directly
to the inflexibility of inheritance as compared to composition.
By composition, I simply mean using instance variables that are references to other objects. For example, in the following
code, class Apple is related to class Fruit by composition, because Apple has an instance variable that holds a reference
to a Fruit object:
class Fruit {
//...
}
class Apple {
private Fruit fruit = new Fruit();
//...
}
In this example, Apple is what I call the front-end class and Fruit is what I call the back-end class. In a composition
relationship, the front-end class holds a reference in one of its instance variables to a back-end class.