Target Audience: This series is pitched at developers interested in Object Oriented Design Patterns using the Moose framework in Perl. I cover the examples from the Head First Design Patterns book, replicating Java implementation in Moose. You need to obtain a copy of the book, which is not hard to come by.
Target Audience: This series is pitched at developers interested in Object Oriented Design Patterns using the Moose framework in Perl. I cover the examples from the Head First Design Patterns book, replicating Java implementation in Moose. You need to obtain a copy of the book, which is not hard to come by.
I like to talk of State Machines. Because they are so natural. I like to think about interesting elements the world about me in terms of State Machines. Take girlfriend or spouse, for instance, their behavior depends so much on their state..
Jokes apart, what I've hinted at is the essence of a State Machine. Behavior that is regulated by State. Or stated in Object-Oriented parlance, the implementation of a method varies according to the state of the object.
Obviously, that leaves us but one choice for dealing with these Jekyl-Hydesque objects - to encapsulate behavior in 'State' classes. And that's all about to make sense.
The brains at Head First cite the example of Gumball Machine which has various methods like 'insert quarter', 'turn crank', 'dispense gumball', etc., which all depend on the state of the machine, i.e. if the machine has a quarter in the slot then turning the crank delivers a gumball. To illustrate this, the folks at HFDP show a state diagram. The states are circles and the arrows are actions (methods) that typically produce a change of state. Neato!
(Now if I could do this with the women in my life... sigh!)
The implementation consists of encapsulating behavior in an abstract class 'State' and providing method definitions in concrete classes corresponding to each of various possible States. For example, 'turning crank' when the machine state is 'has quarter' leads to dispensation of a gumball but when there is 'no quarter' or the machine is 'sold out' doesn't produce the same result.
Here then is the State class:
package State;
use Moose;
has 'gumballMachine' => (
is => 'rw',
isa => 'GumballMachine',
predicate => 'has_gumballMachine',
);
sub insertQuarter {};
sub ejectQuarter {};
sub turnCrank {};
sub dispense {};
When the machine has a quarter, the state is 'HasQuarterState'.
package HasQuarterState;
use Moose;
use GumballMachine;
extends 'State';
sub insertQuarter {
print qq{You cannot insert a quarter now.};
};
sub ejectQuarter {
my $self = shift;
print qq{Here's your quarter back.};
$self->gumballMachine->currentState($self->gumballMachine->noQuarterState);
};
sub turnCrank {
my $self = shift;
print qq{You turned...};
$self->gumballMachine->currentState($self->gumballMachine->soldState);
};
sub dispense {
print qq{No gumball dispensation.};
};
Notice - the action only happens upon two events: turning the crank and eject. The rest of the methods just mumble on standard output.
Now here's the thing! The Machine (which we are about to see) delegates behavior to an object of the State class for implementation, according to current state. That means the Machine has got to hold objects of State class, one for each state. And each of these objects in turn own a copy of (i.e. reference to) the Machine. At any time, one (and only one) among the State objects of a Machine is set to the current state.
That's so something like this makes sense:
$self->gumballMachine->currentState($self->gumballMachine->soldState);
Notice how this action changes the current State of the Gumball Machine to a new State.
The underlying principle is Composition. It's something Beethoven did a lot of, before he started decomposing. The Machine is composed of States. Savvy?
Here then, is the Machine class:
package GumballMachine;
use Moose;
use State;
has 'hasQuarterState' => (
is => 'rw',
isa => 'State',
default => sub { HasQuarterState->new(gumballMachine => $_[0]); }
);
has 'soldState' => (
is => 'rw',
isa => 'State',
default => sub { SoldState->new(gumballMachine => $_[0]); }
);
has 'noQuarterState' => (
is => 'rw',
isa => 'State',
default => sub { NoQuarterState->new(gumballMachine => $_[0]); }
);
has 'soldOutState' => (
is => 'rw',
isa => 'State',
default => sub { SoldOutState->new(gumballMachine => $_[0]); }
);
has 'count' => (
is => 'rw',
isa => 'Num',
default => 0,
);
has 'currentState' => (
is => 'rw',
isa => 'State',
default => sub { $_[0]->{noQuarterState} },
);
sub releaseBall {
my $self = shift;
print qq{"Releasing a gumball.."};
$self->count($self->count - 1);
print $self->count." remaining.";
}
sub insertQuarter {
my $self = shift;
$self->currentState->insertQuarter;
}
sub ejectQuarter {
my $self = shift;
$self->currentState->ejectQuarter;
}
sub turnCrank {
my $self = shift;
$self->currentState->turnCrank;
}
sub dispense {
my $self = shift;
$self->currentState->dispense;
}
1;
A few notes on Moose:
1.
Note the keyword 'extends' for inheritance. The abstract class is simply implemented with empty methods.
2.
Note the use of default attribute to populate slots. The default holds a sub reference. For reference to self in the default definition, $_[0] applies.
3.
Note the use of delegation and composition. The Machine is composed of States and delegates actions to the object of State class representing its current state. These are extremely powerful concepts.
One area where I have found it useful to deploy these concepts is inter-operable document standards - when converting between standard XML namespaces. My target data-structure delegates parsing to a streaming SAX parser which packages information from the source file into neat parcels. The delegatee then ships out ready parcels to the target data-structure's container classes.
4.
I have used an abstract class instead of a role. Though roles, in general, are more strongly associated with composition, I believe this is largely a matter of semantics. Notice, I could have used the Method Modifier 'after' instead of over-riding methods from the abstract class. You will find Method Modifiers used in my latest code.
~ * ~
All code in this series may be downloaded from:
http://sites.google.com/site/sanjaybhatikar/codeunquote/designpatterns-1
http://sites.google.com/site/sanjaybhatikar/codeunquote/designpatterns-1
No comments:
Post a Comment