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