From: Stevan Little Date: Sun, 26 Mar 2006 20:15:44 +0000 (+0000) Subject: cooking-with-gas X-Git-Tag: 0_05~57 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=703d9522a59b696a3ade220de01eed471d7df5af;p=gitmo%2FMoose.git cooking-with-gas --- diff --git a/lib/Moose/Cookbook/Recipe1.pod b/lib/Moose/Cookbook/Recipe1.pod index b672444..3156eec 100644 --- a/lib/Moose/Cookbook/Recipe1.pod +++ b/lib/Moose/Cookbook/Recipe1.pod @@ -176,8 +176,9 @@ attributes. It does not I that you pass in the all the attributes, and it will politely ignore any named arguments it does not recognize. -From here, you can use C<$point> and C<$point3d> just as you would -any other Perl 5 object. +From here on, you can use C<$point> and C<$point3d> just as you would +any other Perl 5 object. For a more detailed example of what can be +done, you can refer to the F test file. =head1 CONCLUSION diff --git a/lib/Moose/Cookbook/Recipe2.pod b/lib/Moose/Cookbook/Recipe2.pod index 1786c1b..04c5613 100644 --- a/lib/Moose/Cookbook/Recipe2.pod +++ b/lib/Moose/Cookbook/Recipe2.pod @@ -3,7 +3,7 @@ =head1 NAME -Moose::Cookbook::Recipe2 +Moose::Cookbook::Recipe2 - A simple Bank Account example =head1 SYNOPSIS @@ -39,7 +39,7 @@ Moose::Cookbook::Recipe2 before 'withdraw' => sub { my ($self, $amount) = @_; my $overdraft_amount = $amount - $self->balance(); - if ($overdraft_amount > 0) { + if (self->overdraft_account && $overdraft_amount > 0) { $self->overdraft_account->withdraw($overdraft_amount); $self->deposit($overdraft_amount); } @@ -47,6 +47,160 @@ Moose::Cookbook::Recipe2 =head1 DESCRIPTION +In the first recipe we showed how to build basic Moose classes +whose attributes had various accessor schemes and built in +type constraints. However our objects were very data-oriented, +and did not have many behavioral aspects to them (i.e. - methods). +In this recipe, we will expand upon the concepts from the first +recipe and give a more realistic scenario of more behavior +oriented classes. + +We are using an example of a bank account, which has a standard +account (you can deposit money, withdraw money and check your +current balance), and a checking account which has optional +overdraft protection. The overdraft protection will protect the +owner of the checking account by automatically withdrawing the +needed funds from the overdraft account to ensure that a check +will not bounce. + +Now, onto the code. The first class B introduces a +new attribute feature, that of a default value. + + has 'balance' => (isa => 'Int', is => 'rw', default => 0); + +This tells is that a B has a C attribute, +which is has the C type constraint, a read/write accessor, +and a default value of C<0>. This means that every instance of +B that is created will have it's C slot +initialized to C<0>. Very simple really :) + +Next come the methods. The C and C methods +should be fairly self explanitory, they are nothing specific to +Moose, just your standard Perl 5 OO. + +Now, onto the B class. As you know from the +first recipe, the keyword C sets a class's superclass +relationship. Here we see that B is a +B. The next line introduces yet another new aspect +of Moose, that of class based type-constraints. + + has 'overdraft_account' => (isa => 'BankAccount', is => 'rw'); + +Up until now, we have only had C type constraints, which +(as I said in the first recipe) is a built-in type constraint +that Moose provides for you. The C type constraint +is new, and was actually defined at the moment we created the +B class itself. In fact, for every Moose class that +you define, a corresponding type constraint will be created for +that class. This means that in the first recipe, a C and +C type constraint were created, and in this recipe, both +a C and a C type constraint were +created. Moose does this as a convience for you so that your +class model and the type constraint model can both be kept in +sync with one another. In short, Moose makes sure that it will +just DWIM (1). + +Next, we come to the behavioral part of B, and +again we see a method modifier, but this time we have a C +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 modifier from the first recipe, Moose +will handle calling the superclass method (in this case the +C method). The C modifier shown +above will run (obviously) I the code from the superclass +with run. The C modifier here implements the overdraft +protection by first checking if there are enough available +funds in the checking account and if not (and if there is an overdraft +account available), it transfers the appropriate funds into the +checking account. + +As with the method modifier in the first recipe, there is another +way to accomplish this same thing using the built in C +pseudo-package. So the above method is equivalent to the one here. + + sub withdraw { + my ($self, $amount) = @_; + my $overdraft_amount = $amount - $self->balance(); + if ($overdraft_amount > 0 && $self->overdraft_account) { + $self->overdraft_account->withdraw($overdraft_amount); + $self->deposit($overdraft_amount); + } + $self->SUPER::withdraw($amount); + } + +The benefits of taking the method modifier approach is that the +author of the B subclass does not need to remember +to call C and to pass it the C<$amount> argument. +Instead the method modifier assures that all arguments make it +to the superclass method correctly. But this is actually more +than just a convience for forgetful programmers, it also helps +isolate subclasses from changes in the superclasses. For instance, +if B were to add an additional argument +of some kind, the version of B which +uses C would not pass that extra argument +correctly. Whereas the method modifier version of would pass +all arguments along correctly automatically. + +Just as with the first recipe, object instantiation is a fairly +normal process, here is an example: + + 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 of using +these classes can be found in the F test file. + +=head1 CONCLUSION + +The aim of this recipe was to take the knowledge learned in the +first recipe and expand upon it within a more realistic use case. +I hope that this recipe has accomplished this goal. The next +recipe will expand even more upon the capabilties of attributes +in Moose to create a behaviorally sophisticated class almost +entirely defined by attributes. + +=head1 FOOTNOTES + +=over 4 + +=item (1) + +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, and +specializes the constraint check to allow for subclasses. This +means that an instance of B will pass a +C type constraint successfully. For more details, +please refer to the L documentation. + +=back + +=head1 SEE ALSO + +=over 4 + +=item Acknowledgement + +The BankAccount example in this recipe is directly taken from the +examples in this chapter of "Practical Common Lisp". A link to that +can be found here: + +L + +=back + =head1 AUTHOR Stevan Little Estevan@iinteractive.comE diff --git a/lib/Moose/Cookbook/Recipe3.pod b/lib/Moose/Cookbook/Recipe3.pod index 87ba270..3625a30 100644 --- a/lib/Moose/Cookbook/Recipe3.pod +++ b/lib/Moose/Cookbook/Recipe3.pod @@ -3,7 +3,7 @@ =head1 NAME -Moose::Cookbook::Recipe3 +Moose::Cookbook::Recipe3 - A binary tree =head1 SYNOPSIS diff --git a/t/002_basic.t b/t/002_basic.t index ec98fd0..73a6756 100644 --- a/t/002_basic.t +++ b/t/002_basic.t @@ -3,7 +3,7 @@ use strict; use warnings; -use Test::More tests => 16; +use Test::More tests => 24; use Test::Exception; BEGIN { @@ -43,7 +43,7 @@ BEGIN { before 'withdraw' => sub { my ($self, $amount) = @_; my $overdraft_amount = $amount - $self->balance(); - if ($overdraft_amount > 0) { + if (self->overdraft_account && $overdraft_amount > 0) { $self->overdraft_account->withdraw($overdraft_amount); $self->deposit($overdraft_amount); } @@ -62,26 +62,52 @@ is($savings_account->balance, 200, '... got the right savings balance after with $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'); +{ + 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'); + + lives_ok { + $checking_account->withdraw(50); + } '... withdrew from checking successfully'; + is($checking_account->balance, 50, '... got the right checkings balance after withdrawl'); + is($savings_account->balance, 350, '... got the right savings balance after checking withdrawl (no overdraft)'); + + lives_ok { + $checking_account->withdraw(200); + } '... withdrew from checking successfully'; + is($checking_account->balance, 0, '... got the right checkings balance after withdrawl'); + is($savings_account->balance, 200, '... got the right savings balance after overdraft withdrawl'); +} -lives_ok { - $checking_account->withdraw(50); -} '... withdrew from checking successfully'; -is($checking_account->balance, 50, '... got the right checkings balance after withdrawl'); -is($savings_account->balance, 350, '... got the right savings balance after checking withdrawl (no overdraft)'); +{ + 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'); + + lives_ok { + $checking_account->withdraw(50); + } '... withdrew from checking successfully'; + is($checking_account->balance, 50, '... got the right checkings balance after withdrawl'); + + dies_ok { + $checking_account->withdraw(200); + } '... withdrawl failed due to attempted overdraft'; + is($checking_account->balance, 50, '... got the right checkings balance after withdrawl failure'); +} -lives_ok { - $checking_account->withdraw(200); -} '... withdrew from checking successfully'; -is($checking_account->balance, 0, '... got the right checkings balance after withdrawl'); -is($savings_account->balance, 200, '... got the right savings balance after overdraft withdrawl');