state pattern?

  • Thread starter Michael 'entropie' Trommer
  • Start date
M

Michael 'entropie' Trommer

--E39vaYmALEf/7YXx
Content-Type: multipart/mixed; boundary="OXfL5xGRrasGEqWY"
Content-Disposition: inline


--OXfL5xGRrasGEqWY
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

Hello,

I found this State-pattern example on [1], and was interested to
find a way to make this more useable for standard classes (not just
servers as in the example)

I'am not so skilled in OO coding and ruby, so iam interested what you all think.
(and is it another implementation of the state pattern or iam on the
wrong path?)


[1] http://www.rubygarden.org/ruby?StatePattern

So long
--
Michael 'entropie' Trommer; http://ackro.org

ruby -e "0.upto((a='njduspAhnbjm/dpn').size-1){|x| a[x]-=1}; p 'mailto:'+a"

--OXfL5xGRrasGEqWY
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="state_pattern.rb"
Content-Transfer-Encoding: quoted-printable

# =3D Example
# class StateTest
#
# include StateHelper
#
# def denied_during_curr_state_msg(meth, state)
# "my method saz now: calling `#{meth}` during `#{state}` not allowed"
# end
#=09
# def initialize
# @state =3D StateHelper::StateOff
# end
#
# def on_msg(msg)
# true
# end
#
# def off_bar
# true
# end
#
# def meth
# puts "sup?"
# end
# end
#
# a =3D StateTest.new # nothing cool happend
# a.msg "foo" # is not allowed, because msg has the prefix `on`, rai=
se NotAllowedDuringState
# a.connect # toggles state, comes from module StateHelper
# a.msg "bar" # is now allowed, raise NotAllowedDuringState error
# a.connect # not allowed, because we are already "connected", rai=
se NotAllowedDuringState
# a.disconnect # allowed, comes as +connect* from StateHelper module
# a.meth # returns "sup?" ;)
# a.foo # raise a NoMethodError
module StateHelper

# def[ine] this method with argument <tt>meth</tt> and <tt>state</tt> for
# yourself if you want
DefaultErrDef =3D 'denied_during_curr_state_msg'

class NotAllowedDuringState < Exception; end
class StateOn < TrueClass; end
class StateOff < FalseClass; end=09

def state?; @state; end
=09
# toggles @state
def toggle_state
@state =3D (state? =3D=3D StateOn) ? StateOff : StateOn
puts "state is now #{@state}"
end

# returns both state methods as array
# first one is method for actual state, second only for debugging
def mkMethodString(meth, state)
arr =3D "off_#{meth}", "on_#{meth}"
if @state.superclass =3D=3D TrueClass
return arr.reverse
end
arr
end
=09
# grep virtual method call and check allowed during @state
def method_missing(meth, *par)
n, f =3D mkMethodString(meth, @state)

if self.methods.include?(n)
self.send(n, *par)
elsif self.methods.include?(f)
raise NotAllowedDuringState, denied_during_curr_state_msg(meth, @state)
else
# raise original expetion
raise NoMethodError, "undefined method `#{meth}' #{self.inspect}"
end
end

# toggles state
alias :eek:ff_connect :toggle_state
# toggles state
alias :eek:n_disconnect :toggle_state

# ovewrite it if you need to
def initialize
@state =3D StateHelper::StateOff
end

end


class StateTest
include StateHelper

# used to overwrite error msg... i dunno..
def denied_during_curr_state_msg(meth, state)
"my error msg is cooler and saz: calling `#{meth}` during `#{state}` not =
allowed"
end
=09
def on_msg(msg)
true
end

def off_bar
true
end

def meth
puts "sup?"
end
end

a=3DStateTest.new
a.msg "foo" # is not allowed, because msg has the prefix `on`
a.connect # toggles state, comes from module StateHelper
a.msg "bar" # is now allowed
a.connect # not allowed, because we are already "connected"
a.disconnect # allowed
a.meth # returns "sup?" ;)
a.foo



--OXfL5xGRrasGEqWY--

--E39vaYmALEf/7YXx
Content-Type: application/pgp-signature
Content-Disposition: inline

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.2 (GNU/Linux)

iD8DBQFDvxKOBBd8ye5RguQRAiE/AJ9mqEc1GoZDWkHvwfz61Hul1FgPpQCgwh/q
YqK/fEvjaVim8sO8MIAjEcc=
=BTFJ
-----END PGP SIGNATURE-----

--E39vaYmALEf/7YXx--
 
R

Robert Klemme

Michael 'entropie' Trommer said:
Hello,

I found this State-pattern example on [1], and was interested to
find a way to make this more useable for standard classes (not just
servers as in the example)

I'am not so skilled in OO coding and ruby, so iam interested what you
all think. (and is it another implementation of the state pattern or
iam on the
wrong path?)

