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