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