Forum: Ruby state pattern?

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Michael 'entropie' Trommer (Guest)
on 2006-01-07 03:01
(Received via mailing list)
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"
Robert K. (Guest)
on 2006-01-07 16:05
(Received via mailing list)
Michael 'entropie' Trommer <removed_email_address@domain.invalid> 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
Michael 'entropie' Trommer (Guest)
on 2006-01-09 20:38
(Received via mailing list)
Hello Robert,

* Robert K. (removed_email_address@domain.invalid) wrote:
> >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).

Ok.

> [...]
> Other optimizations are also possible, for example caching state
> instance
> 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='njduspAhnbjm/dpn').size-1){|x| a[x]-=1}; p
'mailto:'+a"
Daniel C. (Guest)
on 2009-06-08 11:23
Michael 'entropie' Trommer 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?)

I just created this gem that you may find useful
http://github.com/dcadenas/state_pattern
This topic is locked and can not be replied to.