typo fixes
[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
2c739d1a 162the F<t/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
2dba68c4 184explicitly declare a class type before the corresponding class is
185loaded.
9cf569f3 186
187=item (3)
188
189Moose does not attempt to encode a class's is-a relationships within
190the type constraint hierarchy. Instead, Moose just considers the class
191type constraint to be a subtype of C<Object>, and specializes the
192constraint check to allow for subclasses. This means that an instance
193of B<CheckingAccount> will pass a C<BankAccount> type constraint
194successfully. For more details, please refer to the
195L<Moose::Util::TypeConstraints> documentation.
196
197=item (4)
198
199If the overdraft account does not have the amount needed, it will
200throw an error. Of course, the overdraft account could also have
201overdraft protection. See note 1.
703d9522 202
203=back
204
205=head1 SEE ALSO
206
207=over 4
208
4711f5f7 209=item Acknowledgment
703d9522 210
9cf569f3 211The BankAccount example in this recipe is directly taken from the
c6182301 212examples in this chapter of "Practical Common Lisp":
703d9522 213
214L<http://www.gigamonkeys.com/book/object-reorientation-generic-functions.html>
215
216=back
217
c79239a2 218=begin testing
219
220my $savings_account;
221
222{
223 $savings_account = BankAccount->new( balance => 250 );
224 isa_ok( $savings_account, 'BankAccount' );
225
226 is( $savings_account->balance, 250, '... got the right savings balance' );
b10dde3a 227 is(
228 exception {
229 $savings_account->withdraw(50);
230 },
231 undef,
232 '... withdrew from savings successfully'
233 );
c79239a2 234 is( $savings_account->balance, 200,
2dba68c4 235 '... got the right savings balance after withdrawal' );
c79239a2 236
237 $savings_account->deposit(150);
238 is( $savings_account->balance, 350,
239 '... got the right savings balance after deposit' );
240}
241
242{
243 my $checking_account = CheckingAccount->new(
244 balance => 100,
245 overdraft_account => $savings_account
246 );
247 isa_ok( $checking_account, 'CheckingAccount' );
248 isa_ok( $checking_account, 'BankAccount' );
249
250 is( $checking_account->overdraft_account, $savings_account,
251 '... got the right overdraft account' );
252
253 is( $checking_account->balance, 100,
254 '... got the right checkings balance' );
255
b10dde3a 256 is(
257 exception {
258 $checking_account->withdraw(50);
259 },
260 undef,
261 '... withdrew from checking successfully'
262 );
c79239a2 263 is( $checking_account->balance, 50,
2dba68c4 264 '... got the right checkings balance after withdrawal' );
c79239a2 265 is( $savings_account->balance, 350,
2dba68c4 266 '... got the right savings balance after checking withdrawal (no overdraft)'
c79239a2 267 );
268
b10dde3a 269 is(
270 exception {
271 $checking_account->withdraw(200);
272 },
273 undef,
274 '... withdrew from checking successfully'
275 );
c79239a2 276 is( $checking_account->balance, 0,
2dba68c4 277 '... got the right checkings balance after withdrawal' );
c79239a2 278 is( $savings_account->balance, 200,
2dba68c4 279 '... got the right savings balance after overdraft withdrawal' );
c79239a2 280}
281
282{
283 my $checking_account = CheckingAccount->new(
284 balance => 100
285
286 # no overdraft account
287 );
288 isa_ok( $checking_account, 'CheckingAccount' );
289 isa_ok( $checking_account, 'BankAccount' );
290
291 is( $checking_account->overdraft_account, undef,
292 '... no overdraft account' );
293
294 is( $checking_account->balance, 100,
295 '... got the right checkings balance' );
296
b10dde3a 297 is(
298 exception {
299 $checking_account->withdraw(50);
300 },
301 undef,
302 '... withdrew from checking successfully'
303 );
c79239a2 304 is( $checking_account->balance, 50,
2dba68c4 305 '... got the right checkings balance after withdrawal' );
c79239a2 306
b10dde3a 307 isnt(
308 exception {
309 $checking_account->withdraw(200);
310 },
311 undef,
312 '... withdrawal failed due to attempted overdraft'
313 );
c79239a2 314 is( $checking_account->balance, 50,
2dba68c4 315 '... got the right checkings balance after withdrawal failure' );
c79239a2 316}
317
318=end testing
319
e08c54f5 320=cut