【问题标题】:Reference the invoking object in the passed block in Ruby在 Ruby 中引用传递的块中的调用对象
【发布时间】:2023-12-12 20:56:01
【问题描述】:

有什么方法可以在被调用的块中获取被调用的对象。例如,块是否有任何方法可以访问方法 batman 或类 SuperHeros 的范围

class SuperHeros

  attr_accessor :news

  def initialize
    @news = []
  end

  def batman task
    puts "Batman: #{task} - done"
    yield "feed cat"
    @news << task
  end

end

cat_woman = lambda do |task| 
  puts "Cat Woman: #{task} - done" 
  # invoker.news << task
end

robin = lambda do |task| 
  puts "Robin: #{task} - done"
  # invoker.news << task
end


characters = SuperHeros.new
characters.batman("kick Joker's ass", &cat_woman)
characters.batman("break Bane's bones", &robin)

【问题讨论】:

    标签: ruby lambda block proc


    【解决方案1】:

    您可以使用类似于Instance eval with delegation 模式的东西,例如在Savon gem 中使用:

    def batman(task, &block)
      @original_self = eval('self', block.binding)
      puts "Batman: #{task} - done"
      instance_exec('feed cat', &block)
      @news << task
    end
    
    private
    
    def method_missing(method, *args, &block)
      if @original_self
        @original_self.send(method, *args, &block)
      else
        super
      end
    end
    

    在这种方法中,当您在传递给batman 方法的块内调用方法(带有隐式接收器)时,它会在SuperHeros 实例的上下文中调用。如果没有这样的方法可用,则调用(通过method_missing)到原始块self

    【讨论】:

    • 感谢您的宝贵时间,但您能否指点一下在 instance_eval() 中使用时如何将参数传递给块
    • @JikkuJose 你可以使用instance_exec - 看看我编辑的帖子。
    • 太棒了,这行得通!我没有使用method_missing 实现!坦率地说,没有得到original_self 的用途。 (这是我根据您的建议对代码进行的修改:pastebin.com/ZdHsZ4Ck
    • @JikkuJose 我使用了这个,以防你在块中调用的方法(带有隐式接收器)在SuperHeros 实例方法中找不到 - 然后它被委托给从块评估的self。但是,正如您已经注意到的那样,它并不总是必要的。您的解决方案似乎就足够了。如果您觉得有帮助,请接受我的回答。
    【解决方案2】:

    在块中获取接收器对象的最简单方法是将对象分配给实例变量。

    这个例子更清楚地说明了 lambdas cat_woman 和 robin 如何访问块的接收者对象的属性:

    class SuperHeros
      attr_accessor :news, :name, :current_task
    
      def initialize(a_name)
        @name = a_name
        @news = []
      end
    
      def batman(task)
        puts "Inside the method batman of #{name}: #{task} in progress ..."
        @current_task = task
        yield
        @news << task
      end
    
    end
    
    cat_woman = lambda do |extra_task|
      puts "cat_woman even #{extra_task} before doing #{@caller_obj.current_task}"
      puts "Cat Woman: #{@caller_obj.current_task} - done by #{@caller_obj.name}"
      # invoker.news << task
    end
    
    robin = lambda do |extra_task|
      puts "robin even #{extra_task} before doing #{@caller_obj.current_task}"
      puts "Robin: #{@caller_obj.current_task} - done by #{@caller_obj.name}"
    end
    
    
    character_1 = SuperHeros.new('batman_1')
    (@caller_obj = character_1).batman("kick Joker's ass") { cat_woman['eats some burger'] }
    
    puts
    
    character_2 = SuperHeros.new('batman_2')
    (@caller_obj = character_2).batman("break Bane's bones") { robin['drinks some beer'] }
    

    输出将是:

    Inside the method batman of batman_1: kick Joker's ass in progress ...
    cat_woman even eats some burger before doing kick Joker's ass
    Cat Woman: kick Joker's ass - done by batman_1
    
    Inside the method batman of batman_2: break Bane's bones in progress ...
    robin even drinks some beer before doing break Bane's bones
    Robin: break Bane's bones - done by batman_2
    

    【讨论】: