[英]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.