Diketahui bahwa di Ruby, metode kelas diturunkan:
class P
def self.mm; puts 'abc' end
end
class Q < P; end
Q.mm # works
Namun, saya terkejut karena ini tidak berfungsi dengan mixin:
module M
def self.mm; puts 'mixin' end
end
class N; include M end
M.mm # works
N.mm # does not work!
Saya tahu bahwa metode #extend dapat melakukan ini:
module X; def mm; puts 'extender' end end
Y = Class.new.extend X
X.mm # works
Tetapi saya sedang menulis sebuah mixin (atau, lebih tepatnya, ingin menulis) yang berisi metode instan dan metode kelas:
module Common
def self.class_method; puts "class method here" end
def instance_method; puts "instance method here" end
end
Sekarang yang ingin saya lakukan adalah:
class A; include Common
# custom part for A
end
class B; include Common
# custom part for B
end
Saya ingin A, B mewarisi metode instance dan class dari Common
modul. Tapi, tentu saja, itu tidak berhasil. Jadi, bukankah ada cara rahasia untuk membuat warisan ini berfungsi dari satu modul?
Tampaknya tidak enak bagi saya untuk membagi ini menjadi dua modul yang berbeda, satu untuk dimasukkan, yang lain untuk diperluas. Solusi lain yang mungkin adalah menggunakan kelas, Common
bukan modul. Tapi ini hanya solusi. (Bagaimana jika ada dua set fungsi umum Common1
dan Common2
dan kita benar-benar perlu memiliki mixin?) Apakah ada alasan yang mendalam mengapa pewarisan metode kelas tidak bekerja dari mixin?
Jawaban:
Idiom umum adalah menggunakan
included
metode kelas hook and inject dari sana.sumber
include
menambahkan metode instance,extend
menambahkan metode kelas. Begini Cara kerjanya. Saya tidak melihat ketidakkonsistenan, hanya harapan yang tidak terpenuhi :)included
definisi metode ke modul lain dan menyertakan ITU di modul utama Anda;)Berikut adalah kisah lengkapnya, menjelaskan konsep metaprogramming yang diperlukan untuk memahami mengapa penyertaan modul bekerja seperti di Ruby.
Apa yang terjadi jika modul disertakan?
Memasukkan modul ke dalam kelas menambahkan modul ke leluhur kelas. Anda dapat melihat leluhur kelas atau modul apa pun dengan memanggil
ancestors
metodenya:Saat Anda memanggil metode pada sebuah instance
C
, Ruby akan melihat setiap item dari daftar leluhur ini untuk menemukan metode instance dengan nama yang diberikan. Karena kita memasukkanM
keC
,M
sekarang adalah leluhur dariC
, jadi ketika kita memanggilfoo
sebuah instanceC
, Ruby akan menemukan metode itu diM
:Perhatikan bahwa inklusi tidak menyalin setiap contoh atau metode kelas ke kelas - itu hanya menambahkan "catatan" ke kelas yang juga harus mencari metode instan dalam modul yang disertakan.
Bagaimana dengan metode "kelas" dalam modul kita?
Karena inklusi hanya mengubah cara metode instans dikirim, memasukkan modul ke dalam kelas hanya membuat metode instansinya tersedia di kelas itu. Metode "class" dan deklarasi lain dalam modul tidak otomatis disalin ke kelas:
Bagaimana Ruby mengimplementasikan metode kelas?
Di Ruby, kelas dan modul adalah objek biasa - mereka adalah instance dari kelas
Class
danModule
. Ini berarti Anda dapat secara dinamis membuat kelas baru, menugaskannya ke variabel, dll .:Juga di Ruby, Anda memiliki kemungkinan untuk mendefinisikan apa yang disebut metode tunggal pada objek. Metode ini ditambahkan sebagai metode instance baru ke kelas tunggal khusus yang tersembunyi dari objek:
Tapi bukankah kelas dan modul hanya objek biasa juga? Faktanya mereka! Apakah itu berarti bahwa mereka juga dapat memiliki metode tunggal? Ya, benar! Dan begitulah cara metode kelas lahir:
Atau, cara yang lebih umum untuk mendefinisikan metode kelas adalah dengan menggunakan
self
blok definisi kelas, yang mengacu pada objek kelas yang sedang dibuat:Bagaimana cara menyertakan metode kelas dalam modul?
Seperti yang baru saja kita buat, metode kelas sebenarnya hanyalah metode contoh pada kelas tunggal objek kelas. Apakah ini berarti bahwa kita hanya dapat memasukkan modul ke kelas tunggal untuk menambahkan sekumpulan metode kelas? Ya, benar!
Ini
self.singleton_class.include M::ClassMethods
baris tidak terlihat sangat bagus, sehingga Ruby menambahkanObject#extend
, yang melakukan hal yang sama - yaitu termasuk modul ke dalam kelas tunggal dari objek:Memindahkan
extend
panggilan ke modulContoh sebelumnya ini bukanlah kode yang terstruktur dengan baik, karena dua alasan:
include
danextend
dalamHostClass
definisi untuk memasukkan modul kita dengan benar. Ini bisa menjadi sangat rumit jika Anda harus menyertakan banyak modul serupa.HostClass
referensi langsungM::ClassMethods
, yang merupakan detail implementasi dari modulM
yangHostClass
seharusnya tidak perlu diketahui atau dipedulikan.Jadi bagaimana dengan ini: ketika kita memanggil
include
baris pertama, kita entah bagaimana memberi tahu modul bahwa itu telah disertakan, dan juga memberikannya objek kelas kita, sehingga itu bisa memanggilextend
dirinya sendiri. Dengan cara ini, tugas modul adalah menambahkan metode kelas jika diinginkan.Untuk itulah tepatnya metode khusus
self.included
itu. Ruby secara otomatis memanggil metode ini setiap kali modul dimasukkan ke dalam kelas lain (atau modul), dan meneruskan objek kelas host sebagai argumen pertama:Tentu saja, menambahkan metode kelas bukanlah satu-satunya hal yang dapat kita lakukan
self.included
. Kami memiliki objek kelas, jadi kami dapat memanggil metode (kelas) lainnya di atasnya:sumber
Seperti yang disebutkan Sergio dalam komentar, untuk orang-orang yang sudah menggunakan Rails (atau tidak keberatan bergantung pada Dukungan Aktif ),
Concern
sangat membantu di sini:sumber
Anda bisa mendapatkan kue dan memakannya juga dengan melakukan ini:
Jika Anda berniat untuk menambahkan instance, dan variabel kelas, Anda akan berakhir dengan menarik rambut Anda karena Anda akan menemukan banyak kode yang rusak kecuali Anda melakukannya dengan cara ini.
sumber