Commit | Line | Data |
2e3d0a0a |
1 | |
2 | =pod |
3 | |
4 | =head1 NAME |
5 | |
021b8139 |
6 | Moose::Cookbook::Roles::Recipe2 - Advanced Role Composition - method exclusion and aliasing |
2e3d0a0a |
7 | |
8 | =head1 SYNOPSIS |
9 | |
10 | package Restartable; |
11 | use Moose::Role; |
12 | |
13 | has 'is_paused' => ( |
14 | is => 'rw', |
9711d93b |
15 | isa => 'Bool', |
2e3d0a0a |
16 | default => 0, |
17 | ); |
18 | |
19 | requires 'save_state', 'load_state'; |
20 | |
c79239a2 |
21 | sub stop { 1 } |
2e3d0a0a |
22 | |
c79239a2 |
23 | sub start { 1 } |
2e3d0a0a |
24 | |
25 | package Restartable::ButUnreliable; |
26 | use Moose::Role; |
27 | |
a39ea7dc |
28 | with 'Restartable' => { |
c8b8d92f |
29 | -alias => { |
a39ea7dc |
30 | stop => '_stop', |
31 | start => '_start' |
0d412f7a |
32 | }, |
33 | -excludes => [ 'stop', 'start' ], |
a39ea7dc |
34 | }; |
2e3d0a0a |
35 | |
36 | sub stop { |
37 | my $self = shift; |
38 | |
39 | $self->explode() if rand(1) > .5; |
40 | |
41 | $self->_stop(); |
42 | } |
43 | |
44 | sub start { |
45 | my $self = shift; |
46 | |
47 | $self->explode() if rand(1) > .5; |
48 | |
49 | $self->_start(); |
50 | } |
51 | |
52 | package Restartable::ButBroken; |
53 | use Moose::Role; |
54 | |
c8b8d92f |
55 | with 'Restartable' => { -excludes => [ 'stop', 'start' ] }; |
2e3d0a0a |
56 | |
57 | sub stop { |
58 | my $self = shift; |
59 | |
60 | $self->explode(); |
61 | } |
62 | |
63 | sub start { |
64 | my $self = shift; |
65 | |
66 | $self->explode(); |
67 | } |
68 | |
69 | =head1 DESCRIPTION |
70 | |
aa8d5e2d |
71 | In this example, we demonstrate how to exercise fine-grained control |
72 | over what methods we consume from a role. We have a C<Restartable> |
73 | role which provides an C<is_paused> attribute, and two methods, |
74 | C<stop> and C<start>. |
2e3d0a0a |
75 | |
aa8d5e2d |
76 | Then we have two more roles which implement the same interface, each |
77 | putting their own spin on the C<stop> and C<start> methods. |
2e3d0a0a |
78 | |
79 | In the C<Restartable::ButUnreliable> role, we want to provide a new |
80 | implementation of C<stop> and C<start>, but still have access to the |
81 | original implementation. To do this, we alias the methods from |
82 | C<Restartable> to private methods, and provide wrappers around the |
83 | originals (1). |
84 | |
0d412f7a |
85 | Note that aliasing simply I<adds> a name, so we also need to exclude the |
86 | methods with their original names. |
87 | |
aa8d5e2d |
88 | with 'Restartable' => { |
c8b8d92f |
89 | -alias => { |
aa8d5e2d |
90 | stop => '_stop', |
91 | start => '_start' |
0d412f7a |
92 | }, |
93 | -excludes => [ 'stop', 'start' ], |
aa8d5e2d |
94 | }; |
95 | |
2e3d0a0a |
96 | In the C<Restartable::ButBroken> role, we want to provide an entirely |
aa8d5e2d |
97 | new behavior for C<stop> and C<start>. We exclude them entirely when |
2e3d0a0a |
98 | composing the C<Restartable> role into C<Restartable::ButBroken>. |
99 | |
c8b8d92f |
100 | It's worth noting that the C<-excludes> parameter also accepts a single |
2e3d0a0a |
101 | string as an argument if you just want to exclude one method. |
102 | |
c8b8d92f |
103 | with 'Restartable' => { -excludes => [ 'stop', 'start' ] }; |
aa8d5e2d |
104 | |
2e3d0a0a |
105 | =head1 CONCLUSION |
106 | |
aa8d5e2d |
107 | Exclusion and renaming are a power tool that can be handy, especially |
108 | when building roles out of other roles. In this example, all of our |
109 | roles implement the C<Restartable> role. Each role provides same API, |
110 | but each has a different implementation under the hood. |
2e3d0a0a |
111 | |
112 | You can also use the method aliasing and excluding features when |
113 | composing a role into a class. |
114 | |
115 | =head1 FOOTNOTES |
116 | |
117 | =over 4 |
118 | |
119 | =item (1) |
120 | |
121 | The mention of wrapper should tell you that we could do the same thing |
122 | using method modifiers, but for the sake of this example, we don't. |
123 | |
124 | =back |
125 | |
126 | =head1 AUTHOR |
127 | |
128 | Dave Rolsky E<lt>autarch@urth.orgE<gt> |
129 | |
130 | =head1 COPYRIGHT AND LICENSE |
131 | |
7e0492d3 |
132 | Copyright 2006-2010 by Infinity Interactive, Inc. |
2e3d0a0a |
133 | |
134 | L<http://www.iinteractive.com> |
135 | |
136 | This library is free software; you can redistribute it and/or modify |
137 | it under the same terms as Perl itself. |
138 | |
c79239a2 |
139 | =begin testing |
140 | |
141 | { |
142 | my $unreliable = Moose::Meta::Class->create_anon_class( |
143 | superclasses => [], |
144 | roles => [qw/Restartable::ButUnreliable/], |
145 | methods => { |
146 | explode => sub { }, # nop. |
147 | 'save_state' => sub { }, |
148 | 'load_state' => sub { }, |
149 | }, |
150 | )->new_object(); |
151 | ok( $unreliable, 'made anon class with Restartable::ButUnreliable role' ); |
152 | can_ok( $unreliable, qw/start stop/ ); |
153 | } |
154 | |
155 | { |
156 | my $cnt = 0; |
157 | my $broken = Moose::Meta::Class->create_anon_class( |
158 | superclasses => [], |
159 | roles => [qw/Restartable::ButBroken/], |
160 | methods => { |
161 | explode => sub { $cnt++ }, |
162 | 'save_state' => sub { }, |
163 | 'load_state' => sub { }, |
164 | }, |
165 | )->new_object(); |
166 | |
167 | ok( $broken, 'made anon class with Restartable::ButBroken role' ); |
168 | |
169 | $broken->start(); |
170 | |
171 | is( $cnt, 1, '... start called explode' ); |
172 | |
173 | $broken->stop(); |
174 | |
175 | is( $cnt, 2, '... stop also called explode' ); |
176 | } |
177 | |
178 | =end testing |
179 | |
2e3d0a0a |
180 | =cut |