从函数覆盖访问外部属性

Access outer attribute from a function override

我有一个包装器 class,它重新定义了包装器 class 的方法。有什么方法可以从覆盖方法内部访问包装器的状态?

class WidgetWrapper

  attr_accessor :result_saved_by_widget

  def initialize(widget)
    @widget = widget
    # we intercept the widget's usual "save" method so we can see
    # what the widget tries to save
    def @widget.save_result(result) # this override works fine ...
      OUTER.result_saved_by_widget = result # .. but I need something like this inside it!
    end
  end

  def call
    widget.calculate # this will call "save_result" at some stage
  end

end

# How it gets used
wrapper = Wrapper.new(Widget.new)
wrapper.call
puts wrapper.result_saved_by_widget

用一个非常愚蠢的 hack 解决了这个问题 - 使用 instance_variable_set.

预先注入包装器对象
class WidgetWrapper

  attr_accessor :result_saved_by_widget

  def initialize(widget)
    @widget = widget
    @widget.instance_variable_set :@wrapper, self
    # we intercept the widget's usual "save" method so we can see
    # what the widget tries to save
    def @widget.save_result(result) # this override works fine ...
      @wrapper.result_saved_by_widget = result # ... and this works too :)
    end
  end

  def call
    widget.calculate # this will call "save_result" at some stage
  end

end

# How it gets used
wrapper = Wrapper.new(Widget.new)
wrapper.call
puts wrapper.result_saved_by_widget

其实也没那么难。几点:

  1. 您可能想调用原始 save_result。否则,它不是一个包装器。
  2. 您需要使用闭包来捕获当前的词汇上下文(意思是,记住我们在 WidgetWrapper

    class Widget
      def calculate
        save_result(3)
      end
    
      def save_result(arg)
        puts "original save_result: #{arg}"
      end
    end
    
    class WidgetWrapper
    
      attr_accessor :result_saved_by_widget, :widget
    
      def initialize(widget)
        @widget = widget
    
        wrapper = self # `self` can/will unpredictably change.
    
    
        @widget.define_singleton_method :save_result do |result|
          wrapper.result_saved_by_widget = result
          super(result)
        end
      end
    
      def call
        widget.calculate
      end
    
    end
    
    # How it gets used
    wrapper = WidgetWrapper.new(Widget.new)
    wrapper.call
    puts 'intercepted value'
    puts wrapper.result_saved_by_widget
    # >> original save_result: 3
    # >> intercepted value
    # >> 3
    

我不太明白你的问题,但我认为过去做过类似的事情,也许以下几行可以帮助你:

documents_to_wrap.each do |doc|
  doc.define_singleton_method(:method){override_code}
  tmp = doc.instance_variable_get(:@instance_var).
  doc.instance_variable_set(:@other_instance_var, tmp.do_something)
end

根据你的例子,我会 extend 带有模块的对象:

module WidgetExtension
  attr_accessor :results_saved_by_widget

  def save_result(result)
    @results_saved_by_widget = result
    super
  end
end


w = Widget.new
w.extend(WidgetExtension)
w.calculate

w.results_saved_by_widget #=> stored value