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'
Foo
Nesnelerin oluşturulmasını kontrol ediyorsanız, bu işe yarar . Bunun Foo
yerine , 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 Foo
onlar 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, Foo
nesnenin 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#DelegateClass
yardı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#prepend
bu kullanım örneğini az çok desteklemek için eklendi. sınıfın hemen altındaki karışımda karışması dışında Module#prepend
aynı ş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#prepend
bu 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 include
ing yerine bir mixin prepend
ing:
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#include
mixin ekler yukarıdaki araçlar kalıtım hiyerarşisinde sınıfı FooExtensions#bar
denilen asla (ve eğer edildi denilen, super
aslında atıfta olmaz Foo#bar
ziyade üzere Object#bar
beri var olmayan) Foo#bar
her 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_bar
sadece 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_method
bir blok aldığından ve çevreleyen sözcüksel ortamını kapattığından (bu yüzden burada kullanmak define_method
yerine kullanıyoruz def
), o (ve sadece o) old_bar
kapsam dışına çıktıktan sonra bile hala erişime sahip olacaktır .
Kısa açıklama:
old_bar = instance_method(:bar)
Burada bar
yöntemi bir UnboundMethod
yö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 self
Ruby'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 bind
Our UnboundMethod
bir dönecektir ilk bir nesne ile Method
o zaman diyoruz ki nesneyi. ( UnboundMethod
çağrılamazlar, çünkü bilmeden ne yapacaklarını bilmiyorlar self
.)
Ve ne yapacağız bind
? Biz sadece bind
kendimize, bu şekilde davranacaktır aynen orijinal gibi bar
olurdu!
Son olarak, Method
geri gönderileni aramalıyız bind
. Ruby 1.9'da, bunun için bazı şık yeni sözdizimi vardır ( .()
), ancak 1.8 üzerindeyseniz, call
yö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_bar
yö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#prepend
yukarı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#prepend
etrafta 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
bar
yöntemin yürütülmesine “bağlanabilirsiniz” .
Bununla birlikte, bar
içindeki dönüş değerine erişip erişmediğinizi ve nasıl erişeceğinizi açık değildir bar:after
. Belki (ab) super
anahtar kelimesini kullanabiliriz ?
class Foo
def bar
'Hello'
end
end
class Foo
def bar:after
super + ' World'
end
end
Değiştirme
Önceki birleştirici, prepend
yöntemin super
en sonunda çağıran geçersiz kılma yöntemine sahip bir mixin oluşturmakla eşdeğerdir . Benzer şekilde, sonraki birleştirici, prepend
yöntemin super
en 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 super
yapabilir, super
birden çok kez arayabilir ve hem geri super
dönüş değerini alıp hem de manipüle ederek prepend
yö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 old
varsa, artık arayamazsınız!
Değiştirme
super
prepend
ed kanşımındaki geçersiz kılma yönteminde esasen old
bu öneri ile aynıdır .
redef
anahtar kelime
Yukarıdakine benzer, ancak üzerine yazılan yöntemi çağırmak ve def
yalnı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 super
iç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
redef
bir yöntemin uygulanması, bir prepend
edim karışımında yöntemin geçersiz kılınmasına denktir . super
geçersiz kılma yönteminde bu teklif gibi super
veya old
bu davranışta bulunur .