IntroductionInheritance is often misused. For example, many web frameworks require subtypes of HttpServlet all over the place, which forces you to deal with the constructor of HttpServlet to test your classes. It's also bad because it breaks encapsulation of the superclass, so the author of the subclass can run into lots of problems. (TODO: examples from Effective Java) The protected API can have extra thorns that the public API doesn't - although you can still break things even with composition using the public API. The general guidance here is Favor Composition over Inheritance. I think the language can make this the only way. Our goal is to provide the same subtyping advantage of inheritance without coupling the particular supertype. DetailsThe real value of a subtype is polymorphism. Animal getAnimal() {
return new Cat();
}
Animal c = new Cat();Ok, so you decide to create a Dog class. You need to be able to assign an Animal reference to point to an instance of Dog. But, we don't want you to subclass Animal. Instead, use composition, and delegate to an instance of Animal. class Dog(Animal delegate) {
String name() {
return delegate.name();
}
@Override
Int numberOfLegs() {
return 4;
}
}
Animal dog = new Dog(); // doesn't work!!!The problem with plain composition is that Dog is not an instance of Animal. The answer in Java is to have an Interface as the common ancestor of both. But if Animal is a class outside your control, you're in trouble, as you cannot make it implement a new interface. Another problem is if the delegate has 30 public methods, then you have a long class that just delegates all 30 methods, minus whatever different behavior you want. You're required to have all of them, to honor the polymorphism. So, here's a possible syntax: class Dog(Animal delegate) composes delegate {
Int numberOfLegs() {
return 4;
}
}
Animal dog = new Dog(); // works!The composes keyword indicates that this class is now a subtype of Animal, however, we didn't inherit from Animal. And all the public methods of Animal are now implemented in Dog for free as straight pass-throughs. We can then override them as we like, and use the delegate reference. The delegate is provided like any other constructor parameter. We essentially did this: - created an anonymous interface that matches the public API of Animal
- made Animal implement that interface, it already has the implementation
- made Dog implement that interface with a bunch of delegation
- client code still sees Animal as a concrete type
- method dispatch on an Animal reference still checks for polymorphic subtypes
As the best advantage, take the HttpServlet example: class MyServlet(HttpServlet hs) {
}
MyServlet forTesting = new MyServlet(new MockHttpServlet());Alternate SyntaxI'm liking this way more and more: class Circle(Int i) => Shape s {
}
Shape delegate = new Shape();
Shape c = new Circle(i) => delegate;Strong Delegate conceptan alternative is to use a delegate notion with strong language support. Essentially the keyword "delegate" provides the compiler with a hint to expand the API of the class to include the methods on the delegate, and the delegating class can be passed around as the type of the delegate (without having to formally declare its interface). For example:
public class Foo {
public String doFoo() { return "foo"; }
}
public class Delegator(delegate Foo foo) {}
// calling code can then do the following
...
Foo foo = new Foo();
Delegator d = new Delegator(foo);
assertEquals("foo", d.doFoo());
// and Delegator is assignable to a Foo variable
Foo otherFoo = d;
The class is free to implement it's own form of the method, and can always call the delegate's implementation directly like so:
public class Foo {
public String doFoo() { return "foo"; }
}
public class Delegator(delegate Foo foo) {
public String doFoo() { return foo.doFoo() + "blah"; }
}
// calling code can then do the following
...
Foo foo = new Foo();
Delegator d = new Delegator(foo);
assertEquals("fooblah", d.doFoo());This can be done with multiple delegates, which would each contribute their API to the delegating object. In the case where the two or more delegates have no common API, there is no ambiguity, and the obvious semantic applies (methods on Foo go to that delegate, methods on Bar go to that delegate). However, if there is a union between the set of methods/properties comprising the API, then the common methods would need to be disambiguated by means of a method delegation syntax. The following example would not compile: public class Foo {
public String doFoo() { return "foo"; }
public String doCommon() { return "commonFoo"; }
}
public class Bar {
public String doBar() { return "bar"; }
public String doCommon() { return "commonBar"; }
}
public class Delegator(delegate Foo foo, delegate Bar bar) {}This would require disambiguation. The compiler would disallow it until the following hint was provided: public class Delegator(delegate Foo foo, delegate Bar bar) {
public String doCommon() delegate foo;
}
// The calling code would therefore invoke doCommon on foo
Foo foo = new Foo();
Bar bar = new Bar();
Delegator d = new Delegator(foo, bar);
assertEquals("commonFoo", d.doCommon());Note that this is instance delegation, not implementation inheritance. So two delegates of the same type could be provided like so: public class Foo(String crazy) {
public String processCrazy() { return crazy; }
public String toUpperCase() { return crazy.toUpperCase(); }
}
public class Delegator(delegate Foo foo1, delegate Foo foo2) {
public String processCrazy() delegate foo1;
public String toUpperCase() delegate foo2;
}
...
Foo foo1 = new Foo("Foo1");
Foo foo2 = new Foo("Foo2");
Delegator d = new Delegator(foo1, foo2);
assertEquals("Foo1", d.processCrazy());
assertEquals("FOO2", d.processCrazy());This can be used to simulate inheritance, but is specifically instance-based, and can therefore be used to implement delegate patterns using extremely terse form, and with minimal boilerplate. Sometimes the user might want to expose a small subset of methods belonging to a member object as methods of the containing object without simulating inheritance. One solution might be to allow using the above "delgateto" syntax on injected objects that are not qualified with the "delegate" keyword. See below: public class Foo {
public void doOneThing() { /*...*/ }
public void doAnotherThing() { /*...*/ }
public void doManyMoreThings() { /*...*/ }
}
public class Delegator(Foo foo) { // notice the "delegate" keyword is missing from the injected parameter list
public void doOneThing() delegate foo;
}
// Delegator forwards calls to doOneThing() to its foo member. Delegator is not a Foo.
Foo foo = new Foo();
Delegator d = new Delegator(foo);
d.doOneThing(); // invokes foo.doOneThing()
d.doAnotherThing(); // compiler error
d.doManyMoreThings(); // compiler error
Foo otherFoo = d; // compiler errorThus, the "delegateto" method syntax could remove boilerplate code for simple wrappers that only expose proper subsets of the wrapped object's public methods. Modification to delegate keyword to allow for polymorphic state machines// Type definitions
interface Calculator {
Double calculate(Double left, Double right);
}
class Add() implements Calculator {
Double calculate(Double left, Double right) {
return left + right;
}
}
class Subtract() implements Calculator {
Double calculate(Double left, Double right) {
return left - right;
}
}
class Multiply() implements Calculator {
Double calculate(Double left, Double right) {
return left * right;
}
}
class Divide() implements Calculator {
Double calculate(Double left, Double right) {
return left / right;
}
}
enum State {
ADD,
SUBTRACT,
MULTIPLY,
DIVIDE
}
class StatefulCalculator(
Calculator begin as delegate state, Map<String, Calculator> space) {
void setMode(State mode) {
state = space.get(mode);
}
}
// Usage
Map<String, Calculator> space = HashMap.new();
Calculator begin = Add.new();
space.put(State.ADD, begin);
space.put(State.SUBTRACT, Subtract.new());
space.put(State.MULTIPLY, Multiply.new());
space.put(State.DIVIDE, Divide.new());
StatefulCalculator calculator = new StatefulCalculator(begin, space);
assertEquals(4, calculator.calculate(2, 2));
calculator.setMode(State.SUBTRACT);
assertEquals(0, calculator.calculate(2, 2));
calculator.setMode(State.MULTIPLY);
assertEquals(6, calculator.calculate(2, 3));
calculator.setMode(State.DIVIDE);
assertEquals(5, calculator.calculate(10, 2));
// Also
Calculator restrictedView = calculator;
restrictedView.calculate(5, 6) // polymorphic depending upon the state of the StatefulCalculator
// The above example is very simple, but essentially you could pass an arbitrary directed
// graph in the map representing the state space, and have state transitions depend upon
// input combined with the current hidden state.
// I feel that this pattern, enabled by the new "as delegate" keyword syntax, will promote
// polymorphism over switch/case/if statement hell. -robertsdionne
|
Christian, your new section is similar to what I had in mind, let me try to distill the differences.
I like requiring disambiguation is there is method overlap.
In multiple delegation, how would you handle things like isEqual and hashCode that would always overlap?
This approach would also take care of mixins since you can use this to add methods (and even instance variables without worrying about conflicts).
The more I think about it the more I like it.
@aeagle - yes to multiple -that's the second part of the example. We thought of the "both execute" case, but if there are return values, then how do you merge them? And if there's a parameter, then do you execute them in an order or concurrently. If ordered, which order? If concurrently, then how do you guarantee that the parameters are thread-safe. it's too ambiguous, so we pitched that and simply required disambiguation. Probably the disambiguation syntax should use the "delegatesto" keyword, instead of overloading "delegate"
As to supertype declaration, the auto-documentation would show it as declaring the APIs of Foo and Bar (for the example above) in a Liskov style substitutability.
@gabrielh - if the default isEqual() and hashCode() are inherited from the root object, then by definition the Delegator class (above) has an implementation that's "closer" than the delegates' impl. Therefore it takes precedence, unless you explicitly specify a "delegatesto" item.
And as to taking care of mixins - heck yeah! Hadn't thought of that, but yeah.
I still need to deal with null delegates. It's quite possible that you declare a delgate property (havne't finished that part of the proposal) which is readable (for, say, a UI framework where you want to allow easy controller delegates to UI classes). So if you declare a delegate property - it may be wired in later, because it may be set, unset, reset. There, I think we need an unchecked hard AbsentDelegateException?
I just thought up a problematic situation for my proposal to extend the "delegatesto" method syntax to allow delegating a small number of methods to a member.
Given
public class Foo {
}public class Delegator(Foo one, Foo two) {
}Delegator now effectively implements Foo's interface even though it does not use the special "delegate Foo one" constructor syntax that enables the following assignment to be valid: Foo anotherFoo = new Delegator(new Foo(), new Foo());
I don't think that's a problem. If you want it to do so, you either declare it a delegate, or (if foo is an interface) you declare that the class implements the Foo API.
I wonder if we could extend the 'delegate' to a 'proxy' which is a special kind of delegator that:
Well a delegator isn't really a thing, in my concept, it's merely an object that's delegating. It's not itself a special type. You can effectively implement a proxy this way by delegating to a single delegate, and, assuming my above-suggested rule for inherited methods having priority over delegated methods unless explicitly delegated) you wouldn't have to worry about isEqual() or hashCode().
Now, you're almost suggesting objective-c's forwarding feature, where all method invocations actually go through a central message forwarding method which, in its default implementation checks if the object responds to the selector of that message (the method signature) and, if so, invokes that method. One can override that behaviour in a proxy class by simply overriding the message forwarding method. And NSProxy actually doesn't inherit from NSObject, so you're in the right zone, but that's slightly different.
Certainly delegate/delegatesTo would be a great aid to implementing proxies, but the arbitrary dispatch/overriding is trickier, and would need other abilities - ones which might actually be limited due to the java security model, or without creating selector dictionaries which would radically slow down the language (even though I love the idea, conceptually).
Can you maybe provide some examples of how you see this working in sample code? That'll help us figure out how to structure this, or whether there's an alternate idiom which might meet the need.
Yeah by proxy I guess I am thinking of a way to deal with aspect oriented stuff.
This is what I am thinking about roughly:
public class MyController { public Void requestA() { ... } public Void requestB() { ... } } public class SecureController(delegate MyController controller) { public Void requestA() { if (!loggedIn) raise ...; controller.requestA(); } public Void requestB() { if (!loggedIn) raise ...; controller.requestB(); } }But some builtin way to handle cases like this...
Don't forget, we have an IoC infrastructure built-in, so could do AOP stuff by interception or some related means.
If the goal is to have a read-friendly language then use the composes keyword over the alternate syntax.
This is brilliant stuff. It might also be a good idea to restrict delegate types to interfaces, to avoid the equals() and hashCode() problem, since interfaces do not implicitly define them. That is ...
class Clazz { ... } interface Interface { ... } class Delegator(delegate Clazz clazz) { ... } // compiler error! class Delegator(delegate Interface interface) { ... } // OK!Then you could simply think of delegators as implementing the interface(s) given by the delegate keyword.
interface Foo { ... } interface Bar { ... } class Delegator(delegate Foo foo, delegate Bar bar) { ... } // implicitly implements Foo, BarCombine this with some dependency injection magic, voilĂ !