Delegation pattern
This article may be too technical for most readers to understand.(July 2009) |
In software engineering, the delegation pattern is a design pattern in object-oriented programming where an object, instead of performing one of its stated tasks, delegates that task to an associated helper object. It passes the buck, so to speak (technically, an Inversion of Responsibility). The helper object is called the delegate. The delegation pattern is one of the fundamental abstraction patterns that underlie other software patterns such as composition (also referred to as aggregation), mixins and aspects.
Examples
Simple Java example
In this Java example, the Printer
class has a print
method. This print method, rather than performing the print itself, delegates to class RealPrinter
. To the outside world it appears that the Printer
class is doing the print, but RealPrinter
class is the one actually doing the work.
Delegation is simply passing a duty off to someone/something else. Here is a simple example:
class RealPrinter { // the "delegate"
void print() {
System.out.print("something");
}
}
class Printer { // the "delegator"
RealPrinter p = new RealPrinter(); // create the delegate
void print() {
p.print(); // delegation
}
}
public class Main {
// to the outside world it looks like Printer actually prints.
public static void main(String[] args) {
Printer printer = new Printer();
printer.print();
}
}
Complex Java example
By using interfaces, delegation can be made more flexible and typesafe. "Flexibility" here means, that C, need not refer to A or B in any way, the switching of delegation is abstracted from C. Needless to say, toA and toB don't count as references to A and B. In this example, class C
can delegate to either class A
or class B
. Class C
has methods to switch between classes A
and B
. Including the implements clauses improves type safety, because each class must implement the methods in the interface. The main tradeoff is more code.
interface I {
void f();
void g();
}
class A implements I {
public void f() { System.out.println("A: doing f()"); }
public void g() { System.out.println("A: doing g()"); }
}
class B implements I {
public void f() { System.out.println("B: doing f()"); }
public void g() { System.out.println("B: doing g()"); }
}
class C implements I {
// delegation
I i = new A();
public void f() { i.f(); }
public void g() { i.g(); }
// normal attributes
void toA() { i = new A(); }
void toB() { i = new B(); }
}
public class Main {
public static void main(String[] args) {
C c = new C();
c.f(); // output: A: doing f()
c.g(); // output: A: doing g()
c.toB();
c.f(); // output: B: doing f()
c.g(); // output: B: doing g()
}
}
Complex C++ example
This example is a C++ version of the complex Java example above. Since C++ does not have an interface construct, a pure virtual class plays the same role. The advantages and disadvantages are largely the same as in the Java example.
#include <iostream>
using namespace std;
class I {
public:
virtual void f() = 0;
virtual void g() = 0;
virtual ~I() {}
};
class A : public I {
public:
void f() { cout << "A: doing f()" << endl; }
void g() { cout << "A: doing g()" << endl; }
~A() { cout << "A: cleaning up." << endl; }
};
class B : public I {
public:
void f() { cout << "B: doing f()" << endl; }
void g() { cout << "B: doing g()" << endl; }
~B() { cout << "B: cleaning up." << endl; }
};
class C : public I {
public:
// construction/destruction
C() : i( new A() ) { }
virtual ~C() { delete i; }
private:
// delegation
I* i;
public:
void f() { i->f(); }
void g() { i->g(); }
// normal attributes
void toA() { delete i; i = new A(); }
void toB() { delete i; i = new B(); }
};
int main() {
// we use by default the instance of A.
C c;
// Here we are calling A methods.
c.f();
c.g();
// Here delete the instance of A and switch with the instance of B.
c.toB();
// Now with the same methods we are calling B methods.
c.f();
c.g();
// The delegate is deleted by normal C++ scoping rules.
}
The output:
A: doing f() A: doing g() A: cleaning up. B: doing f() B: doing g() B: cleaning up.
Objective-C example
Delegation is very common in the Cocoa framework (the most common client library of Objective-C). Here's an example involving a scrolling view, which will ask its delegate if it's okay to scroll to a certain point before doing so.
Objective-C
// A custom view that scrolls its children.
@interface TCScrollView : NSView {
id _delegate; // A delegate that wants to act on events in this view
}
-(IBAction)scrollToCenter:(id)sender; // A method that can be bound to a button in the UI
-(void)scrollToPoint:(NSPoint)to;
// Accessors.
-(id)delegate;
-(void)setDelegate:(id)delegate;
@end
// A category on NSObject describing possible TCScrollView delegate methods.
// This is an informal protocol: implementor doesn't have to implement all or even any of
// the methods in the protocol
@interface NSObject (TCScrollViewDelegate)
-(BOOL)scrollView:(TCScrollView*)scrollView shouldScrollToPoint:(NSPoint)newPoint;
@end
@implementation TCScrollView
-(void)delegate {
return _delegate;
}
-(void)setDelegate:(id)delegate {
_delegate = delegate;
}
-(IBAction)scrollToCenter:(id)sender; {
[self scrollToPoint:NSPointMake(0,0)];
}
-(void)scrollToPoint:(NSPoint)to {
BOOL shouldScroll = YES;
// If we have a delegate, and that delegate indeed does implement our delegate method,
if(delegate && [_delegate respondsToSelector:@selector(scrollView:shouldScrollToPoint:)])
shouldScroll = [_delegate scrollView:self shouldScrollToPoint:to]; // ask it if it's okay to scroll to this point.
// If not, ignore the scroll request.
if(!shouldScroll)
return;
/// Scrolling code omitted.
}
@end
@interface MyCoolAppController {
IBOutlet TCScrollView* scrollView;
}
@end
@implementation MyCoolAppController
-(void)awakeFromNib {
[scrollView setDelegate:self];
}
-(BOOL)scrollView:(TCScrollView*)scrollView shouldScrollToPoint:(NSPoint)newPoint {
if(newPoint.x > 0 && newPoint.y > 0)
return YES;
return NO;
}
@end
Objective-C 2.0
// A custom view that scrolls its children.
@interface TCScrollView : NSView {
id delegate; // A delegate that wants to act on events in this view
}
-(IBAction)scrollToCenter:(id)sender; // A method that can be bound to a button in the UI
-(void)scrollToPoint:(NSPoint)to;
// Accessors. Implementation not shown.
@property (nonatomic, assign) id delegate;
@end
// This is a formal protocol: implementor doesn't have to implement all or even any of
// the optional methods in the protocol
@protocol TCScrollViewDelegate
@optional
-(BOOL)scrollView:(TCScrollView*)scrollView shouldScrollToPoint:(NSPoint)newPoint;
@end
@implementation TCScrollView
-(IBAction)scrollToCenter:(id)sender; {
[self scrollToPoint:NSPointMake(0,0)];
}
-(void)scrollToPoint:(NSPoint)to {
BOOL shouldScroll = YES;
// If we have a delegate, and that delegate indeed does implement our delegate method,
if(delegate && [delegate respondsToSelector:@selector(scrollView:shouldScrollToPoint:)])
shouldScroll = [delegate scrollView:self shouldScrollToPoint:to]; // ask it if it's okay to scroll to this point.
// If not, ignore the scroll request.
if(!shouldScroll)
return;
// Scrolling code omitted.
}
@end
@interface MyCoolAppController : NSObject <TCScrollViewDelegate> {
IBOutlet TCScrollView* scrollView;
}
@end
@implementation MyCoolAppController
-(void)awakeFromNib {
[scrollView setDelegate:self];
}
-(BOOL)scrollView:(TCScrollView*)scrollView shouldScrollToPoint:(NSPoint)newPoint {
if(newPoint.x > 0 && newPoint.y > 0)
return YES;
return NO;
}
@end
Complex Eiffel example
This example is a Eiffel version of the complex Java example above.
deferred class I feature
f is deferred end
g is deferred end
end
class A inherit I feature
f is do print("A: doing f%N") end
g is do print("A: doing g%N") end
end
class B inherit I feature
f is do print("B: doing f%N") end
g is do print("B: doing g%N") end
end
class C inherit I creation to_a, to_b feature
i: I
f is do i.f end
g is do i.g end
to_a is do create {A} i end
to_b is do create {B} i end
end
class MAIN creation main feature
main is local c: C do
create c.to_a
c.f
c.g
c.to_b
c.f
c.g
end
end
Python
class I:
def f(self): pass
def g(self): pass
class A(I):
def f(self):
print "A: doing f()"
def g(self):
print "A: doing g()"
class B(I):
def f(self):
print "B: doing f()"
def g(self):
print "B: doing g()"
class C(I):
def __init__(self):
# delegation
self.i = None
def f(self):
self.i.f()
def g(self):
self.i.g()
# normal attributes
def to_a(self):
self.i = A()
def to_b(self):
self.i = B()
if __name__ == '__main__':
c = C()
c.to_a()
c.f() # output: A: doing f()
c.g() # output: A: doing g()
c.to_b()
c.f() # output: B: doing f()
c.g() # output: B: doing g()
Tcl
#TclOO is part of Tcl in 8.6 addon package in 8.5
if { [ catch {package require TclOO } err ] != 0 } {
puts stderr "Unable to find package TclOO ... adjust your auto_path!";
}
oo::class create I {
constructor {} {
}
method f { } {puts "Error please implement"; };
method g { } {puts "Error please implement"; };
}
oo::class create A {
superclass I
constructor {} {
next;
}
method f { } {
puts "A : doing f()"
}
method g { } {
puts "A : doing g()"
}
}
oo::class create B {
superclass I
constructor {} {
next;
}
method f { } {
puts "B : doing f()"
}
method g { } {
puts "B : doing g()"
}
}
oo::class create C {
variable i
constructor {} {
# delegation
set i [A new ]
}
method f { } {
$i f
}
method g { } {
$i g
}
# normal attributes
method to_a { } {
$i destroy;
set i [A new ]
}
method to_b { } {
$i destroy;
set i [B new ]
}
}
set c [C new ]
$c to_a
$c f ; # output A : doing f
$c g ; # output A : doing g
$c to_b
$c f ; # output B : doing f
$c g ; # output B : doing g