File Coverage

blib/lib/Class/Delegator.pm
Criterion Covered Total %
statement 44 44 100.0
branch 22 22 100.0
condition 11 13 84.6
subroutine 5 5 100.0
pod n/a
total 82 84 97.6


line stmt bran cond sub pod time code
1             package Class::Delegator;
2              
3             # $Id: Delegator.pm 3912 2008-05-15 03:33:00Z david $
4              
5 1     1   34888 use strict;
  1         2  
  1         405  
6              
7             $Class::Delegator::VERSION = '0.09';
8              
9             =begin comment
10              
11             Fake-out Module::Build. Delete if it ever changes to support =head1 headers
12             other than all uppercase.
13              
14             =head1 NAME
15              
16             Class::Delegator - Simple and fast object-oriented delegation
17              
18             =end comment
19              
20             =head1 Name
21              
22             Class::Delegator - Simple and fast object-oriented delegation
23              
24             =head1 Synopsis
25              
26             package Car;
27              
28             use Class::Delegator
29             send => 'start',
30             to => '{engine}',
31              
32             send => 'power',
33             to => 'flywheel',
34             as => 'brake',
35              
36             send => [qw(play pause rewind fast_forward shuffle)],
37             to => 'ipod',
38              
39             send => [qw(accelerate decelerate)],
40             to => 'brakes',
41             as => [qw(start stop)],
42              
43             send => 'drive',
44             to => [qw(right_rear_wheel left_rear_wheel)],
45             as => [qw(rotate_clockwise rotate_anticlockwise)]
46             ;
47              
48              
49             =head1 Description
50              
51             This module provides a subset of the functionality of Damian Conway's lovely
52             L module. Why a subset? Well, I didn't
53             need all of the fancy matching semantics, just string string specifications to
54             map delegations. Furthermore, I wanted it to be fast (See
55             L). And finally, since Class::Delegation uses an
56             C block to do its magic, it doesn't work in persistent environments that
57             don't execute C blocks, such as in L.
58              
59             However the specification semantics of Class::Delegator differ slightly from
60             those of Class::Delegation, so this module isn't a drop-in replacement for
61             Class::Delegation. Read on for details.
62              
63             =head2 Specifying methods to be delegated
64              
65             The names of methods to be redispatched can be specified using the C
66             parameter. This parameter may be specified as a single string or as an array
67             of strings. A single string specifies a single method to be delegated, while
68             an array reference is a list of methods to be delegated.
69              
70             =head2 Specifying attributes to be delegated to
71              
72             Use the C parameter to specify the attribute(s) or accessor method(s) to
73             which the method(s) specified by the C parameter are to be delegated.
74             The semantics of the C parameter are a bit different from
75             Class::Delegation. In order to ensure the fastest performance possible, this
76             module simply installs methods into the calling class to handle the
77             delegation. There is no use of C<$AUTOLOAD> or other such trickery. But since
78             the new methods are installed by Cing a string, the C parameter for
79             each delegation statement must be specified in the manner appropriate to
80             accessing the underlying attribute. For example, to delegate a method call to
81             an attribute stored in a hash key, simply wrap the key in braces:
82              
83             use Class::Delegator
84             send => 'start',
85             to => '{engine}',
86             ;
87              
88             To delegate to a method, simply name the method:
89              
90             use Class::Delegator
91             send => 'power',
92             to => 'flywheel',
93             ;
94              
95             If your objects are array-based, wrap the appropriate array index number in
96             brackets:
97              
98             use Class::Delegator
99             send => 'idle',
100             to => '[3]',
101             ;
102              
103             And so on.
104              
105             =head2 Specifying the name of a delegated method
106              
107             Sometimes it's necessary for the name of the method that's being delegated to
108             be different from the name of the method to which you're delegating execution.
109             For example, your class might already have a method with the same name as the
110             method to which you're delegating. The C parameter allows you translate
111             the method name or names in a delegation statement. The value associated with
112             an C parameter specifies the name of the method to be invoked, and may be
113             a string or an array (with the number of elements in the array matching the
114             number of elements in a corresponding C array).
115              
116             If the attribute is specified via a single string, that string is taken as the
117             name of the attribute to which the associated method (or methods) should be
118             delegated. For example, to delegate invocations of C<$self-Epower(...)> to
119             C<$self-E{flywheel}-Ebrake(...)>:
120              
121             use Class::Delegator
122             send => 'power',
123             to => '{flywheel}',
124             as => 'brake',
125             ;
126              
127             If both the C and the C parameters specify array references, each
128             local method name and deleted method name form a pair, which is invoked. For
129             example:
130              
131             use Class::Delegator
132             send => [qw(accelerate decelerate)],
133             to => 'brakes',
134             as => [qw(start stop)],
135             ;
136              
137             In this example, the C method will be delegated to the C
138             method of the C attribute and the C method will be
139             delegated to the C method of the C attribute.
140              
141             =head2 Delegation to multiple attributes in parallel
142              
143             An array reference can be used as the value of the C parameter to specify
144             the a list of attributes, I are delegated to--in the same order
145             as they appear in the array. In this case, the C parameter B be a
146             scalar value, not an array of methods to delegate.
147              
148             For example, to distribute invocations of C<$self-Edrive(...)> to both
149             C<$self-E{left_rear_wheel}-Edrive(...)> and
150             C<$self-E{right_rear_wheel}-Edrive(...)>:
151              
152             use Class::Delegator
153             send => 'drive',
154             to => ["{left_rear_wheel}", "{right_rear_wheel}"]
155             ;
156              
157             Note that using an array to specify parallel delegation has an effect on the
158             return value of the delegation method specified by the C parameter. In a
159             scalar context, the original call returns a reference to an array containing
160             the (scalar context) return values of each of the calls. In a list context,
161             the original call returns a list of array references containing references to
162             the individual (list context) return lists of the calls. So, for example, if
163             the C method of a class were delegated like so:
164              
165             use Class::Delegator
166             send => 'cost',
167             to => ['supplier', 'manufacturer', 'distributor']
168             ;
169              
170             then the total cost could be calculated like this:
171              
172             use List::Util 'sum';
173             my $total = sum @{$obj->cost()};
174              
175             If both the C<"to"> key and the C<"as"> parameters specify multiple values,
176             then each attribute and method name form a pair, which is invoked. For
177             example:
178              
179             use Class::Delegator
180             send => 'escape',
181             to => ['{flywheel}', '{smokescreen}'],
182             as => ['engage', 'release'],
183             ;
184              
185             would sequentially call, within the C delegation method:
186              
187             $self->{flywheel}->engage(...);
188             $self->{smokescreen}->release(...);
189              
190             =cut
191              
192             ##############################################################################
193              
194             sub import {
195 16     16   39543 my $class = shift;
196 16         54 my ($caller, $filename, $line) = caller;
197 16         56 while (@_) {
198 16         25 my ($key, $send) = (shift, shift);
199 16 100       48 _die(qq{Expected "send => " but found "$key => $send"})
200             unless $key eq 'send';
201              
202 15         25 ($key, my $to) = (shift, shift);
203 15 100       40 _die(qq{Expected "to => " but found "$key => $to"})
204             unless $key eq 'to';
205              
206 14 100 100     44 _die('Cannot specify both "send" and "to" as arrays')
207             if ref $send && ref $to;
208              
209 13 100       28 if (ref $to) {
210 4 100 100     18 my $as = ($_[0] || '') eq 'as' ? (shift, shift) : undef;
211 4 100       19 if (ref $as) {
    100          
212 2 100       10 _die('Arrays specified for "to" and "as" must be the same length')
213             unless @$to == @$as;
214             } elsif (defined $as) {
215 1         3 _die('Cannot specify "as" as a scalar if "to" is an array')
216             } else {
217 1         2 $as = [];
218             }
219              
220 2         4 my $meth = "$caller\::$send";
221 2         13 my @lines = (
222             # Copy @_ to @args to ensure same args passed to all methods.
223             "#line $line $filename",
224             "sub { local \*__ANON__ = '$meth';",
225             'my ($self, @args) = @_;',
226             'my @ret;',
227             );
228 2         3 my @array = (
229             'return (',
230             );
231 2         4 my @scalar = (
232             ') if wantarray;',
233             'return [',
234             );
235              
236 2         4 while (@$to) {
237 4         5 my $t = shift @$to;
238 4   66     43 my $m = shift @$as || $send;
239 4         8 push @scalar, "scalar \$self->$t->$m(\@args),";
240 4         12 push @array, "[\$self->$t->$m(\@args)],";
241             }
242 1     1   5 no strict 'refs';
  1         2  
  1         127  
243 2         227 *{$meth} = eval join "\n", @lines, @array, @scalar, ']', '}';
  2         398  
244              
245             } else {
246 9 100 100     44 my $as = ($_[0] || '') eq 'as'
    100          
247             ? (shift, ref $_[0] ? shift : [shift])
248             : [];
249 9 100       25 $send = [$send] unless ref $send;
250              
251 9         22 while (@$send) {
252 11         14 my $s = shift @$send;
253 11   66     35 my $m = shift @$as || $s;
254 11         21 my $meth = "$caller\::$s";
255 1     1   4 no strict 'refs';
  1         5  
  1         127  
256 11         674 *{$meth} = eval qq{#line $line $filename
  11         1413  
257             sub {
258             local \*__ANON__ = '$meth';
259             shift->$to->$m(\@_);
260             };
261             };
262             }
263             }
264             }
265             }
266              
267             sub _die {
268 5     5   34 require Carp;
269 5         883 Carp::croak(@_);
270             }
271              
272             ##############################################################################
273              
274             =head1 Benchmarks
275              
276             I whipped up a quick script to compare the performance of Class::Delegator to
277             Class::Delegation and a manually-installed delegation method (the control).
278             I'll let the numbers speak for themselves:
279              
280             Benchmark: timing 1000000 iterations of Class::Delegation, Class::Delegator, Manually...
281             Class::Delegation: 106 wallclock secs (89.03 usr + 2.09 sys = 91.12 CPU) @ 10974.54/s (n=1000000)
282             Class::Delegator: 3 wallclock secs ( 3.44 usr + 0.02 sys = 3.46 CPU) @ 289017.34/s (n=1000000)
283             Control: 3 wallclock secs ( 3.01 usr + 0.02 sys = 3.03 CPU) @ 330033.00/s (n=1000000)
284              
285             =head1 Bugs
286              
287             Please send bug reports to or report them
288             via the CPAN Request Tracker at
289             L.
290              
291             =head1 Author
292              
293             =begin comment
294              
295             Fake-out Module::Build. Delete if it ever changes to support =head1 headers
296             other than all uppercase.
297              
298             =head1 AUTHOR
299              
300             =end comment
301              
302             David Wheeler
303              
304             =head1 See Also
305              
306             =over
307              
308             =item L
309              
310             Damian Conway's brilliant module does ten times what this one does--and does
311             it ten times slower.
312              
313             =item L
314              
315             Kurt Starsinic's module uses inheritance to manage delegation, and has a
316             somewhat more complex interface.
317              
318             =item L
319              
320             Simon Cozen's delegation module takes the same approach as this module, but
321             provides no method for resolving method name clashes the way this module's
322             C parameter does.
323              
324             =back
325              
326             =head1 Copyright and License
327              
328             Copyright (c) 2005-2008 David Wheeler. Some Rights Reserved.
329              
330             This module is free software; you can redistribute it and/or modify it under
331             the same terms as Perl itself.
332              
333             =cut