Robert Klemme
1/7/2006 2:02:00 PM
Michael 'entropie' Trommer <mictro@gmail.com> wrote:
> 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 :owner
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 :owner
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