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