|
NAMEClass::StateMachine - define classes for state machinesSYNOPSISpackage MySM; no warnings 'redefine'; use parent 'Class::StateMachine'; sub foo : OnState(one) { print "on state one\n" } sub foo : OnState(two) { print "on state two\n" } sub bar : OnState(__any__) { print "default action\n" } sub bar : OnState(three, five, seven) { print "on several states\n" } sub bar : OnState(one) { print "on state one\n" } sub new { my $class = shift; my $self = {}; Class::StateMachine::bless $self, $class, 'one'; $self; } sub leave_state : OnState(one) { print "leaving state $_[1] from $_[2]" } sub enter_state : OnState(two) { print "entering state $_[1] from $_[2]" } package main; my $sm = MySM->new; $sm->state('one'); $sm->foo; # prints "on state one" $sm->state('two'); $sm->foo; # prints "on state two" DESCRIPTIONThis module allows to build classes whose instance behavior (methods) depends not only on inheritance but also on some internal state.For example, suppose we want to develop a Dog class implementing the following behavior: my $dog = Dog->new; $dog->state("happy"); $dog->on_touched_head; # the dog moves his tail $dog->state("angry"); $dog->on_touched_head; # the dog bites you With the help of Class::StateMachine, that state dependant behaviour can be easily programmed using the "OnState" subroutine attribute as follows: package Dog; use parent 'Class::StateMachine'; sub on_touched_head : OnState(happy) { shift->move_tail } sub on_touched_head : OnState(angry) { shift->bite } Object constructionClass::StateMachine does not impose any particular type of data structure for the instance objects. Any Perl reference type (HASH, ARRAY, SCALAR, GLOB, etc.) can be used.The unique condition that must be fulfilled is to use the "bless" subroutine provided by Class::StateMachine to create the object instead of the Perl builtin of the same name. For instance: package Dog; sub new { my $class = shift; my $dog = { name => 'Oscar' }; Class::StateMachine::bless($dog, $class, 'happy'); } A default state "new" gets assigned to the object when the third parameter to "Class::StateMachine::bless" is omitted. Instance stateThe instance state is maintained internally by Class::StateMachine and can be accessed though the "state" method:my $state = $dog->state; State changes must be performed explicitly calling the "state" method with the new state as an argument: $dog->state('tired'); Class::StateMachine will not change the state of your objects in any other way. If you want to limit the possible set of states that the objects of some class can take, define a "state_check" method for that class: package Dog; ... sub state_check { my ($self, $state) = @_; $state =~ /^(?:happy|angry|tired)$/ } That will cause to die any call to "state" requesting a change to an invalid state. New objects get assigned the state 'new' when they are created. Method definitionInside a class derived from Class::StateMachine, methods (submethods) can be assigned to some particular states using the "OnState" attribute with a list of the states where it applies.sub bark :OnState(happy, tired) { play "happy_bark.wav" } sub bark :OnState(injured) { play "pitiful_bark.wav" } The text inside the "OnState" parents is evaluated in list context on the current package and with strictures turned off in order to allow usage of barewords. For instance: sub foo : OnState(map "foo$_", a..z) { ... } Though note that lexicals variables will not be reachable from the text inside the parents. Note also that Perl does not allow attribute declarations to spawn over several lines. A special state "__any__" can be used to indicate a default submethod that is called in case a specific submethod has not been declared for the current object state. For instance: sub happy :OnState(happy ) { say "I am happy" } sub happy :OnState(__any__) { say "I am not happy" } Method resolution orderWhat happens when you declare submethods spread among a class inheritance hierarchy?Class::StateMachine will search for the method as follows:
mro can be used to set the search order inside the inheritance trees (for instance, the default deep-first or C3). State transitionsWhen an object changes between two different states, the methods "leave_state" and "enter_state" are called if they are defined.Note that they can be defined using the "OnState" attribute: package Dog; ... sub enter_state :OnState(angry) { shift->bark } sub enter_state :OnState(tired) { shift->lie_down } The method "on_leave_state" can also be used to register per-object callbacks that are run just before changing the object state. APIThese are the methods available from Class::StateMachine:
DebuggingClass::StateMachine supports a debugging mode that prints traces of state changes and callback invocation. It can be enabled as follows:$Class::StateMachine::debug = 1; InternalsThis module internally plays with the inheritance chain creating new classes and reblessing objects on the fly and (ab)using the mro mechanism in funny ways.The objects state is maintained using Hash::Util::FieldHash objects. BUGSBackward compatibility has been broken in version 0.13 in order to actualize the class to use modern Perl features as MRO and provide saner semantics.Passing several states in the same submethod definition can break the "next::method" machinery from the "mro" package. For instance: sub foo :OnState(one, two, three) { shift->next::method(@_) } may not work as expected. SEE ALSOattributes, perlsub, perlmod, Attribute::Handlers, mro, MRO::Define.The "dog.pl" example included within the package. COPYRIGHT AND LICENSECopyright (C) 2003-2006, 2011-2014 by Salvador Fandiño (sfandino@yahoo.com).This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
Visit the GSP FreeBSD Man Page Interface. |