When working with object-oriented languages, there are many instances when one would want to implement an interface re-using code from another implementation of the interface. While inheritance will work, it couples the new implementation to the specific other implementation. Composition with delegation will be a better way of doing this.
Let’s consider an application which deals with doors of different shapes. You’d
have a Door type and a Shape interface implemented by different concrete
shapes. You’d also have a DrawingSurface which takes any implementation of
Shape and invokes draw()
and fill()
on it passing a graphics context.
Here’s the verbose and repetitive code you’d have in Java with neither delegates nor any form of duck-typing.
interface Shape {
public void draw(GContext ctx);
public void fill(GContext ctx);
}
class Door implements Shape {
private Shape shape;
public Door(Shape shape) {
this.shape = shape;
}
public void draw(GContext ctx) {
shape.draw(ctx);
}
public void fill(GContext ctx) {
shape.draw(ctx);
}
}
In a dynamic language with meta-programming like Ruby, this is less verbose but
the delegates still need to be explicitly defined. The forwardable
library
defines the specified methods on Door
to invoke corresponding methods on
@shape
at run-time. However, tools like IDEs can’t recognize Door
as an
implementation of Shape
unless they handle the specific library call as a
special case.
require 'forwardable'
class Door
def_delegators :@shape, :draw, :fill
def initialize(shape)
@shape = shape
end
end
Go gives you delegates with no extra work. You just say Door
has a Shape
and you get statically checked and compiled delegation.
type Shape interface {
draw(ctx GContext)
fill(ctx GContext)
}
type Door struct {
Shape
}
This has all the compile-time safety checks of Java with the expressiveness of Ruby. This is one of the many cases where Go’s simple design makes code less verbose!
All opinions are my own. Copyright 2005 Chandra Sekar S.