Hm, I think you probably did not get there completely. For example the
toggle state metchod is typically implemented in the each state class (every
state knows the state that follows him under certain conditions, like a
finite state automata distributed across several classes). Also inheriting
TrueClass and FalseClass is generally not a good idea (instances of
FalseClass's subclass won't be treated as false).

Have a look at this

class StateTest
BaseState = Struct.new :eek:wner
class StateError < Exception; end

class StateOn < BaseState
attr_accessor :target
def connect(target) raise StateError, "already connected" end
def disconnect()
return StateOff.new(owner), true
end
def description() [self, "We're connected to #{target}"] end
end

class StateOff < BaseState
def connect(target)
st = StateOn.new(owner)
st.target=target
return st, true
end

def disconnect() raise StateError, "already disconnected" end
def description() [self, "We're disconnected"] end
end

def initialize
@state = StateOff.new(self)
end

def method_missing(m,*a,&b)
@state, result = @state.send(m,*a,&b)
result
end
end

irb(main):036:0> t = StateTest.new
=> #<StateTest:0x101a6cc0 @state=#<struct StateTest::StateOff
owner=#<StateTest:0x101a6cc0 ...>>>
irb(main):037:0> t.description
=> "We're disconnected"
irb(main):038:0> t.connect "foo"
=> true
irb(main):039:0> t.description
=> "We're connected to foo"
irb(main):040:0> t.disconnect
=> true
irb(main):041:0> t.description
=> "We're disconnected"
irb(main):042:0> t.disconnect
(irb):21:in `disconnect': already disconnected (StateTest::StateError)
from (irb):32:in `method_missing'
from (irb):42:in `irb_binding'
from /usr/lib/ruby/1.8/irb/workspace.rb:52:in `irb_binding'
from :0

Of course you could choose different ways to update the owner's state so you
don't have to use two return values for all methods. For example

class StateTest
BaseState = Struct.new :eek:wner
class BaseState
private
def next_state(new_state)
owner.instance_eval { @state = new_state }
end
end

class StateOff < BaseState
def connect(target)
owner.instance_eval { @connection = target }
next_state StateOn.new(owner)
true
end
....

Other optimizations are also possible, for example caching state instance if
there are a lot state changes and they should be fast.

Kind regards

robert
 
M

Michael 'entropie' Trommer

--HcAYCG3uE/tztfnV
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable


Hello Robert,

* Robert Klemme ([email protected]) said:
Michael 'entropie' Trommer said:
Hello,

I found this State-pattern example on [1], and was interested to
find a way to make this more useable for standard classes (not just
servers as in the example)

I'am not so skilled in OO coding and ruby, so iam interested what you
all think. (and is it another implementation of the state pattern or
iam on the
wrong path?)
=20
Hm, I think you probably did not get there completely. For example the= =20
toggle state metchod is typically implemented in the each state class=20
(every state knows the state that follows him under certain conditions,= =20
like a finite state automata distributed across several classes). Also= =20
inheriting TrueClass and FalseClass is generally not a good idea (instanc= es=20
of FalseClass's subclass won't be treated as false).
Ok.

[...]
Other optimizations are also possible, for example caching state
instance =20
if there are a lot state changes and they should be fast.

Thanks much! I will take a deeper look.
This was very helpful.

So long
--
Michael 'entropie' Trommer; http://ackro.org

ruby -e "0.upto((a=3D'njduspAhnbjm/dpn').size-1){|x| a[x]-=3D1}; p 'mailto:=
'+a"

--HcAYCG3uE/tztfnV
Content-Type: application/pgp-signature
Content-Disposition: inline

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.2 (GNU/Linux)

iD8DBQFDwq0cBBd8ye5RguQRAiRWAJ4oZbvX67cqT0HcuKI0Od28LyvKkQCfYQiY
pZ5rDCr6CnuKvxCOXHdTUNw=
=xyxZ
-----END PGP SIGNATURE-----

--HcAYCG3uE/tztfnV--
 
C

Charles Rapp

SMC - the State Machine Compiler now generates Ruby code using the GoF
State Pattern for you. An SMC state machine can be easily associated
with your application class - with no change to your class. For more
information see http://smc.sourceforge.net.

Be sure to check out the on-line Programmer's Manual. SMC is simply to
use with a low learning curve.

Charles Rapp
 
D

Daniel Cadenas

Michael said:
Hello,

I found this State-pattern example on [1], and was interested to
find a way to make this more useable for standard classes (not just
servers as in the example)

I'am not so skilled in OO coding and ruby, so iam interested what you
all think.
(and is it another implementation of the state pattern or iam on the
wrong path?)

I just created this gem that you may find useful
http://github.com/dcadenas/state_pattern
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,770
Messages
2,569,583
Members
45,075
Latest member
MakersCBDBloodSupport

Latest Threads

Top