EDIT : Aslında bu cevabı yazdığım 9 yıl oldu ve güncel tutmak için bazı kozmetik cerrahiyi hak ediyor.
Düzenlemeden önceki son sürümü burada görebilirsiniz .
Üzerine yazılan yöntemi ada veya anahtar sözcüğe göre çağıramazsınız . Açıkçası beri bu maymun yama kaçınılmalıdır ve miras yerine tercih edilmesi birçok nedenden biri olabilir diyoruz geçersiz kılınan yöntem.
Maymun Yamadan Kaçının
miras
Yani, eğer mümkünse, böyle bir şeyi tercih etmelisiniz:
class Foo
def bar
'Hello'
end
end
class ExtendedFoo < Foo
def bar
super + ' World'
end
end
ExtendedFoo.new.bar # => 'Hello World'
FooNesnelerin oluşturulmasını kontrol ediyorsanız, bu işe yarar . Bunun Fooyerine , bir oluşturmak için her yeri değiştirin ExtendedFoo. Bağımlılık Enjeksiyon Tasarım Deseni , Fabrika Metodu Tasarım Deseni , Soyut Fabrika Tasarım Deseni veya bu çizgiler boyunca bir şey kullanırsanız, bu daha da iyi çalışır , çünkü bu durumda yalnızca değiştirmeniz gereken yer vardır.
delegasyon
Eğer varsa yok oluşturulmasını kontrol Fooonlar sizin kontrolünüz dışında olan bir çerçeve tarafından oluşturulur, çünkü böyle (örneğin nesnelerin,raylar üzerinde yakutörneğin), Sarıcı Tasarım Deseni'ni kullanabilirsiniz :
require 'delegate'
class Foo
def bar
'Hello'
end
end
class WrappedFoo < DelegateClass(Foo)
def initialize(wrapped_foo)
super
end
def bar
super + ' World'
end
end
foo = Foo.new # this is not actually in your code, it comes from somewhere else
wrapped_foo = WrappedFoo.new(foo) # this is under your control
wrapped_foo.bar # => 'Hello World'
Temel olarak, Foonesnenin kodunuza girdiği sistemin sınırında, onu başka bir nesneye sararsınız ve daha sonra , kodunuzdaki başka her yerde orijinal yerine bu nesneyi kullanırsınız.
Bu , stdlib'deki kütüphaneden Object#DelegateClassyardımcı yöntemi kullanır delegate.
“Temiz” Maymun Yamalama
Yukarıdaki iki yöntem, maymun yamasını önlemek için sistemin değiştirilmesini gerektirir. Bu bölüm, sistemi değiştirmenin bir seçenek olmaması gerektiğinde, tercih edilen ve en az invaziv maymun yaması yöntemini göstermektedir.
Module#prependbu kullanım örneğini az çok desteklemek için eklendi. sınıfın hemen altındaki karışımda karışması dışında Module#prependaynı şeyi yapar :Module#include
class Foo
def bar
'Hello'
end
end
module FooExtensions
def bar
super + ' World'
end
end
class Foo
prepend FooExtensions
end
Foo.new.bar # => 'Hello World'
Not: Ben de Module#prependbu soru hakkında biraz yazdım : Ruby modülü türetmek vs türev
Mixin Kalıtım (kırık)
Bazı insanlar denemek gördüm (ve neden burada StackOverflow üzerinde çalışmadığı hakkında sormak) böyle bir şey, yani includeing yerine bir mixin prepending:
class Foo
def bar
'Hello'
end
end
module FooExtensions
def bar
super + ' World'
end
end
class Foo
include FooExtensions
end
Ne yazık ki, bu işe yaramaz. Bu iyi bir fikir, çünkü miras kullanıyor, yani kullanabileceğiniz anlamına geliyor super. Ancak, Module#includemixin ekler yukarıdaki araçlar kalıtım hiyerarşisinde sınıfı FooExtensions#bardenilen asla (ve eğer edildi denilen, superaslında atıfta olmaz Foo#barziyade üzere Object#barberi var olmayan) Foo#barher zaman ilk bulunacaktır.
Yöntem Sarma
Büyük soru şudur: Gerçek bir yöntemibar tutmadan yönteme nasıl dayanabiliriz ? Cevap işlevsel programlamada sıklıkla olduğu gibi yatıyor. Yöntemin gerçek bir nesne olarak tutulduğunu ve yalnızca ve yalnızca o nesneye bağlı kaldığımızdan emin olmak için bir kapatma (yani bir blok) kullanıyoruz :
class Foo
def bar
'Hello'
end
end
class Foo
old_bar = instance_method(:bar)
define_method(:bar) do
old_bar.bind(self).() + ' World'
end
end
Foo.new.bar # => 'Hello World'
Bu çok temiz: old_barsadece yerel bir değişken olduğu için sınıf gövdesinin sonunda kapsam dışına çıkacak ve yansıma kullanarak bile her yerden erişmek imkansız ! Ve Module#define_methodbir blok aldığından ve çevreleyen sözcüksel ortamını kapattığından (bu yüzden burada kullanmak define_methodyerine kullanıyoruz def), o (ve sadece o) old_barkapsam dışına çıktıktan sonra bile hala erişime sahip olacaktır .
Kısa açıklama:
old_bar = instance_method(:bar)
Burada baryöntemi bir UnboundMethodyöntem nesnesine sararak yerel değişkene atarız old_bar. Bu, barüzerine yazıldıktan sonra bile tutunmamız gereken bir yol var .
old_bar.bind(self)
Bu biraz zor. Temel olarak, Ruby'de (ve hemen hemen tüm tek dağıtım tabanlı OO dillerinde), yöntem selfRuby'de adlandırılan belirli bir alıcı nesnesine bağlanır . Başka bir deyişle: bir yöntem her zaman hangi nesneye çağrıldığını bilir, onun ne olduğunu bilir self. Ancak, yöntemi doğrudan bir sınıftan aldık, bunun ne olduğunu nasıl biliyor self?
Eh, biz gereken, bu yüzden de değil bindOur UnboundMethodbir dönecektir ilk bir nesne ile Methodo zaman diyoruz ki nesneyi. ( UnboundMethodçağrılamazlar, çünkü bilmeden ne yapacaklarını bilmiyorlar self.)
Ve ne yapacağız bind? Biz sadece bindkendimize, bu şekilde davranacaktır aynen orijinal gibi barolurdu!
Son olarak, Methodgeri gönderileni aramalıyız bind. Ruby 1.9'da, bunun için bazı şık yeni sözdizimi vardır ( .()), ancak 1.8 üzerindeyseniz, callyöntemi kullanabilirsiniz ; Ne en o .()zaten için tercüme.
İşte bu kavramlardan bazılarının açıklandığı birkaç soru daha:
“Kirli” Maymun Yamaları
Maymun yamasıyla ilgili yaşadığımız sorun, yöntemin üzerine yazdığımızda yöntemin gitmiş olmasıdır, bu yüzden artık arayamayız. Şimdi yedek bir kopya yapalım!
class Foo
def bar
'Hello'
end
end
class Foo
alias_method :old_bar, :bar
def bar
old_bar + ' World'
end
end
Foo.new.bar # => 'Hello World'
Foo.new.old_bar # => 'Hello'
Buradaki sorun, artık ad alanını gereksiz bir old_baryöntemle kirletmiş olmamız . Bu yöntem dokümanlarımızda görünecek, IDE'lerimizde kod tamamlandığında görünecek, yansıma sırasında görünecektir. Ayrıca, hala çağrılabilir, ancak muhtemelen maymun yamalı, çünkü ilk etapta davranışını beğenmedik, bu yüzden diğer insanların aramasını istemeyebiliriz.
Bunun istenmeyen bazı özelliklere sahip olmasına rağmen, ne yazık ki AciveSupport's aracılığıyla popüler hale geldi Module#alias_method_chain.
Sistemin tamamında değil, yalnızca birkaç belirli yerde farklı davranışa ihtiyacınız varsa, maymun yamasını belirli bir kapsamla sınırlamak için İyileştirmeler'i kullanabilirsiniz. Ben Module#prependyukarıdaki örneği kullanarak burada göstermek için gidiyorum :
class Foo
def bar
'Hello'
end
end
module ExtendedFoo
module FooExtensions
def bar
super + ' World'
end
end
refine Foo do
prepend FooExtensions
end
end
Foo.new.bar # => 'Hello'
# We haven’t activated our Refinement yet!
using ExtendedFoo
# Activate our Refinement
Foo.new.bar # => 'Hello World'
# There it is!
Bu soruda Ayrıntılandırmaları kullanmanın daha karmaşık bir örneğini görebilirsiniz: Maymun düzeltme eki belirli bir yöntem için nasıl etkinleştirilir?
Terk edilmiş fikirler
Ruby topluluğu yerleşmeden önce, Module#prependetrafta yüzen, bazen daha eski tartışmalarda atıfta bulunabileceğiniz birçok farklı fikir vardı. Bunların hepsi tarafından toplanır Module#prepend.
Yöntem Kombinatörleri
Bir fikir, CLOS'tan yöntem birleştiriciler fikriydi. Bu temel olarak Aspect-Oriented Programming'in bir alt kümesinin çok hafif bir sürümüdür.
Gibi sözdizimi kullanma
class Foo
def bar:before
# will always run before bar, when bar is called
end
def bar:after
# will always run after bar, when bar is called
# may or may not be able to access and/or change bar’s return value
end
end
baryöntemin yürütülmesine “bağlanabilirsiniz” .
Bununla birlikte, bariçindeki dönüş değerine erişip erişmediğinizi ve nasıl erişeceğinizi açık değildir bar:after. Belki (ab) superanahtar kelimesini kullanabiliriz ?
class Foo
def bar
'Hello'
end
end
class Foo
def bar:after
super + ' World'
end
end
Değiştirme
Önceki birleştirici, prependyöntemin superen sonunda çağıran geçersiz kılma yöntemine sahip bir mixin oluşturmakla eşdeğerdir . Benzer şekilde, sonraki birleştirici, prependyöntemin superen başında çağıran geçersiz kılma yöntemine sahip bir mixin oluşturmakla eşdeğerdir .
Ayrıca , aramadan önce ve sonra bir şeyler superyapabilir, superbirden çok kez arayabilir ve hem geri superdönüş değerini alıp hem de manipüle ederek prependyöntem birleştiricilerinden daha güçlü hale getirebilirsiniz .
class Foo
def bar:before
# will always run before bar, when bar is called
end
end
# is the same as
module BarBefore
def bar
# will always run before bar, when bar is called
super
end
end
class Foo
prepend BarBefore
end
ve
class Foo
def bar:after
# will always run after bar, when bar is called
# may or may not be able to access and/or change bar’s return value
end
end
# is the same as
class BarAfter
def bar
original_return_value = super
# will always run after bar, when bar is called
# has access to and can change bar’s return value
end
end
class Foo
prepend BarAfter
end
old anahtar kelime
Bu fikir super, üzerine yazılan yöntemi aynı şekilde geçersiz kılma yöntemini superçağırmanıza izin veren yeni bir anahtar kelime ekler :
class Foo
def bar
'Hello'
end
end
class Foo
def bar
old + ' World'
end
end
Foo.new.bar # => 'Hello World'
Bununla ilgili temel sorun, geriye dönük uyumsuz olmasıdır: çağrılan bir yönteminiz oldvarsa, artık arayamazsınız!
Değiştirme
superprepended kanşımındaki geçersiz kılma yönteminde esasen oldbu öneri ile aynıdır .
redef anahtar kelime
Yukarıdakine benzer, ancak üzerine yazılan yöntemi çağırmak ve defyalnız bırakmak için yeni bir anahtar kelime eklemek yerine, yöntemleri yeniden tanımlamak için yeni bir anahtar kelime ekliyoruz . Şu anda sözdizimi yasadışı olduğu için bu geriye dönük olarak uyumludur:
class Foo
def bar
'Hello'
end
end
class Foo
redef bar
old + ' World'
end
end
Foo.new.bar # => 'Hello World'
Bunun yerine eklemenin iki yeni anahtar kelimeler, biz de bir anlam yeniden tanımlamak olabilir superiçeriden redef:
class Foo
def bar
'Hello'
end
end
class Foo
redef bar
super + ' World'
end
end
Foo.new.bar # => 'Hello World'
Değiştirme
redefbir yöntemin uygulanması, bir prependedim karışımında yöntemin geçersiz kılınmasına denktir . supergeçersiz kılma yönteminde bu teklif gibi superveya oldbu davranışta bulunur .