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