Bizarre Love Triangle In Java

Java and Smalltalk and many others have single inheritance, and it seems that, with Java anyway, single inheritance creates duplicate code by its very nature.

Imagine you have Three Classes: Alice, Bob, and Chris. Now, these three classes all share a common method with a common implementation (and for the sake of argument, let's say that it is complex and very important). What shall we call the method? Let's call it "love()".

Let's stipulate:


Now, the best I've been able to do is this:

 public abstract class Person { 
public void love(){ 
/* Everyone can love, it just isn't always easy */ 
}
 } 
 public interface Singer(){
public void sing();
 }
 public class SingHelper(){
public void sing() {
/* Singing is hard, not every one likes to do it */
}
 }
 public interface Musician(){
public void playMusic();
 }
 public class MusicHelper(){
public void playMusic(){
/* playing music takes a lot of practice */
}
 }
 public interface Dancer(){
public void dance();
 }
 public class DanceHelper(){
public void dance(){
/* all movement is dancing, but some is more imaginative */
}
 }
 public class Alice extends Person implements Singer, Dancer {
SingHelper singHelper = new SingHelper();
DanceHelper danceHelper = new DanceHelper();
public void sing(){
singHelper.sing();
}
public void dance(){
danceHelper.dance();
}
 }
 public class Bob extends Person implements Singer, Musician { 
SingHelper singHelper = new SingHelper();
MusicHelper musicHelper = new MusicHelper();
public void sing(){ 
singHelper.sing(); 
} 
public void playMusic(){
musicHelper.playMusic();
}
 }
 public class Chris extends Person implements Musician, Dancer { 
DanceHelper danceHelper = new DanceHelper(); 
MusicHelper musicHelper = new MusicHelper();
public void dance(){ 
danceHelper.dance(); 
} 
public void playMusic(){ 
musicHelper.playMusic(); 
}
 } 

Is there a more sensible way to use composition than this? This seems a little clunky to me. First, there are the separate definitions of the interfaces and the "helper" classes. Second, each implementer of the interface must duplicate the code of PassThroughMethod?s to invoke the desired helper.

Any thoughts?

-- EricHerman

Or (but using the same concept), you could do love, sing, and dance using single inheritance, and playMusic using a helper.

Perhaps I don't understand your suggestion, but the problem with that is that Chris shouldn't sing and Bob shouldn't Dance.


Why are Alice and Bob and Chris classes rather than instances of Person? Sure, some Bobs can't dance - but other Bobs can! Taking a cue from the problem domain - although not every person can dance, it is certainly the case that every person can be asked to dance, or at least whether or not they know how to dance.

It seems to me that the strategy pattern (which is quite close to the above) is appropriate here. If it is essential that it be possible to treat a person who dances as a dancer, then Person must implement the Dancer interface, and you are stuck with the duplication of delegation methods. But in many cases, it would be sufficient to be able to get a Dancer interface from a Person. (A person who cannot dance would return null, or perhaps a NullDancer, when asked for its Dancer interface.)

The strategy pattern makes it so that different Persons can dance differently; maybe Alice is a BallroomDancer and Chris is a BreakDancer.

 interface Dancer {
public bustAMove();
 }

class BallroomDancer implements Dancer { public void bustAMove() { //do a few waltz steps and take a bow } }

class BreakDancer implements Dancer { public void bustAMove() { //do a quick moonwalk } }

class NullDancer implements Dancer { public void bustAMove() { //leave in a huff } }

class Person { private Dancer d;

public Person(Dancer d) { dancer = d; }

public Dancer getDancer() { return dancer; } }

Person Alice = new Person(new BallroomDancer()); Person Bob = new Person(new NullDancer()); Person Chris = new Person(new BreakDancer());

or

 //Dancer and subclasses same as above

class Person implements Dancer { private Dancer d;

public Person(Dancer d) { dancer = d; }

public void bustAMove() { dancer.bustAMove(); } }

Person Alice = new Person(new BallroomDancer()); Person Bob = new Person(new NullDancer()); Person Chris = new Person(new BreakDancer());

I suppose it's still possible there is some domain where you really do have different classes that really do have to be themselves treated as various base classes, where the set of base classes for each of the derived classes overlaps but does not match the set of base classes for the other derived classes. But I suspect this is sufficiently rare that when it appears to be the case, there is a high probability that a different representation will be easier.


You can use InnerClasses to simulate MultipleInheritance in Java, which may or may not help in this situation. Or, you can add a getSinger() method to your Alice and Bob classes, in order to avoid excessive code duplication when the Singer interface has multiple methods. -- JaredLevy

would you be willing to show an example? It isn't obvious how this addresses EricHerman's issue.


Here's how I'd do it:

 public class Triangle {
public static void main(String[] args) {
talentShow(new SingerDancer("Alice"));
talentShow(new SingerMusician("Bob"));  
talentShow(new MusicianDancer("Chris"));
}

public static void talentShow(Person aPerson) { System.out.println("---- " + aPerson.name() + " --------"); aPerson.love(); aPerson.sing(); aPerson.dance(); aPerson.playMusic(); } }

class Person { Person( String aName, Ability aSingAbility, Ability aMusicAbility, Ability aDanceAbility ) { name = aName; singAbility = aSingAbility; musicAbility = aMusicAbility; danceAbility = aDanceAbility; }

private String name; private Ability singAbility; private Ability musicAbility; private Ability danceAbility;

public String name() { return name; }

public void love() { System.out.println("Everyone can love"); } public void sing() { singAbility.showOff(); } public void playMusic() { musicAbility.showOff(); } public void dance() { danceAbility.showOff(); } }

class Ability { public Ability(String aTask) { task = aTask; }

public void showOff() { System.out.println("Basic ability to " + task()); }

public String task() { return task; }

private String task; }

class AdvancedAbility extends Ability { AdvancedAbility(String aTask) { super(aTask); }

public void showOff() { System.out.println("Extraordinary ability to " + task()); } }

class SingerDancer extends Person { SingerDancer(String name) { super( name, new AdvancedAbility("sing"), new Ability("play music"), new AdvancedAbility("dance") ); } }

class SingerMusician extends Person { SingerMusician(String name) { super( name, new AdvancedAbility("sing"), new AdvancedAbility("play music"), new Ability("dance") ); } }

class MusicianDancer extends Person { MusicianDancer(String name) { super( name, new Ability("sing"), new AdvancedAbility("play music"), new AdvancedAbility("dance") ); } }

The "disadvantage" is that it kind of skirts the static type checking. What I mean is this: if Chris really, really couldn't sing (say he'd die if he did (or, say others would die if he did)), you'd have to pass in "null" as his "Sing ability" class, which would throw an exception on invocation. This can't be checked until run-time. On the other hand, who needs static type checking when you've got UnitTests? (Not DavidThomas: see DavidThomasOnTheBenefitsOfDynamicTyping.)

Maybe those who don't want to bother writing code validating a property that could be checked by the compiler?


Sure, there is certainly elegance to this approach. But the code doesn't feel any less redundant. Just as in the first example, there is a lot of similar code. Perhaps more. *shrug* -- EricHerman


Well, ItDepends.

Let's stipulate:

This might be better (forgive the pseudocode. :-)

 public abstract class Talent {
public void perform();
 }

public class Sing extends Talent{ public void perform() {...} ; }

public class Person { public void love(){ /* Everyone can love, it just isn't always easy */ } Talent talents[] = ... public void perform() { foreach (talents) { talent.perform(); } } }


Enlightening discussion so far. Here's what I'm taking away from it : part of the problem is in the way the original question is formulated. It is formulated (not maliciously, or so it seems to me) in such a way as to suggest a typing hierarchy, which is precisely what we'd like to demonstrate is needed - circular reasoning.

The key insight in the attempts at refactoring is introducing the notion of "talent". This will enable encapsulation, that is, moving away from specifics and into a higher level of abstraction. You won't really care that Alice or Bob can specifically sing() rather than dance(); what matters is that each Person has one or more special Talent, which you can ask them to display for your amusement. You could name the talents and look them up by name if it were really necessary to identify them.


Ah hah! I think I get it, If what one cares about is iterating over a A,B,and C and asking each to perform() then we only need to say that Alice, Bob, and Chris implement Performer. However, if what we want to have collections of Singers, Dancers, or MusicPlayers?, we need to know more. In the case of a talent show, perform() would probably be enough. But, if instead of love(), sing(), playMusic(), and dance(), we had love(), cookMushrooms(), driveCar(), and performSurgery() ... it is hard to imagine that a single perform() method would apply. So, with related, similar methods, we can potentially restructure the application to be more concerned with perform() rather than sing() and dance(). Am I getting this? -- EricHerman

In case of cookMushrooms(), driveCar(), and performSurgery(), they are Activities. So instead of perform(), we can have performActivity(). Nothing but a change of name. In my opinion, if they have to be put together in one class using multiple inheritance, it shows that they are related activities. Hence they can be solved using the above solution. i.e., the above solution still applies. -- VhIndukumar

That doesn't sit quite right with me. Let me try to express why. I'd like to have myVet.performSurgery(myPet) or depending on how bad it is, myVet.putToSleep(myPet) but I don't want myVet.perfromActivity(myPet) because performActivity() could mean anything. But, my vet is still Person, which means she can love() and is also a Mycologist which means she can findMushrooms() and cookMushrooms() but maybe she doesn't have a drivers license so she can't drive() (without performing an illegal operation <wink>). -- EricHerman


ItDepends on problem context.. For some problems, using visitor can solve problem. Sorry about the pseudocode..

 Person {
 acceptVisitor(ActivityVistor? v) {
    v.visit(this);
 }
 }

SurgeryVisitor? { visit(Person p) { // Do surgery with person's members (breaks encapsulation?) performSurgery(p.getPet()); putToSleep(p.getPet()); } }

CookingVisitor? { visit(Person p) { // Do cooking } }

DrivingVisitor? { visit(Person p) { // Drive if (!p.hasDrivingLicense()) throw IllegalOperation?(); } }
What I don't like with this approach is that it instead of telling a Dog to move, it moves each leg of the Dog. (I don't remember who said it..) It would also require to expose some members which generally you won't if it's inherited. But you can allways make Visitors your friend ;-) -- VhIndukumar


I am still of the opinion that multiple inheritance would result in less similar/duplicate code and a potentially more clear design. This code sample is written using the FauxMultipleInheritanceJava language:

 public class Person {
        public void love(){
           /******/
        }
 }
 public class Singer {
        public void sing(){
           /******/
        }
 }
 public class Dancer {
        public void dance() {
           /******/
        }
 }
 public class Musician {
        public void playMusic() {
            /*****/
        }
 }
 public class Alice extends Person, Singer, Dancer {
 }
 public class Bob extends Person, Singer, Musician {
 } 
 public class Alice extends Person, Dancer, Musician {
 } 

This seems simple and clear as well as being less code. Granted, MultipleInheritance has its own set of drawbacks, complexities, and head-aches ... but it still seems to me that SingleInheritance trades some of those in exchange for different complexities and often more duplicate/similar code ... sometimes less clear code. Does that make sense? -- EricHerman

Yes, but what happens if I call Alice.dip() ?


Even with MultipleInheritance there is the CoreTheApples problem

That's life. There is always a need to find a balance between storing different things in the same bag and splitting similar things into lots of bags. For example, do I keep track of managers and staff separately and combine when needed, or do I keep track of them collectively and separate when needed?

OO allows us to say that, for this purpose, these things are interchangeable if we want.

eg.

 sub TalentShow? { foreach (@_){ $_->perform(); } }

It also allows us to be more specific if we want. But if we want to be specific then we need to either keep track of objects of different types separately, or be prepared to work it out as needed.

eg.

 sub DanceContest? { foreach (@_){ $_->dance(); } }  # responsibility on caller to provide correct arguments.

sub DanceContest? { foreach (@_){ $_->dance() if $_::ISA{Dancer}; } } # responsibility locally

And if we sometimes want to treat them as the same and sometimes want to treat them as different, then, well, TANSTAAFL.

If you focus on what you are trying to achieve rather than how you are trying to do it, you will succeed.

For example, "I spent 10 minutes looking for the torch so I could cut some wood. I didn't have enough light to find the torch so I turned on the bike's headlight. After 1/2 an hour, I still couldn't find the torch." (paraphrased from ZenAndTheArtOfMotorcycleMaintenance).

If you want a Talent show, implement talents. If you want a dance show, implement dancers. If you don't know what you want, the problem is not with the language.


I had a similar problem recently in J2ME - additional forces were:

  1. the analogues to sing(), dance(), playMusic() had the same implementation for each Person, but love() did not (or at least, I eventually refactored things such that this was the case).

  2. Being a J2ME program, I couldn't afford the (space) overhead of a bunch of interfaces and what not.

  3. The Persons were Strategy objects anyway, so I rather wanted them to have the same interface anyway, and didn't mind tight coupling to their... er... talent-show.

So I ended up handling it by ExternalPolymorphism. Not exactly my proudest moment. For the provided example it would probably run something like this:

 class Person {
   public int talents; // Sorry! Imagine this dressed up a little more nicely if you must
   public void love();
 }

class Alice extends Person { public Alice() { talents = SINGING | DANCING; } public void love() { // Alice is pretty typical in this regard, no-frills. } }

class Bob extends Person { public Bob() { talents = SINGING | PLAYING; } public void love() { // Bob is a hopeless romantic. } }

class Carol extends Person { public Carol() { talents = DANCING | PLAYING; } public void love() { // Carol likes it rough! } }

class TalentShow? { private Person[] persons; public TalentShow?() { persons = new Person[] { new Alice(), new Bob(), new Carol() }; public void runTheShow() { for (int i = 0; i < persons.length; i++) { Person p = persons[i]; p.love(); if (p.talents & DANCING) { // dance } if (p.talents & SINGING) { // sing } if (p.talents & PLAYING) { // play music } } } }


AutoDelegation? would help with this problem.


Not a solution but doesnt need helper classes and therefor reduces the codesize:

public interface Dancer {

  public void dance();
}

public interface Singer {

  public void sing();
}

public class Person {

  public void singNow()
  {
    System.out.println("sing!");
  }
  public void danceNow()
  {
    System.out.println("dance!");
  }
  public void love()
  {
    System.out.println("make love not war!");
  }
}

public class Alice extends Person implements Singer {

  public void sing()
  {
    singNow();
  }
}


Notice that the contributions to this discussion start having some substance when the author assumes a use-case or two? Of course, the resolution is different for the different use-case assumptions.

The problem with the initial statement of the problem was that it failed to supply the one most essential factor: What do you want to do with it? Once you have the use cases, the design tends to be straightforward. -- MarcThibault


CategoryJava


EditText of this page (last edited December 2, 2005) or FindPage with title or text search