簡體   English   中英

使用super with class_eval

[英]Using super with class_eval

我有一個應用程序,其中包含用於添加客戶端自定義的核心類的模塊。

我發現class_eval是覆蓋核心類中方法的好方法,但有時我想避免重寫整個方法,只需遵循原始方法。

例如,如果我有一個名為account_balance的方法,那么在我的模塊中執行類似這樣的操作會很好(即包含在類中的模塊):

module CustomClient
  def self.included base
    base.class_eval do
      def account_balance
        send_alert_email if balance < min
        super # Then this would just defer the rest of the logic defined in the original class
      end
    end
  end
end

但是使用class_eval似乎將super方法從查找路徑中取出。

有誰知道如何解決這個問題?

謝謝!

我認為有幾種方法可以做你想做的事情。 一種是打開類並為舊實現添加別名:

class MyClass
  def method1
    1
  end
end

class MyClass
  alias_method :old_method1, :method1
  def method1
    old_method1 + 1
  end
end

MyClass.new.method1
 => 2 

這是猴子修補的一種形式,所以最好是適度使用這個成語。 此外,有時需要的是一個保持常用功能的獨立輔助方法。

編輯 :有關更全面的選項,請參閱JörgWMittag的答案。

我發現instance_eval是覆蓋核心類中方法的好方法,

你不是壓倒一切。 你正在覆蓋又名monkeypatching。

但有時我想避免重寫整個方法,只需遵循原始方法。

您不能遵循原始方法。 沒有原創方法。 你把它覆蓋了。

但是使用instance_eval似乎將super方法從查找路徑中取出。

您的示例中沒有繼承。 super甚至沒有發揮作用。

有關可能的解決方案和替代方案,請參閱此答案: 當猴子修補方法時,是否可以從新實現中調用重寫方法?

如你所說,必須小心使用alias_method。 鑒於這個人為的例子:

module CustomClient
...    
    host.class_eval do
      alias :old_account_balance :account_balance
      def account_balance ...
        old_account_balance
      end
...
class CoreClass
    def old_account_balance ... defined here or in a superclass or
                                in another included module
    def account_balance
        # some new stuff ...
        old_account_balance # some old stuff ...
    end
    include CustomClient
end

你最終得到一個無限循環,因為在別名之后,old_account_balance是account_balance的副本,現在它自己調用:

$ ruby -w t4.rb 
t4.rb:21: warning: method redefined; discarding old old_account_balance
t4.rb:2: warning: previous definition of old_account_balance was here
[ output of puts removed ]
t4.rb:6: stack level too deep (SystemStackError)

[來自Pickaxe]這種技術[alias_method]的問題在於你依賴的是不存在名為old_xxx的現有方法。 更好的選擇是使用方法對象,這些方法對象實際上是匿名的。

話雖如此,如果你擁有源代碼,一個簡單的別名就足夠了。 但是對於更一般的情況,我將使用Jörg的Method Wrapping技術。

class CoreClass
    def account_balance
        puts 'CoreClass#account_balance, stuff deferred to the original method.'
    end
end

module CustomClient
  def self.included host
    @is_defined_account_balance = host.new.respond_to? :account_balance
    puts "is_defined_account_balance=#{@is_defined_account_balance}"
        # pass this flag from CustomClient to host :
    host.instance_variable_set(:@is_defined_account_balance,
                                @is_defined_account_balance)
    host.class_eval do
      old_account_balance = instance_method(:account_balance) if
                @is_defined_account_balance
      define_method(:account_balance) do |*args|
        puts 'CustomClient#account_balance, additional stuff'
            # like super :
        old_account_balance.bind(self).call(*args) if
                self.class.instance_variable_get(:@is_defined_account_balance)
      end
    end
  end
end

class CoreClass
    include CustomClient
end

print 'CoreClass.new.account_balance : '
CoreClass.new.account_balance

輸出:

$ ruby -w t5.rb 
is_defined_account_balance=true
CoreClass.new.account_balance : CustomClient#account_balance, additional stuff
CoreClass#account_balance, stuff deferred to the original method.

為什么不是類變量@@ is_defined_account_balance? [來自Pickaxe]包含include的模塊或類定義可以訪問它包含的模塊的常量,類變量和實例方法。
它會避免將它從CustomClient傳遞給主機並簡化測試:

    old_account_balance if @@is_defined_account_balance # = super

但有些人不喜歡類變量和全局變量一樣多。

[來自Pickaxe]方法Object#instance_eval允許你將self設置為某個任意對象,用塊評估塊中的代碼,然后重置self。

module CustomClient
  def self.included base
    base.instance_eval do
      puts "about to def account_balance in #{self}"
      def account_balance
        super
      end
    end
  end
end

class Client
    include CustomClient #=> about to def account_balance in Client
end

如您所見, def account_balance在類Client(包含模塊的宿主類)的上下文中進行評估,因此account_balance成為Client的單例方法(也稱為類方法):

print 'Client.singleton_methods : '
p Client.singleton_methods #=> Client.singleton_methods : [:account_balance]

Client.new.account_balance不起作用,因為它不是實例方法。

“我有一個包含模塊到核心類的應用程序”

由於您沒有提供太多細節,我想象了以下基礎設施:

class SuperClient
    def account_balance
        puts 'SuperClient#account_balance'
    end
end

class Client < SuperClient
    include CustomClient
end

現在用class_eval替換instance_eval。 [來自Pickaxe] class_eval設置就像你在類定義的主體中一樣,因此方法定義將定義實例方法。

module CustomClient
...
   base.class_eval do
...

print 'Client.new.account_balance : '
Client.new.account_balance

輸出:

  #=> from include CustomClient :
about to def account_balance in Client #=> as class Client, in the body of Client
Client.singleton_methods : []
Client.new.account_balance : SuperClient#account_balance #=> from super


"But using instance_eval seems to take the super method out of the lookup path."

super工作。 問題是instance_eval。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM