Home Design Patterns Delegation
Delegation
Modern PracticeForward work to a held object rather than inheriting the ability to do it.
Purpose
An object is asked to do something and, instead of doing it, passes the request to another object it holds and returns whatever comes back. The caller sees no difference: the operation is still part of the receiver's public contract, and the helper doing the actual work is a private detail that can be swapped, wrapped or replaced while the program is running.
That is deliberately close to "composition", and it is fair to ask what the
extra name buys. It buys the distinction between holding a collaborator and
being answerable for what it does. A class that holds a
Database and exposes save() is composing. A class that
promises area() in its own interface and implements it as
return shape.area(); is delegating: the responsibility is the
receiver's, the work is somebody else's, and the client is never told. Inheritance
is the other way to arrange that, which is why delegation is best understood as
the runtime alternative to subclassing rather than as a kind of composition.
Design
The delegator holds a field typed as an interface, and each delegated method is one line that forwards. Because the field is an interface and is assigned rather than declared by a class header, the choice of helper is made at run time and can be changed at run time, which is precisely what inheritance cannot do.
There is one detail that makes delegation more than forwarding, and it is the
one the Gang of Four picked out in their introduction: the delegator often passes
this to the delegate, so the delegated operation can refer back to the
object it is acting on behalf of. That is what recovers the thing inheritance gives
you for free, where a method in the base class can call back into the subclass. It
also lets one stateless helper serve many delegators, since everything it needs
about the caller arrives as an argument.
Example
Autobot Robotics needed the Arm802 controller to enforce the same motion limits
as the Arm601: joint ranges, velocity ceilings, and a check that a commanded pose
does not put the wrist through the table. The first attempt was the obvious one,
making Arm802Controller a subclass of Arm601Controller.
It worked, and it was wrong in a way that took a year to show up.
When the Arm1210 arrived with a seventh axis, the inherited limit checking came
with assumptions baked into the parent: an array sized for six joints, a protected
field the subclass was expected to keep in step, and a homeAll() that
the constructor called before the subclass had finished initializing. Fixing the
1210 meant editing Arm601Controller, which meant re-testing the 601
and the 802 on real hardware to ship a change that had nothing to do with either.
Inheritance had made three products into one release.
Limit checking became a LimitChecker object that each controller
holds and forwards to. The controller keeps checkMove() in its own
interface, so no caller changed, and passes itself to the checker so the checker
can read the current pose without holding any state of its own. Each arm now gets
the checker it needs, a test build gets a permissive one, and the diagnostics tool
swaps in a checker that records rejections instead of refusing them, on a live
controller, without a rebuild. The cost was about a dozen one-line forwarding
methods, which is the honest price of the pattern and is worth it here.
// Before: reuse by subclassing, which fixes the choice at compile time
// and welds Arm802 to Arm601's internals.
class Arm802Controller : Arm601Controller
{
// inherits checkMove(), and everything else it never wanted
}
interface LimitChecker
{
bool allows(ArmController arm, Pose target);
}
class Arm802Controller : ArmController
{
private LimitChecker limits;
public Arm802Controller(LimitChecker l) { limits = l; }
// The delegation. checkMove() stays part of this class's contract,
// and "this" goes along so the checker can read the live pose.
public bool checkMove(Pose target)
{
return limits.allows(this, target);
}
public void useChecker(LimitChecker l) { limits = l; }
}
class SixAxisLimits : LimitChecker
{
public bool allows(ArmController arm, Pose target)
{
// joint ranges, velocity ceiling, table clearance
}
}
// Same controller, different rules, chosen at run time.
class SevenAxisLimits : LimitChecker { }
class RecordingLimits : LimitChecker { } // logs rejections, refuses nothing
limits field, the
forwarding body of checkMove, and useChecker, which is
the ability inheritance cannot offer at any price.Seen in the Wild
Kotlin has delegation in the language: class Logger(base: Writer) :
Writer by base tells the compiler to generate the forwarding methods, which
exists because writing them by hand is the pattern's main cost. Ruby ships
Forwardable and SimpleDelegator in its standard library
for the same reason, and Go's struct embedding promotes the embedded type's
methods onto the outer type, which is compiler-generated forwarding with different
words. Cocoa builds a large part of its API on it: a UITableView owns
the drawing and hands every decision about content and selection to a separate
delegate object your code supplies. In Java, FilterInputStream holds
an InputStream and forwards every method to it, which is delegation
being used to build a Decorator.
Comparisons
- Strategy, State and Bridge are all delegation with a reason attached. Strategy delegates an algorithm chosen from outside, State delegates to an object that chooses its own successor, Bridge delegates across a whole implementation layer that is expected to keep growing. The mechanism is identical in all three; what differs is why the helper changes and who changes it.
- Template Method is the inheritance-shaped answer to the same problem, and the pair is the clearest illustration of "favor composition over inheritance" available. Template Method binds the varying step at compile time; delegation binds it at run time and can rebind it later.
- Decorator and Proxy both delegate while keeping the wrapped object's exact interface. A Decorator adds behavior around the forwarded call, a Proxy decides whether and when to make it at all, and both are delegation with a specific intent.
- Adapter delegates while deliberately changing the interface, which is the one case where the forwarding method is not allowed to be a straight pass-through.
- Facade delegates to several objects at once and owns the ordering between them, rather than forwarding one operation to one helper.
Why It Exists
My take
Delegation exists because inheritance is a poor tool for reuse and an excellent one for classification, and for about twenty years the industry used it for both. Subclassing to get a method is a decision made at compile time, binding you to the parent's internals, its constructor order, its protected fields and its future changes, in exchange for not typing a forwarding method. The Arm802 in the example is the standard shape of that mistake: the parent was chosen because it happened to contain code that was needed, and after that every product shared one release.
The reason it is worth naming, rather than filing under composition, is that it names the mechanism that half the catalog is built from. Bridge, Strategy, State, Decorator, Proxy and Adapter are all one object holding another and forwarding to it; they differ in what varies and why, not in how. Once you see that, those patterns stop being six things to memorize and become one thing with six intents, and, more usefully, you stop reaching for the pattern names when what you actually need is a field and a one-line method.
I will happily concede this is barely a pattern. It is a technique, and it is
the technique that "favor composition over inheritance" turns into when you type
it. That is exactly why it belongs on a list like this: the advice is repeated
everywhere and is nearly useless without the concrete move it implies, and the
concrete move is one field, one forwarding method and, when the helper needs
context, this as an argument.
Criticisms
The boilerplate is real and is the reason several languages grew syntax for it. Inheritance gives you every member of the parent for one line in a class header; delegation makes you write each one, and adding a method to the interface later means visiting every delegator. For a wide interface that is a genuine tax, and it is a tax paid in the kind of code nobody reads carefully, so a forwarding method that quietly does something slightly different is easy to miss in review.
The deeper problem is the broken self reference, sometimes called object
schizophrenia. Under inheritance, a call from the base class to an overridable
method dispatches back to the subclass automatically. Under delegation it does
not: the helper calling its own methods stays inside the helper, and there is no
path back to the delegator unless you passed this and the helper
calls through it. That is why delegation is not a drop-in replacement for
inheritance, and why the translation between them is not mechanical. Two objects
also mean two identities, so equality, hashing and any code that compares
references now has to decide which object it means.
And it adds a hop that only exists at run time. A stack trace grows a frame, jump-to-definition lands on an interface, and the question "what actually happens when I call this" becomes a question about how the object was wired rather than about what the source says. That is the standard price of every indirection on this site, and it is the reason delegation should be applied where something genuinely varies, not applied everywhere on principle.