Commit | Line | Data |
471c4f09 |
1 | |
2 | =pod |
3 | |
4 | =head1 NAME |
5 | |
3824830b |
6 | Moose::Cookbook::Recipe2 - A simple B<BankAccount> example |
471c4f09 |
7 | |
8 | =head1 SYNOPSIS |
9 | |
10 | package BankAccount; |
471c4f09 |
11 | use Moose; |
12 | |
13 | has 'balance' => (isa => 'Int', is => 'rw', default => 0); |
14 | |
15 | sub deposit { |
16 | my ($self, $amount) = @_; |
17 | $self->balance($self->balance + $amount); |
18 | } |
19 | |
20 | sub withdraw { |
21 | my ($self, $amount) = @_; |
22 | my $current_balance = $self->balance(); |
23 | ($current_balance >= $amount) |
24 | || confess "Account overdrawn"; |
25 | $self->balance($current_balance - $amount); |
26 | } |
27 | |
28 | package CheckingAccount; |
471c4f09 |
29 | use Moose; |
30 | |
31 | extends 'BankAccount'; |
32 | |
33 | has 'overdraft_account' => (isa => 'BankAccount', is => 'rw'); |
34 | |
35 | before 'withdraw' => sub { |
cdcae970 |
36 | my ($self, $amount) = @_; |
37 | my $overdraft_amount = $amount - $self->balance(); |
e9bb8a31 |
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 | |
703d9522 |
46 | In the first recipe we showed how to build basic Moose classes |
47 | whose attributes had various accessor schemes and built in |
48 | type constraints. However our objects were very data-oriented, |
49 | and did not have many behavioral aspects to them (i.e. - methods). |
50 | In this recipe, we will expand upon the concepts from the first |
51 | recipe and give a more realistic scenario of more behavior |
52 | oriented classes. |
53 | |
54 | We are using an example of a bank account, which has a standard |
55 | account (you can deposit money, withdraw money and check your |
56 | current balance), and a checking account which has optional |
57 | overdraft protection. The overdraft protection will protect the |
58 | owner of the checking account by automatically withdrawing the |
59 | needed funds from the overdraft account to ensure that a check |
60 | will not bounce. |
61 | |
62 | Now, onto the code. The first class B<BankAccount> introduces a |
63 | new attribute feature, that of a default value. |
64 | |
65 | has 'balance' => (isa => 'Int', is => 'rw', default => 0); |
66 | |
e08c54f5 |
67 | This tells us that a B<BankAccount> has a C<balance> attribute, |
68 | which has the C<Int> type constraint, a read/write accessor, |
703d9522 |
69 | and a default value of C<0>. This means that every instance of |
e08c54f5 |
70 | B<BankAccount> that is created will have its C<balance> slot |
703d9522 |
71 | initialized to C<0>. Very simple really :) |
72 | |
73 | Next come the methods. The C<deposit> and C<withdraw> methods |
4711f5f7 |
74 | should be fairly self explanatory, they are nothing specific to |
703d9522 |
75 | Moose, just your standard Perl 5 OO. |
76 | |
77 | Now, onto the B<CheckingAccount> class. As you know from the |
78 | first recipe, the keyword C<extends> sets a class's superclass |
79 | relationship. Here we see that B<CheckingAccount> is a |
80 | B<BankAccount>. The next line introduces yet another new aspect |
81 | of Moose, that of class based type-constraints. |
82 | |
83 | has 'overdraft_account' => (isa => 'BankAccount', is => 'rw'); |
84 | |
85 | Up until now, we have only had C<Int> type constraints, which |
86 | (as I said in the first recipe) is a built-in type constraint |
87 | that Moose provides for you. The C<BankAccount> type constraint |
88 | is new, and was actually defined at the moment we created the |
89 | B<BankAccount> class itself. In fact, for every Moose class that |
90 | you define, a corresponding type constraint will be created for |
91 | that class. This means that in the first recipe, a C<Point> and |
92 | C<Point3D> type constraint were created, and in this recipe, both |
93 | a C<BankAccount> and a C<CheckingAccount> type constraint were |
e08c54f5 |
94 | created. Moose does this as a convenience for you so that your |
703d9522 |
95 | class model and the type constraint model can both be kept in |
96 | sync with one another. In short, Moose makes sure that it will |
97 | just DWIM (1). |
98 | |
99 | Next, we come to the behavioral part of B<CheckingAccount>, and |
100 | again we see a method modifier, but this time we have a C<before> |
101 | modifier. |
102 | |
103 | before 'withdraw' => sub { |
104 | my ($self, $amount) = @_; |
105 | my $overdraft_amount = $amount - $self->balance(); |
e9bb8a31 |
106 | if ($self->overdraft_account && $overdraft_amount > 0) { |
703d9522 |
107 | $self->overdraft_account->withdraw($overdraft_amount); |
108 | $self->deposit($overdraft_amount); |
109 | } |
110 | }; |
111 | |
112 | Just as with the C<after> modifier from the first recipe, Moose |
113 | will handle calling the superclass method (in this case the |
114 | C<BankAccount::withdraw> method). The C<before> modifier shown |
115 | above will run (obviously) I<before> the code from the superclass |
116 | with run. The C<before> modifier here implements the overdraft |
117 | protection by first checking if there are enough available |
118 | funds in the checking account and if not (and if there is an overdraft |
119 | account available), it transfers the appropriate funds into the |
120 | checking account. |
121 | |
122 | As with the method modifier in the first recipe, there is another |
123 | way to accomplish this same thing using the built in C<SUPER::> |
124 | pseudo-package. So the above method is equivalent to the one here. |
125 | |
126 | sub withdraw { |
127 | my ($self, $amount) = @_; |
128 | my $overdraft_amount = $amount - $self->balance(); |
e9bb8a31 |
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 | |
136 | The benefits of taking the method modifier approach is that the |
137 | author of the B<BankAccount> subclass does not need to remember |
138 | to call C<SUPER::withdraw> and to pass it the C<$amount> argument. |
139 | Instead the method modifier assures that all arguments make it |
140 | to the superclass method correctly. But this is actually more |
e08c54f5 |
141 | than just a convenience for forgetful programmers, it also helps |
703d9522 |
142 | isolate subclasses from changes in the superclasses. For instance, |
143 | if B<BankAccount::withdraw> were to add an additional argument |
144 | of some kind, the version of B<CheckingAccount::withdraw> which |
145 | uses C<SUPER::withdraw> would not pass that extra argument |
e08c54f5 |
146 | correctly. Whereas the method modifier version would automatically pass |
147 | along all arguments correctly. |
703d9522 |
148 | |
149 | Just as with the first recipe, object instantiation is a fairly |
150 | normal process, here is an example: |
151 | |
152 | my $savings_account = BankAccount->new(balance => 250); |
153 | my $checking_account = CheckingAccount->new( |
154 | balance => 100, |
155 | overdraft_account => $savings_account |
156 | ); |
157 | |
158 | And as with the first recipe, a more in-depth example of using |
db1ab48d |
159 | these classes can be found in the F<t/002_recipe.t> test file. |
703d9522 |
160 | |
161 | =head1 CONCLUSION |
162 | |
163 | The aim of this recipe was to take the knowledge learned in the |
164 | first recipe and expand upon it within a more realistic use case. |
165 | I hope that this recipe has accomplished this goal. The next |
4711f5f7 |
166 | recipe will expand even more upon the capabilities of attributes |
703d9522 |
167 | in Moose to create a behaviorally sophisticated class almost |
168 | entirely defined by attributes. |
169 | |
170 | =head1 FOOTNOTES |
171 | |
172 | =over 4 |
173 | |
174 | =item (1) |
175 | |
176 | Moose does not attempt to encode a class's is-a relationships |
177 | within the type constraint hierarchy. Instead Moose just considers |
178 | the class type constraint to be a subtype of C<Object>, and |
179 | specializes the constraint check to allow for subclasses. This |
180 | means that an instance of B<CheckingAccount> will pass a |
181 | C<BankAccount> type constraint successfully. For more details, |
182 | please refer to the L<Moose::Util::TypeConstraints> documentation. |
183 | |
184 | =back |
185 | |
186 | =head1 SEE ALSO |
187 | |
188 | =over 4 |
189 | |
4711f5f7 |
190 | =item Acknowledgment |
703d9522 |
191 | |
192 | The BankAccount example in this recipe is directly taken from the |
193 | examples in this chapter of "Practical Common Lisp". A link to that |
194 | can be found here: |
195 | |
196 | L<http://www.gigamonkeys.com/book/object-reorientation-generic-functions.html> |
197 | |
198 | =back |
199 | |
471c4f09 |
200 | =head1 AUTHOR |
201 | |
202 | Stevan Little E<lt>stevan@iinteractive.comE<gt> |
203 | |
204 | =head1 COPYRIGHT AND LICENSE |
205 | |
b77fdbed |
206 | Copyright 2006, 2007 by Infinity Interactive, Inc. |
471c4f09 |
207 | |
208 | L<http://www.iinteractive.com> |
209 | |
210 | This library is free software; you can redistribute it and/or modify |
211 | it under the same terms as Perl itself. |
212 | |
e08c54f5 |
213 | =cut |