创建实例方法引用的哈希

时间:2016-10-18 23:27:22

标签: ruby

我有一个可以解析不同类型消息的类,我想要做的是创建一个散列,它将使用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未定义。

谢谢

3 个答案:

答案 0 :(得分:1)

虽然提供的答案可以正常使用,但它有一些“次要”问题:

  1. 如果有大量的方法,硬编码这样的哈希需要时间,并且由于它不是动态的(因为每次将新方法添加到类体时都必须手动更新哈希),这非常容易出错
  2. 即使您在类中,并且在技术上可以访问使用隐式接收器(包括privateprotected)的任何可见范围定义的所有方法,但对于来说仍然是一个好习惯>仅依靠公共接口,因此,我建议使用Object#public_send
  3. 所以这就是我的建议(尽管事实上我并没有看到这样的地图如何在现实生活中发挥作用):

    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
    

答案 1 :(得分: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方法执行它。

但是,我们可以做一些改进。例如,您的Hash关联Integer具有值。但是有一个更好的数据结构已经做到了: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

答案 2 :(得分: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