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