Moose::Cookbook::Basics::BankAccount_MethodModifiersAndSubclassing - Demonstrates the use of method modifiers in a subclass |
Moose::Cookbook::Basics::BankAccount_MethodModifiersAndSubclassing - Demonstrates the use of method modifiers in a subclass
version 2.1605
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); } };
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, BankAccount, introduces a new attribute feature, a default value:
has 'balance' => ( isa => 'Int', is => 'rw', default => 0 );
This says that a BankAccount has a balance
attribute, which has
an Int
type constraint, a read/write accessor, and a default value
of 0
. This means that every instance of BankAccount that is
created will have its balance
slot initialized to 0
, unless some
other value is provided to the constructor.
The deposit
and 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 extends
sets a
class's superclass. Here we see that CheckingAccount extends
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 Int
type constraint, which (as
we saw in the first recipe) is a builtin type constraint. The
BankAccount
type constraint is new, and was actually defined the
moment we created the 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 Point
and
Point3D
were created. In this recipe, both BankAccount
and
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 CheckingAccount, we see another method modifier, the 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 after
modifier from the first recipe, Moose will
handle calling the superclass method (in this case <
BankAccount-
withdraw >>).
The before
modifier will (obviously) run before the code from
the superclass is run. Here, 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
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 SUPER::withdraw
and pass it the $amount
argument when writing 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 <
BankAccount-withdraw >> were to add an additional argument of some
kind, the version of CheckingAccount-withdraw >> which uses
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 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 t/recipes/basics_bankaccount_methodmodifiersandsubclassing.t test file.
This recipe expanded on the basic concepts from the first recipe with a more ``real world'' use case.
deposit
could be implemented via the inc
native
delegation for counters - see
the Moose::Meta::Attribute::Native::Trait::Counter manpage for more specifics,
and the Moose::Meta::Attribute::Native manpage for a broader overview.
Object
, and specializes the
constraint check to allow for subclasses. This means that an instance
of CheckingAccount will pass a BankAccount
type constraint
successfully. For more details, please refer to the
the Moose::Util::TypeConstraints manpage documentation.
The BankAccount example in this recipe is directly taken from the examples in this chapter of ``Practical Common Lisp'':
http://www.gigamonkeys.com/book/object-reorientation-generic-functions.html
This software is copyright (c) 2006 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.
Moose::Cookbook::Basics::BankAccount_MethodModifiersAndSubclassing - Demonstrates the use of method modifiers in a subclass |