Pregunta

I have a class with two states, "state1" and "state2" (there can be only two states, and it never changes since instance's creation) and following code

class MyClass

 attr_accessor :myvar1, :myvar2, :state

 include Extension1
 include Extension2

 def func1
  send("#{self.state}_func1")
 end

 def otherfunc
  send("#{self.state}_otherfunc")
 end

 def anotherfunc
  send("#{self.state}_anotherfunc")
 end

end

module Extension1 #handles state1 functions

 def state1_func1
  #do something using MyClass instance vars
 end

 def state1_otherfunc
  #do something using MyClass instance vars
 end

 def state1_anotherfunc
  #do something using MyClass instance vars
 end

end

module Extension2 #handles state2 functions

 def state2_func1
  #do something using MyClass instance vars
 end

 def state2_otherfunc
  #do something using MyClass instance vars
 end

 def state2_anotherfunc
  #do something using MyClass instance vars
 end

end

How this code can be improved? (this example is very basic, actual object has two state attributes at once, and i need to override first state's state_machine events based on the second state)

¿Fue útil?

Solución 3

In response to toro2k's comment, I'm taking the freedom of showing how I do things at home in cases like this one:

module Ext1
  def f
    puts "state a action"
  end
end

module Ext2
  def f
    puts "state b action"
  end
end

class X
  attr_reader :state

  def initialize( state=:a )
    @state = state
    extend case state
           when :a then Ext1
           when :b then Ext2
           end
  end
end

a, b = [ :a, :b ].map &X.method( :new )

a.f # ...
b.f # ...

But this is quite different from the line of thinking that Pavel has asked about in his question.

Note: Pavel's instance "state" never changes, so there are no transitions. Should there be multiple states and transitions between them, you would have a place/transition net, aka. Petri net sensu lato, embedded in the instance. This would be handled by my y_petri gem, in which places and transitions are objects on their own right and belong to a Net object, which would be an attribute of the MyClass instance, and whose state vector would govern its behavior in the way similar to the one that you or Pavel suggested. Another gem that can be considered is the state machine gem.

Otros consejos

What you are looking for is an implementation of the state pattern, the best way, I think, to implement it in Ruby is by means of the Forwardable module, avoiding explicit metaprogramming. A simple example to give the idea:

require 'forwardable'

class MyClass
  extend Forwardable
  def_delegators :@state, :func1

  def initialize
    @state = InitialState.new
  end
  def do_transition
    @state = FinalState.new
  end
end

class InitialState
  def func1; "InitialState" end
end

class FinalState
  def func1; "FinalState" end
end

obj = MyClass.new
obj.func1
# => "InitialState"
obj.do_transition
obj.func1
# => "FinalState"

Update: Since MyClass objects are initialized with a state and it never changes, probably they are actually strategies, you can modify MyClass like this:

class MyClass
  extend Forwardable
  def_delegators :@strategy, :func1

  def initialize(strategy)
    @strategy = strategy
  end
end

Ruby has many ways to do what one needs to do. Following your chosen path, this is how I'd go about it, Pavle:

module Ext1
  def f_state_a
    puts "state a action"
  end
end

module Ext2
  def f_state_b
    puts "state b action"
  end
end

class X
  include Ext1, Ext2

  attr_reader :state

  def initialize( state=:a )
    @state = state
  end

  def f
    case state
    when :a then f_state_a
    when :b then f_state_b
    end
  end
end

a, b = [ :a, :b ].map &X.method( :new )

a.f # ...
b.f # ...
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top