Rename Basics::Recipe1 to Basics::Point_AttributesAndSubclassing
[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
69d65c16 79self-explanatory, as they are just plain old Perl 5 OO. (2)
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
69d65c16 93program (3).
9cf569f3 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
69d65c16 100it will just DWIM (4).
9cf569f3 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
69d65c16 123account (5).
9cf569f3 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
69d65c16 182Note that for simple methods like these, which just manipulate some
183single piece of data, it is often not necessary to write them at all.
184For instance, C<deposit> could be implemented via the C<inc> native
185delegation for counters - see
186L<Moose::Meta::Attribute::Native::Trait::Counter> for more specifics,
187and L<Moose::Meta::Attribute::Native> for a broader overview.
188
189=item (3)
190
9cf569f3 191In reality, this creation is sensitive to the order in which modules
192are loaded. In more complicated cases, you may find that you need to
2dba68c4 193explicitly declare a class type before the corresponding class is
194loaded.
9cf569f3 195
69d65c16 196=item (4)
9cf569f3 197
198Moose does not attempt to encode a class's is-a relationships within
199the type constraint hierarchy. Instead, Moose just considers the class
200type constraint to be a subtype of C<Object>, and specializes the
201constraint check to allow for subclasses. This means that an instance
202of B<CheckingAccount> will pass a C<BankAccount> type constraint
203successfully. For more details, please refer to the
204L<Moose::Util::TypeConstraints> documentation.
205
69d65c16 206=item (5)
9cf569f3 207
208If the overdraft account does not have the amount needed, it will
209throw an error. Of course, the overdraft account could also have
210overdraft protection. See note 1.
703d9522 211
212=back
213
396dba58 214=head1 ACKNOWLEDGMENT
703d9522 215
9cf569f3 216The BankAccount example in this recipe is directly taken from the
c6182301 217examples in this chapter of "Practical Common Lisp":
703d9522 218
219L<http://www.gigamonkeys.com/book/object-reorientation-generic-functions.html>
220
c79239a2 221=begin testing
222
223my $savings_account;
224
225{
226 $savings_account = BankAccount->new( balance => 250 );
227 isa_ok( $savings_account, 'BankAccount' );
228
229 is( $savings_account->balance, 250, '... got the right savings balance' );
b10dde3a 230 is(
231 exception {
232 $savings_account->withdraw(50);
233 },
234 undef,
235 '... withdrew from savings successfully'
236 );
c79239a2 237 is( $savings_account->balance, 200,
2dba68c4 238 '... got the right savings balance after withdrawal' );
c79239a2 239
240 $savings_account->deposit(150);
241 is( $savings_account->balance, 350,
242 '... got the right savings balance after deposit' );
243}
244
245{
246 my $checking_account = CheckingAccount->new(
247 balance => 100,
248 overdraft_account => $savings_account
249 );
250 isa_ok( $checking_account, 'CheckingAccount' );
251 isa_ok( $checking_account, 'BankAccount' );
252
253 is( $checking_account->overdraft_account, $savings_account,
254 '... got the right overdraft account' );
255
256 is( $checking_account->balance, 100,
257 '... got the right checkings balance' );
258
b10dde3a 259 is(
260 exception {
261 $checking_account->withdraw(50);
262 },
263 undef,
264 '... withdrew from checking successfully'
265 );
c79239a2 266 is( $checking_account->balance, 50,
2dba68c4 267 '... got the right checkings balance after withdrawal' );
c79239a2 268 is( $savings_account->balance, 350,
2dba68c4 269 '... got the right savings balance after checking withdrawal (no overdraft)'
c79239a2 270 );
271
b10dde3a 272 is(
273 exception {
274 $checking_account->withdraw(200);
275 },
276 undef,
277 '... withdrew from checking successfully'
278 );
c79239a2 279 is( $checking_account->balance, 0,
2dba68c4 280 '... got the right checkings balance after withdrawal' );
c79239a2 281 is( $savings_account->balance, 200,
2dba68c4 282 '... got the right savings balance after overdraft withdrawal' );
c79239a2 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
b10dde3a 300 is(
301 exception {
302 $checking_account->withdraw(50);
303 },
304 undef,
305 '... withdrew from checking successfully'
306 );
c79239a2 307 is( $checking_account->balance, 50,
2dba68c4 308 '... got the right checkings balance after withdrawal' );
c79239a2 309
b10dde3a 310 isnt(
311 exception {
312 $checking_account->withdraw(200);
313 },
314 undef,
315 '... withdrawal failed due to attempted overdraft'
316 );
c79239a2 317 is( $checking_account->balance, 50,
2dba68c4 318 '... got the right checkings balance after withdrawal failure' );
c79239a2 319}
320
321=end testing
322
e08c54f5 323=cut