2013-11-29 6 views
1

У меня есть класс с двумя состояниями, «State1» и «State2» (там может быть только два состояния, и она никогда не меняется с момента создания экземпляра) и следующий кодКак вызвать методы в зависимости от состояния экземпляра?

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 

Как этот код может быть улучшен? (этот пример очень простой, фактический объект имеет сразу два атрибута состояния, и мне нужно переопределить события state_machine первого состояния на основе второго состояния)

ответ

0

В ответ на комментарий toro2k я проявляю свободу показывать, как я делаю вещи дома в таких случаях, как этот:

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 # ... 

Но это совсем не так, как думает Павел в своем вопросе.

Примечание: Экземпляр Павла «состояние» никогда не изменяется, поэтому переходов нет. Если между ними существует несколько состояний и переходов, у вас будет сеть с местом/переходом, иначе. Petri net sensu lato, встроенный в экземпляр. Это будет обрабатываться моим драгоценным камнем y_petri, в котором места и переходы являются объектами сами по себе и относятся к объекту Net, который будет атрибутом экземпляра MyClass, и вектор состояния которого будет управлять его поведением в соответствии с тот, который вы или Павел предложили. Другой драгоценный камень, который можно рассмотреть, это драгоценный камень state machine.

+0

К сожалению, это решение не сработало в моем случае. Я использую delayed_job для запуска переопределенных методов и кэширует все классы, которые он обрабатывает. поэтому, как только я инициализирую первый экземпляр, все остальные экземпляры выполняются независимо от их состояния. –

+0

@PavelK., Я могу чувствовать запах анти-шаблона дизайна в 'delayed_job'. Параметризированное подклассирование и паттинг обезьян - это элементарные методы Ruby. Но я не хочу быть слишком суровым на них; Я написал достаточно проектов Ruby, чтобы узнать, как сложно сделать проект более полным, со всеми ожидаемыми шаблонами использования. –

1

Ruby имеет много способов сделать то, что нужно сделать. После выбранного пути, это то, как я бы об этом, Павле:

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 # ... 
+0

Вы имеете в виду, что использовать случай вместо отправки лучше? кроме этого я не замечаю разницы. (true, что я пропустил часть инициализации в MyClass). я в основном готовлюсь к тому, чтобы сделать выше сухую версию кода и, возможно, загрязнять пространство имен меньше –

+0

Я думаю, что вы должны использовать 'Ext1' и' Ext2' методы private, иначе в любой момент объект класса 'X' будет resposnd для обоих' f_state_a' и 'f_state_b'. – toro2k

+0

@PavelK .: Я сделал то, о чем вы просили: честная попытка переписать код, сохраняя в своей линии мышления. На самом деле ваш код был не так уж плох. Я удаляю последнее предложение из моего ответа. Бывают случаи, когда попытка чрезмерной оптимизации кода на самом деле ухудшает ситуацию, например, писать 'a, b = [: a,: b] .map & X.method (: new)' вместо 'a, b = X .new (: a), X.new (: b) '. Ваш код был достаточно хорош. –

1

Что вы ищете является реализация state pattern, лучший способ, я думаю, реализовать его в Руби является средства модуля Forwardable, избегая явного метапрограммирования. Простой пример, чтобы дать представление:

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: Поскольку MyClass объекты инициализируются с состоянием и никогда не меняется, вероятно, на самом деле они strategies, вы можете изменить MyClass так:

class MyClass 
    extend Forwardable 
    def_delegators :@strategy, :func1 

    def initialize(strategy) 
    @strategy = strategy 
    end 
end 
+0

Это был очень воспитательный ответ, и я бы с радостью дал вам еще один +1, если бы мог. однако, если я правильно понимаю, я не смогу получить доступ/изменить экземпляр экземпляра MyClass (obj) в экземпляре @strategy. –

+0

Нет, вы не можете получить доступ к '@ strategy', если вам нужно, просто определите, 'attr_accessor'.Более того, если вам нужно изменить состояние объекта, лучше сделать это с помощью таких методов, как «do_transition» моего примера, а не напрямую обращаться к объекту '@ state'. – toro2k