The problem
A problem especially exacerbated in Java by inversion of control (IoC) is composition or delegation v. inheritance. In C++, this is a non-issue given features such as multiple inheritance, private inheritance and using declarations. However, Java has no such help. Just look at this code:
public interface Commuter { public void walkToWork(); public void driveToWork(); public void flyToWork(); }
And yet another interface:
public interface Parent { public void produceChildren(); public void feedChildren(); public void disciplineChildren(); public void teachChildren(); }
And finally a concrete class:
public class Dad implements Commuter, Parent { private Commuter commuter; private Parent parent; public Dad(final Commuter commuter, final Parent parent) { this.commuter = commuter; this.parent = parent; } public void walkToWork() { commuter.walkToWork(); } public void driveToWork() { commuter.driveToWork(); } public void flyToWork() { commuter.flyToWork(); } public void produceChildren() { parent.produceChildren();
Hey, wait! You didn't finish! you cry. You are right; I got bored with all that redundant typing. This is the problem with delegation in Java — the language support sucks and it's all hand-made. What Java needs is proper delegation support.
In C++ it looks like this:
class Dad : public HoustonCommuter, public GoodParent { };
That's it! (Given suitable implementations of HoustonCommuter
and GoodParent
.) Even better, just like in Java you can change the implementation:
template<typename A, typename B> class CommutingParent : public A, public B { }; typedef CommutingParent<HoustonCommuter, GoodParent> Dad;
If anything, it's now too amazing as you can construct a Dad
from the wrong base classes, although latent typing keeps this problem theoretical. (However, note the C++ FAQ on private inheritance as a counter.)
What's to be done?
The solution
The solution is to change Java, of course. :-) I don't suggest C#'s delegate
approach, and introducing new keywords is bad policy. What I suggest is both simpler and more expressive, and also more flexible. Let's turn back to the Commuter
/Parent
example. Try your mind on this:
class Dad implements Commuter, Parent { public Dad(final Commuter commuter, final Parent parent) { this = commuter; this = parent; } }
What's going on here? I'm teaching the Java compiler to take meaning from an otherwise illegal statement: assignment to this
. I wave my compiler-writer wand and declare that when a class implements an interface, assignment to this
implements that interface with the object assigned (the RHS) provided that the RHS implements the interface.
Internally, I'd teach the compiler for this example to add a hidden Commuter __commuter
instance member to Dad
, assign to that member, and add the forwarding methods to Dad
automatically. I'd expect reflection to work as expected and make all this clear, just as if I'd had coded it all by hand. No magic secrets, please.
More details
If Dad
directly implements any of the methods in Commuter
or Parent
, those let the compiler off the hook and it generates no silent methods. That is:
public class Dad implements Commuter, Parent { public Dad(final Commuter commuter, final Parent parent) { this = commuter; this = parent; } public void teachChildren() { teachSinging(); } private void teachSinging() { System.out.println("Mir ist so wunderbar!"); } }
This implementation of Dad
should provide silent methods for everything except void teachChildren()
. Note that this implies you can implement portions of an interface as well:
public interface CommutingParent extends Commuting, Parent { } public class Dad implements CommutingParent { public Dad(final Commuter commuter) { this = commuter; } }
Now Dad
will not compile until it implements the methods in Parent
. However this leads to an interesting question: what about ambiguities?
public class Dad implements CommutingParent { public Dad(final Commuter commuter, final CommutingParent commutingParent) { this = commuter; this = commutingParent } }
Dad
will not compile with the compiler complaining about an ambiguity: which delegate implements Commuter
, commuter or commutingParent? The simple approach is best to fix this error:
public class Dad implements CommutingParent { public Dad(final Commuter commuter, final CommutingParent commutingParent) { this = commuter; this = (Parent) commutingParent; } }
Here the (Parent)
cast extracts the just the Parent
implementation from commutingParent and ignores the Commuter
implementation.
A final problem
Back to the earlier example:
public class Dad implements Commuter, Parent { public Dad(final Commuter commuter, final Parent parent) { this = commuter; this = parent; } public void teachChildren() { teachSinging(); } private void teachSinging() { System.out.println("Mir ist so wunderbar!"); } }
Notice anything funny? Contrast with this:
public class Dad extends HoustonCommuter, GoodParent { public void teachChildren() { super.teachSinging(); teachSinging(); } private void teachSinging() { System.out.println("Mir ist so wunderbar!"); } }
Ok, we call super.teachChildren()
before extending the behavior of void teachChildren()
so that we add to instead of replace existing behavior. How can I accomplish that now with delegation? Simple, write the exact same code! Since I'm giving meaning to assignment to this
, I feel free to extend the meaning of calls through super
. The compiler will take this as a request to inline the code for the silent delegation method. It will still not generate the silent method as we need reflection to find the our own code, not the silent method.
The only remaining problem is (ok, so the preceding problem was the penultimate one, not the final one):
public class BeetleDriver implements Driver { public void driveToWork() { System.out.println("Baby you can drive my car!"); } }
And:
public class Dad extends BeetleDriver implements CommutingParent { public Dad(final CommutingParent commutingParent) { this = commutingParent; } }
Who implements void driveToWork()
? It's another ambiguity error and the class will not compile. Back to explicit delegation:
public class Dad extends BeetleDriver implements CommutingParent { public Dad(final CommutingParent commutingParent) { this = commutingParent; } public void driveToWork() { super.driveToWork(); } }
This is just like the solution above for super.teachChildren()
.
3 comments:
I know it's just an example to prove your point but I think this is pretty bad design.
I.e. If you look at Peter Coad's work on the Domain Neutral Component, it is much more flexible to create specialised Role classes for things like Commuter & Dad. This has the advantage of being able to add more roles later on without the knowledge of the Person or Organisation etc.
E.g. (with generics because it is clearer what my intent is)
class Party {
}
class Person extends Party {
public <T extends PartyRole> getRole(Class<T extends PartyRole<Person>>);
}
class PartyRole<P extends Party> {
public P getParty(){ ... }
}
class Commuter extends PartyRole<Person> {
public void walkToWork();
public void driveToWork();
public void flyToWork();
}
etc.
Similarly, you could map Dad this way.
This also lends well to an O-R mapping situation.
-- Distiller
Distiller -- which part of the design do you dislike? (Yes, the examples range widely, so I'm not sure which you are spcificatlly pointing out.) I'm wanting a cleaner IoC/delegation solution, one blessed by the language syntax. (I'd like the same cleanness for properties, but I'm unsure how to get it without new keywords.)
The point about using policy classes is great. It's one of my favorite techniques. Look at the Loki smart pointer classes for an excellent example in C++ of how much flexibility and power the idiom lends.
What I don't like is the static implementation of interfaces by the Person. This is inflexible and ultimately unmanagable when you have lots of interfaces to implement. You're better off doing it the otherway around I.e like I said, have a role class and put specific behaviour / attributes in that.
Post a Comment