Commit | Line | Data |
471c4f09 |
1 | |
2 | =pod |
3 | |
4 | =head1 NAME |
5 | |
021b8139 |
6 | Moose::Cookbook::Basics::Recipe2 - A simple B<BankAccount> example |
471c4f09 |
7 | |
8 | =head1 SYNOPSIS |
9 | |
10 | package BankAccount; |
471c4f09 |
11 | use Moose; |
8a7ed43e |
12 | |
13 | has 'balance' => ( isa => 'Int', is => 'rw', default => 0 ); |
14 | |
471c4f09 |
15 | sub deposit { |
8a7ed43e |
16 | my ( $self, $amount ) = @_; |
17 | $self->balance( $self->balance + $amount ); |
471c4f09 |
18 | } |
8a7ed43e |
19 | |
471c4f09 |
20 | sub withdraw { |
8a7ed43e |
21 | my ( $self, $amount ) = @_; |
471c4f09 |
22 | my $current_balance = $self->balance(); |
8a7ed43e |
23 | ( $current_balance >= $amount ) |
471c4f09 |
24 | || confess "Account overdrawn"; |
8a7ed43e |
25 | $self->balance( $current_balance - $amount ); |
471c4f09 |
26 | } |
8a7ed43e |
27 | |
471c4f09 |
28 | package CheckingAccount; |
471c4f09 |
29 | use Moose; |
8a7ed43e |
30 | |
471c4f09 |
31 | extends 'BankAccount'; |
8a7ed43e |
32 | |
33 | has 'overdraft_account' => ( isa => 'BankAccount', is => 'rw' ); |
34 | |
471c4f09 |
35 | before 'withdraw' => sub { |
8a7ed43e |
36 | my ( $self, $amount ) = @_; |
cdcae970 |
37 | my $overdraft_amount = $amount - $self->balance(); |
8a7ed43e |
38 | if ( $self->overdraft_account && $overdraft_amount > 0 ) { |
cdcae970 |
39 | $self->overdraft_account->withdraw($overdraft_amount); |
40 | $self->deposit($overdraft_amount); |
41 | } |
471c4f09 |
42 | }; |
43 | |
44 | =head1 DESCRIPTION |
45 | |
9cf569f3 |
46 | The first recipe demonstrated how to build very basic Moose classes, |
47 | focusing on creating and manipulating attributes. The objects in that |
b0825978 |
48 | recipe were very data-oriented, and did not have much in the way of |
9cf569f3 |
49 | behavior (i.e. methods). In this recipe, we expand upon the concepts |
50 | from the first recipe to include some real behavior. In particular, we |
b0825978 |
51 | show how you can use a method modifier to implement new behavior for a |
52 | method. |
9cf569f3 |
53 | |
54 | The classes in the SYNOPSIS show two kinds of bank account. A simple |
55 | bank account has one attribute, the balance, and two behaviors, |
56 | depositing and withdrawing money. |
57 | |
58 | We then extend the basic bank account in the CheckingAccount |
59 | class. This class adds another attribute, an overdraft account. It |
60 | also adds overdraft protection to the withdraw method. If you try to |
61 | withdraw more than you have, the checking account attempts to |
62 | reconcile the difference by withdrawing money from the overdraft |
63 | account. (1) |
64 | |
65 | The first class, B<BankAccount>, introduces a new attribute feature, a |
66 | default value: |
703d9522 |
67 | |
8a7ed43e |
68 | has 'balance' => ( isa => 'Int', is => 'rw', default => 0 ); |
703d9522 |
69 | |
9cf569f3 |
70 | This says that a B<BankAccount> has a C<balance> attribute, which has |
71 | a C<Int> type constraint, a read/write accessor, and a default value |
72 | of C<0>. This means that every instance of B<BankAccount> that is |
73 | created will have its C<balance> slot initialized to C<0>, unless some |
74 | other value is provided to the constructor. |
703d9522 |
75 | |
9cf569f3 |
76 | The C<deposit> and C<withdraw> methods should be fairly |
77 | self-explanatory, as they are just plain old Perl 5 OO. |
703d9522 |
78 | |
9cf569f3 |
79 | As you know from the first recipe, the keyword C<extends> sets a |
80 | class's superclass. Here we see that B<CheckingAccount> C<extends> |
81 | B<BankAccount>. The next line introduces yet another new attribute |
82 | feature, class-based type constraints: |
703d9522 |
83 | |
8a7ed43e |
84 | has 'overdraft_account' => ( isa => 'BankAccount', is => 'rw' ); |
703d9522 |
85 | |
9cf569f3 |
86 | Up until now, we have only seen the C<Int> type constraint, which (as |
87 | we saw in the first recipe) is a builtin type constraint. The |
88 | C<BankAccount> type constraint is new, and was actually defined the |
89 | moment we created the B<BankAccount> class itself. In fact, Moose |
90 | creates a corresponding type constraint for every class in your |
91 | program (2). |
92 | |
93 | This means that in the first recipe, constraints for both C<Point> and |
94 | C<Point3D> were created. In this recipe, both C<BankAccount> and |
95 | C<CheckingAccount> type constraints are created automatically. Moose |
96 | does this as a convenience so that your classes and type constraint |
97 | can be kept in sync with one another. In short, Moose makes sure that |
98 | it will just DWIM (3). |
99 | |
100 | In B<CheckingAccount>, we see another method modifier, the C<before> |
703d9522 |
101 | modifier. |
102 | |
103 | before 'withdraw' => sub { |
8a7ed43e |
104 | my ( $self, $amount ) = @_; |
703d9522 |
105 | my $overdraft_amount = $amount - $self->balance(); |
8a7ed43e |
106 | if ( $self->overdraft_account && $overdraft_amount > 0 ) { |
703d9522 |
107 | $self->overdraft_account->withdraw($overdraft_amount); |
108 | $self->deposit($overdraft_amount); |
109 | } |
110 | }; |
111 | |
9cf569f3 |
112 | Just as with the C<after> modifier from the first recipe, Moose will |
113 | handle calling the superclass method (in this case C<< |
114 | BankAccount->withdraw >>). |
703d9522 |
115 | |
9cf569f3 |
116 | The C<before> modifier will (obviously) run I<before> the code from |
117 | the superclass is run. Here, C<before> modifier implements overdraft |
118 | protection by first checking if there are available funds in the |
119 | checking account. If not (and if there is an overdraft account |
120 | available), it transfers the amount needed into the checking |
121 | account (4). |
122 | |
123 | As with the method modifier in the first recipe, we could use |
124 | C<SUPER::> to get the same effect: |
703d9522 |
125 | |
126 | sub withdraw { |
8a7ed43e |
127 | my ( $self, $amount ) = @_; |
703d9522 |
128 | my $overdraft_amount = $amount - $self->balance(); |
8a7ed43e |
129 | if ( $self->overdraft_account && $overdraft_amount > 0 ) { |
703d9522 |
130 | $self->overdraft_account->withdraw($overdraft_amount); |
131 | $self->deposit($overdraft_amount); |
132 | } |
133 | $self->SUPER::withdraw($amount); |
134 | } |
135 | |
9cf569f3 |
136 | The benefit of taking the method modifier approach is we do not need |
137 | to remember to call C<SUPER::withdraw> and pass it the C<$amount> |
138 | argument when writing C<< CheckingAccount->withdraw >>. |
139 | |
140 | This is actually more than just a convenience for forgetful |
141 | programmers. Using method modifiers helps isolate subclasses from |
142 | changes in the superclasses. For instance, if B<< |
143 | BankAccount->withdraw >> were to add an additional argument of some |
144 | kind, the version of B<< CheckingAccount->withdraw >> which uses |
145 | C<SUPER::withdraw> would not pass that extra argument correctly, |
146 | whereas the method modifier version would automatically pass along all |
147 | arguments correctly. |
148 | |
149 | Just as with the first recipe, object instantiation uses the C<new> |
150 | method, which accepts named parameters. |
151 | |
152 | my $savings_account = BankAccount->new( balance => 250 ); |
153 | |
703d9522 |
154 | my $checking_account = CheckingAccount->new( |
9cf569f3 |
155 | balance => 100, |
156 | overdraft_account => $savings_account, |
157 | ); |
703d9522 |
158 | |
9cf569f3 |
159 | And as with the first recipe, a more in-depth example can be found in |
c79239a2 |
160 | the F<t/000_recipes/moose_cookbook_basics_recipe2.t> test file. |
703d9522 |
161 | |
162 | =head1 CONCLUSION |
163 | |
ec6df2e6 |
164 | This recipe expanded on the basic concepts from the first recipe with |
165 | a more "real world" use case. |
703d9522 |
166 | |
167 | =head1 FOOTNOTES |
168 | |
169 | =over 4 |
170 | |
171 | =item (1) |
172 | |
9cf569f3 |
173 | If you're paying close attention, you might realize that there's a |
174 | circular loop waiting to happen here. A smarter example would have to |
175 | make sure that we don't accidentally create a loop between the |
176 | checking account and its overdraft account. |
177 | |
178 | =item (2) |
179 | |
180 | In reality, this creation is sensitive to the order in which modules |
181 | are loaded. In more complicated cases, you may find that you need to |
182 | explicitly declare a class type before the corresponding is loaded. |
183 | |
184 | =item (3) |
185 | |
186 | Moose does not attempt to encode a class's is-a relationships within |
187 | the type constraint hierarchy. Instead, Moose just considers the class |
188 | type constraint to be a subtype of C<Object>, and specializes the |
189 | constraint check to allow for subclasses. This means that an instance |
190 | of B<CheckingAccount> will pass a C<BankAccount> type constraint |
191 | successfully. For more details, please refer to the |
192 | L<Moose::Util::TypeConstraints> documentation. |
193 | |
194 | =item (4) |
195 | |
196 | If the overdraft account does not have the amount needed, it will |
197 | throw an error. Of course, the overdraft account could also have |
198 | overdraft protection. See note 1. |
703d9522 |
199 | |
200 | =back |
201 | |
202 | =head1 SEE ALSO |
203 | |
204 | =over 4 |
205 | |
4711f5f7 |
206 | =item Acknowledgment |
703d9522 |
207 | |
9cf569f3 |
208 | The BankAccount example in this recipe is directly taken from the |
c6182301 |
209 | examples in this chapter of "Practical Common Lisp": |
703d9522 |
210 | |
211 | L<http://www.gigamonkeys.com/book/object-reorientation-generic-functions.html> |
212 | |
213 | =back |
214 | |
8c3d5c88 |
215 | =head1 AUTHORS |
471c4f09 |
216 | |
217 | Stevan Little E<lt>stevan@iinteractive.comE<gt> |
218 | |
8c3d5c88 |
219 | Dave Rolsky E<lt>autarch@urth.orgE<gt> |
220 | |
471c4f09 |
221 | =head1 COPYRIGHT AND LICENSE |
222 | |
2840a3b2 |
223 | Copyright 2006-2009 by Infinity Interactive, Inc. |
471c4f09 |
224 | |
225 | L<http://www.iinteractive.com> |
226 | |
227 | This library is free software; you can redistribute it and/or modify |
228 | it under the same terms as Perl itself. |
229 | |
c79239a2 |
230 | =begin testing |
231 | |
232 | my $savings_account; |
233 | |
234 | { |
235 | $savings_account = BankAccount->new( balance => 250 ); |
236 | isa_ok( $savings_account, 'BankAccount' ); |
237 | |
238 | is( $savings_account->balance, 250, '... got the right savings balance' ); |
239 | lives_ok { |
240 | $savings_account->withdraw(50); |
241 | } |
242 | '... withdrew from savings successfully'; |
243 | is( $savings_account->balance, 200, |
244 | '... got the right savings balance after withdrawl' ); |
245 | |
246 | $savings_account->deposit(150); |
247 | is( $savings_account->balance, 350, |
248 | '... got the right savings balance after deposit' ); |
249 | } |
250 | |
251 | { |
252 | my $checking_account = CheckingAccount->new( |
253 | balance => 100, |
254 | overdraft_account => $savings_account |
255 | ); |
256 | isa_ok( $checking_account, 'CheckingAccount' ); |
257 | isa_ok( $checking_account, 'BankAccount' ); |
258 | |
259 | is( $checking_account->overdraft_account, $savings_account, |
260 | '... got the right overdraft account' ); |
261 | |
262 | is( $checking_account->balance, 100, |
263 | '... got the right checkings balance' ); |
264 | |
265 | lives_ok { |
266 | $checking_account->withdraw(50); |
267 | } |
268 | '... withdrew from checking successfully'; |
269 | is( $checking_account->balance, 50, |
270 | '... got the right checkings balance after withdrawl' ); |
271 | is( $savings_account->balance, 350, |
272 | '... got the right savings balance after checking withdrawl (no overdraft)' |
273 | ); |
274 | |
275 | lives_ok { |
276 | $checking_account->withdraw(200); |
277 | } |
278 | '... withdrew from checking successfully'; |
279 | is( $checking_account->balance, 0, |
280 | '... got the right checkings balance after withdrawl' ); |
281 | is( $savings_account->balance, 200, |
282 | '... got the right savings balance after overdraft withdrawl' ); |
283 | } |
284 | |
285 | { |
286 | my $checking_account = CheckingAccount->new( |
287 | balance => 100 |
288 | |
289 | # no overdraft account |
290 | ); |
291 | isa_ok( $checking_account, 'CheckingAccount' ); |
292 | isa_ok( $checking_account, 'BankAccount' ); |
293 | |
294 | is( $checking_account->overdraft_account, undef, |
295 | '... no overdraft account' ); |
296 | |
297 | is( $checking_account->balance, 100, |
298 | '... got the right checkings balance' ); |
299 | |
300 | lives_ok { |
301 | $checking_account->withdraw(50); |
302 | } |
303 | '... withdrew from checking successfully'; |
304 | is( $checking_account->balance, 50, |
305 | '... got the right checkings balance after withdrawl' ); |
306 | |
307 | dies_ok { |
308 | $checking_account->withdraw(200); |
309 | } |
310 | '... withdrawl failed due to attempted overdraft'; |
311 | is( $checking_account->balance, 50, |
312 | '... got the right checkings balance after withdrawl failure' ); |
313 | } |
314 | |
315 | =end testing |
316 | |
e08c54f5 |
317 | =cut |