2016-10-18 62 views
1

我有一個類可以解析不同類型的消息,我想要做的是創建一個散列,它將使用msg類型id作爲鍵和不同的實例方法作爲值。創建實例方法引用的散列

事情是這樣的:

class Parser 
    def initialize(msg_id) 
     @my_methods = {1 => method_1, 2 => method_2, 3 => method_3} 
     @my_methods[msg_id]() 

    end 

    def method_1 
    end 

    def method_2 
    end 

    def method_3 
    end end 

我知道這是可能的,但我不知道該怎麼做。我嘗試使用self.method(:method_1)作爲值,但我得到一個錯誤,說method_1未定義。

謝謝

+1

的'self.method(:method_1)'語法應該有工作就好了,但,只有'self'真的有'method_1'方法時,調用'method'方法時定義。例如,你可以在'initialize'方法中做到這一點,因爲這也是一個實例方法,'self'是你的Parser類的一個實例,但是如果你嘗試在類方法中做,它不會起作用,因爲'method_1'不是一個類方法。 – philomory

回答

0

你正在尋找的方法是send

請注意,您的哈希中的值需要是傳遞到send的符號。

class Parser 
    def initialize(msg_id) 
     @my_methods = {1 => :method_1, 2 => :method_2, 3 => :method_3} 
     send(@my_methods[msg_id]) 
    end 

    def method_1 
    end 

    def method_2 
    end 

    def method_3 
    end 
end 

Documentation here

+1

發送將正常工作,但'method(:method_1)'選項也應該有效,所以可能會發生其他事情。 – philomory

+0

@philomory有趣 - 我明白你的觀點。 –

1

雖然所提供的答案會工作得很好,很少有「小」的問題吧:

  1. 如果有會是萬噸的方法,這樣的硬編碼哈希需要時間,並且由於它不是動態的(因爲每次將新方法添加到類體時都需要手動更新哈希),所以非常容易出錯。
  2. 即使你是在類中,並在技術上有機會獲得與隱含的接收器(包括privateprotected)任何能見度範圍定義的所有方法,它仍然是一個很好的做法,只能依靠公共接口上,因此,我建議使用Object#public_send

因此,這裏是我會建議什麼(儘管我看不出有這樣的地圖的想法是在現實生活中工作):

class Parser 
    def initialize(msg_id) 
    # generate a dynamic hash with keys starting with 1 
    # and ending with the size of the methods count 
    methods_map = Hash[(1..instance_methods.size).zip(instance_methods)] 

    # Use public_send to ensure, only public methods are accessed 
    public_send(methods_map[msg_id]) 
    end 

    # create a method, which holds a list of all instance methods defined in the class 
    def instance_methods 
    self.class.instance_methods(false) 
    end 
end 

快速以爲我後重構它一下,這樣我們就隱藏映射的實施private方法:

class Parser 
    def initialize(msg_id) 
    public_send(methods_map[msg_id]) 
    end 

    # methods omitted 

    private 

    def methods_map # not methods_hash, because what we do is mapping 
    Hash[(1..instance_methods.size).zip(instance_methods)] 
    # or 
    # Hash[instance_methods.each.with_index(1).map(&:reverse)] 
    end 

    def instance_methods 
    self.class.instance_methods(false) 
    end 
end 
+0

在寫作'雖然提供的答案'只有一個答案** AJ Gregory ** :) –

+0

'def methods_map;散列[方法(false).each.with_index(1).map(&:reverse)]'(並刪除實例方法'instance_methods')? –

+0

@CarySwoveland thx,實際上,雖然在給出這個答案之後的某個時間,儘管'方法(假)',但沒有編輯。現在編輯! –

1

最簡單的改變來解決你的代碼是像這樣:

class Parser 
    def initialize(msg_id) 
    @my_methods = { 1 => method(:method_1), 2 => method(:method_2), 3 => method(:method_3) } 
    @my_methods[msg_id].() 
    end 

    def method_1; end 
    def method_2; end 
    def method_3; end 
end 

即,使用Object#method方法獲取Method對象,並使用Method#call方法執行它。

但是,我們可以做出一些改進。首先,你的HashInteger與值相關聯。但有一個更好的數據結構已經這樣做了:Array。 (注意:如果您的留言ID不按順序分配,那麼Hash可能是正確的選擇,但是從你的例子的外觀上來看,他們只是Integer期從1計數。)

其次,硬編碼的方法在Parser#initialize方法裏面可能不是一個好主意。應該有對協議的說明性描述,即消息ID及其對應的方法名稱。

class Parser 
    # this will make your message IDs start at 0, though 
    PROTOCOL_MAPPING = [:method_1, :method_2, :method_3].freeze 

    def initialize(msg_id) 
    @my_methods = PROTOCOL_MAPPING.map(&method(:method)) 
    @my_methods[msg_id].() 
    end 

    def method_1; end 
    def method_2; end 
    def method_3; end 
end 

另一種可能是這樣的:

class Parser 
    PROTOCOL_MAPPING = [] 

    private_class_method def self.parser(name) 
    PROTOCOL_MAPPING << name 
    end 

    def initialize(msg_id) 
    @my_methods = PROTOCOL_MAPPING.map(&method(:method)) 
    @my_methods[msg_id].() 
    end 

    parser def method_1; end 
    parser def method_2; end 
    parser def method_3; end 
end 

或者,也許這樣的:

class Parser 
    PROTOCOL_MAPPING = {} 

    private_class_method def self.parser(msg_id, name) 
    PROTOCOL_MAPPING[msg_id] = name 
    end 

    def initialize(msg_id) 
    @my_methods = PROTOCOL_MAPPING.map {|msg_id, name| [msg_id, method(name)] }.to_h.freeze 
    @my_methods[msg_id].() 
    end 

    parser 1, def method_1; end 
    parser 2, def method_2; end 
    parser 3, def method_3; end 
end