Katakanlah saya monyet menambal metode di kelas, bagaimana saya bisa memanggil metode yang ditimpa dari metode yang menimpa? Yaitu Sesuatu yang agak sepertisuper
Misalnya
class Foo
def bar()
"Hello"
end
end
class Foo
def bar()
super() + " World"
end
end
>> Foo.new.bar == "Hello World"
ruby
monkeypatching
James Hollingworth
sumber
sumber
Foo
dan penggunaanFoo::bar
. Jadi, Anda harus menambal metode ini.Jawaban:
EDIT : Sudah 9 tahun sejak saya awalnya menulis jawaban ini, dan perlu beberapa operasi kosmetik agar tetap mutakhir.
Anda dapat melihat versi terakhir sebelum edit di sini .
Anda tidak dapat memanggil metode yang ditimpa dengan nama atau kata kunci. Itulah salah satu dari banyak alasan mengapa penambalan monyet harus dihindari dan pewarisan lebih disukai, karena jelas Anda dapat memanggil metode yang diganti .
Menghindari Penambalan Monyet
Warisan
Jadi, jika memungkinkan, Anda harus memilih sesuatu seperti ini:
Ini berfungsi, jika Anda mengontrol pembuatan
Foo
objek. Ubah saja setiap tempat yang membuatFoo
untuk bukannya membuatExtendedFoo
. Ini bekerja lebih baik jika Anda menggunakan Pola Desain Injeksi Ketergantungan , Pola Desain Metode Pabrik , Pola Desain Pabrik Abstrak atau sesuatu di sepanjang garis itu, karena dalam kasus itu, hanya ada tempat yang perlu Anda ubah.Delegasi
Jika Anda tidak mengontrol pembuatan
Foo
objek, misalnya karena mereka dibuat oleh kerangka kerja yang berada di luar kendali Anda (sepertiruby-on-railsmisalnya), maka Anda dapat menggunakan Pola Desain Wrapper :Pada dasarnya, pada batas sistem, di mana
Foo
objek datang ke kode Anda, Anda membungkusnya ke objek lain, dan kemudian menggunakan yang objek bukan yang asli di tempat lain dalam kode Anda.Ini menggunakan
Object#DelegateClass
metode pembantu daridelegate
perpustakaan di stdlib."Clean" Monkey Patching
Module#prepend
: Mixin PrependingDua metode di atas perlu mengubah sistem untuk menghindari tambalan monyet. Bagian ini menunjukkan metode penambalan monyet yang disukai dan paling tidak invasif, jika mengubah sistem tidak menjadi pilihan.
Module#prepend
telah ditambahkan untuk mendukung lebih atau kurang tepatnya use case ini.Module#prepend
melakukan hal yang sama denganModule#include
, kecuali itu bercampur dalam mixin langsung di bawah kelas:Catatan: Saya juga menulis sedikit tentang
Module#prepend
pertanyaan ini: Modul Ruby prepend vs derationWarisan Mixin (rusak)
Saya telah melihat beberapa orang mencoba (dan bertanya mengapa itu tidak bekerja di sini di StackOverflow) sesuatu seperti ini, yaitu
include
mixin bukannyaprepend
ing:Sayangnya, itu tidak akan berhasil. Itu ide yang bagus, karena menggunakan warisan, yang berarti Anda bisa menggunakannya
super
. Namun,Module#include
memasukkan mixin di atas kelas dalam hirarki warisan, yang berarti bahwaFooExtensions#bar
tidak akan dipanggil (dan jika yang disebut,super
tidak akan benar-benar mengacuFoo#bar
melainkan untukObject#bar
yang tidak ada), karenaFoo#bar
akan selalu ditemukan pertama.Metode Pembungkus
Pertanyaan besarnya adalah: bagaimana kita bisa berpegang pada
bar
metode tersebut, tanpa benar-benar mempertahankan metode yang sebenarnya ? Jawabannya terletak, seperti yang sering terjadi, dalam pemrograman fungsional. Kami mendapatkan metode sebagai objek aktual , dan kami menggunakan penutupan (yaitu blok) untuk memastikan bahwa kami dan hanya kami yang memegang objek itu:Ini sangat bersih: karena
old_bar
hanya variabel lokal, ia akan keluar dari ruang lingkup di akhir kelas, dan tidak mungkin untuk mengaksesnya dari mana saja, bahkan menggunakan refleksi! Dan karenaModule#define_method
mengambil blok, dan blok menutup lingkungan leksikal sekitarnya (itulah sebabnya kami menggunakandefine_method
alih-alih didef
sini), itu (dan hanya itu) masih akan memiliki akses keold_bar
, bahkan setelah itu telah keluar dari ruang lingkup.Penjelasan singkat:
Di sini kita membungkus
bar
metode menjadiUnboundMethod
objek metode dan menugaskannya ke variabel lokalold_bar
. Ini berarti, kita sekarang memiliki cara untuk bertahanbar
bahkan setelah ditimpa.Ini agak sulit. Pada dasarnya, di Ruby (dan di hampir semua bahasa OO berbasis pengiriman tunggal), metode terikat ke objek penerima tertentu, yang disebut
self
dalam Ruby. Dengan kata lain: metode selalu tahu objek apa yang dipanggil, ia tahu apaself
itu. Tapi, kami mengambil metode ini langsung dari kelas, bagaimana ia tahu apaself
itu?Ya, tidak, itu sebabnya kita perlu ke objek
bind
kitaUnboundMethod
terlebih dahulu, yang akan mengembalikanMethod
objek yang bisa kita panggil. (UnboundMethod
Tidak dapat dipanggil, karena mereka tidak tahu apa yang harus dilakukan tanpa mengetahui merekaself
.)Dan untuk apa kita
bind
? Kita hanyabind
melakukannya untuk diri kita sendiri, dengan cara itu akan berperilaku persis seperti aslinyabar
!Terakhir, kita perlu memanggil dari
Method
mana kembalibind
. Di Ruby 1.9, ada beberapa sintaks baru yang bagus untuk itu (.()
), tetapi jika Anda menggunakan 1,8, Anda bisa menggunakancall
metode ini;.()
toh itu yang akan diterjemahkan.Berikut adalah beberapa pertanyaan lain, di mana beberapa konsep tersebut dijelaskan:
Penambalan Monyet "Kotor"
alias_method
rantaiMasalah yang kita alami dengan tambalan monyet kita adalah ketika kita menimpa metode, metode itu hilang, jadi kita tidak bisa menyebutnya lagi. Jadi, mari kita buat salinan cadangan!
Masalah dengan ini adalah bahwa kita sekarang telah mencemari namespace dengan
old_bar
metode yang berlebihan . Metode ini akan muncul di dokumentasi kami, itu akan muncul dalam penyelesaian kode di IDE kami, itu akan muncul selama refleksi. Juga, masih bisa dipanggil, tapi mungkin kita menambalnya, karena kita tidak suka dengan perilakunya, jadi kita mungkin tidak ingin orang lain menyebutnya.Terlepas dari kenyataan bahwa ini memiliki beberapa properti yang tidak diinginkan, sayangnya telah dipopulerkan melalui AciveSupport
Module#alias_method_chain
.Samping: Perbaikan
Jika Anda hanya perlu perilaku berbeda di beberapa tempat tertentu dan tidak di seluruh sistem, Anda dapat menggunakan Penyempitan untuk membatasi tambalan monyet ke lingkup tertentu. Saya akan menunjukkannya di sini menggunakan
Module#prepend
contoh dari atas:Anda dapat melihat contoh yang lebih canggih dari menggunakan Penyempitan dalam pertanyaan ini: Bagaimana mengaktifkan tambalan monyet untuk metode tertentu?
Gagasan yang ditinggalkan
Sebelum komunitas Ruby diselesaikan
Module#prepend
, ada beberapa ide berbeda yang beredar yang kadang-kadang Anda lihat dirujuk dalam diskusi yang lebih lama. Semua ini digolongkan olehModule#prepend
.Metode Combinators
Satu ide adalah ide kombinator metode dari CLOS. Ini pada dasarnya adalah versi yang sangat ringan dari subset Pemrograman Berorientasi Aspek.
Menggunakan sintaks seperti
Anda akan dapat "menghubungkan ke" pelaksanaan
bar
metode.Namun tidak begitu jelas apakah dan bagaimana Anda mendapatkan akses ke
bar
nilai pengembalian di dalamnyabar:after
. Mungkin kita bisa (ab) menggunakansuper
kata kunci?Penggantian
Combinator yang sebelumnya setara
prepend
dengan mixin dengan metode overriding yang memanggilsuper
di akhir metode. Demikian juga, kombinator setelah adalah setara denganprepend
ing mixin dengan metode utama yang memanggilsuper
pada awal metode.Anda juga dapat melakukan hal-hal sebelum dan setelah menelepon
super
, Anda dapat memanggilsuper
beberapa kali, dan keduanya mengambil dan memanipulasisuper
nilai pengembalian, menjadikannyaprepend
lebih kuat daripada kombinator metode.dan
old
kata kunciGagasan ini menambahkan kata kunci baru yang mirip dengan
super
, yang memungkinkan Anda memanggil metode yang ditimpa dengan cara yang samasuper
memungkinkan Anda memanggil metode yang diganti :Masalah utama dengan ini adalah bahwa itu tidak kompatibel ke belakang: jika Anda memiliki metode yang dipanggil
old
, Anda tidak akan lagi dapat menyebutnya!Penggantian
super
dalam metode override dalamprepend
ed mixin pada dasarnya sama denganold
di proposal ini.redef
kata kunciMirip dengan di atas, tetapi alih-alih menambahkan kata kunci baru untuk memanggil metode yang ditimpa dan membiarkannya
def
sendiri, kami menambahkan kata kunci baru untuk metode mendefinisikan ulang . Ini kompatibel dengan mundur, karena sintaksis saat ini ilegal:Alih-alih menambahkan dua kata kunci baru, kami juga dapat mendefinisikan kembali makna
super
di dalamredef
:Penggantian
redef
ining suatu metode sama dengan mengganti metode dalamprepend
mixin ed.super
dalam metode utama berperilaku sepertisuper
atauold
dalam proposal ini.sumber
bind
samaold_method
?UnboundMethod#bind
akan mengembalikan yang baru, berbedaMethod
, jadi, saya tidak melihat konflik yang muncul, terlepas dari apakah Anda menyebutnya dua kali berturut-turut atau dua kali secara bersamaan dari utas berbeda.old
danredef
? 2.0.0 saya tidak memilikinya. Ah, sulit untuk tidak melewatkan ide-ide lain yang bersaing yang tidak berhasil masuk ke dalam Ruby adalah:Lihatlah metode aliasing, ini semacam mengubah nama metode menjadi nama baru.
Untuk informasi lebih lanjut dan titik awal lihat artikel metode penggantian ini (terutama bagian pertama). The Ruby API docs , juga menyediakan (kurang rumit) misalnya.
sumber
Kelas yang akan membuat override harus di-reload setelah kelas yang berisi metode asli, sehingga
require
di file yang akan membuat overrride.sumber