Kapan harus menggunakan kelas bersarang dan kelas bersarang dalam modul?

144

Saya cukup akrab dengan kapan harus menggunakan subclass dan modul, tetapi baru-baru ini saya telah melihat kelas bersarang seperti ini:

class Foo
  class Bar
    # do some useful things
  end
end

Serta kelas yang bersarang dalam modul seperti:

module Baz
  class Quux
    # more code
  end
end

Dokumentasi dan artikel jarang atau saya tidak dididik pada subjek yang cukup untuk meraba-raba istilah pencarian yang tepat, tetapi saya sepertinya tidak dapat menemukan banyak informasi tentang topik tersebut.

Bisakah seseorang memberikan contoh atau tautan ke posting tentang mengapa / kapan teknik itu akan digunakan?

cmhobb
sumber

Jawaban:

140

Bahasa OOP lainnya memiliki kelas dalam yang tidak dapat dipakai tanpa terikat dengan kelas tingkat atas. Misalnya, di Jawa,

class Car {
    class Wheel { }
}

hanya metode di Carkelas yang dapat membuat Wheels.

Ruby tidak memiliki perilaku itu.

Di Ruby,

class Car
  class Wheel
  end
end

berbeda dari

class Car
end

class Wheel
end

hanya dalam nama kelas Wheelvs Car::Wheel. Perbedaan nama ini dapat membuat eksplisit untuk programmer bahwa Car::Wheelkelas hanya dapat mewakili roda mobil, sebagai lawan dari roda umum. Definisi kelas bersarang di Ruby adalah masalah preferensi, tetapi melayani tujuan dalam arti bahwa itu lebih kuat menegakkan kontrak antara dua kelas dan dengan demikian menyampaikan lebih banyak informasi tentang mereka dan penggunaannya.

Tetapi bagi interpreter Ruby, itu hanya perbedaan nama.

Sedangkan untuk pengamatan kedua Anda, kelas yang bersarang di dalam modul umumnya digunakan untuk namespace kelas. Misalnya:

module ActiveRecord
  class Base
  end
end

berbeda dari

module ActionMailer
  class Base
  end
end

Meskipun ini bukan satu-satunya penggunaan kelas yang bersarang di dalam modul, pada umumnya yang paling umum.

Pan Thomakos
sumber
5
@rubyprince, saya tidak yakin apa yang Anda maksud dengan membangun hubungan antara Car.newdan Car::Wheel.new. Anda pasti tidak perlu menginisialisasi Carobjek untuk menginisialisasi Car::Wheelobjek di Ruby, tetapi Carkelas harus dimuat dan dieksekusi Car::Wheelagar dapat digunakan.
Pan Thomakos
30
@Pan, Anda membingungkan kelas dalam Java dan kelas Ruby namespaced. Kelas bersarang non-statis Java disebut kelas dalam dan itu hanya ada dalam instance dari kelas luar. Ada bidang tersembunyi yang memungkinkan referensi luar. Kelas dalam Ruby hanya dengan namespace dan tidak "terikat" dengan kelas yang disertakan dengan cara apa pun. Ini setara dengan kelas Java statis (bersarang) . Ya, jawabannya memiliki banyak suara tetapi tidak sepenuhnya akurat.
DigitalRoss
7
Saya tidak tahu bagaimana jawaban ini mendapat 60 upvotes, apalagi diterima oleh OP. Tidak ada satu pun pernyataan benar di sini. Ruby tidak memiliki kelas bersarang seperti Beta atau Newspeak. Sama sekali tidak ada hubungan apa pun antara Cardan Car::Wheel. Modul (dan dengan demikian kelas) hanyalah ruang nama untuk konstanta, tidak ada yang namanya kelas bersarang atau modul bersarang di Ruby.
Jörg W Mittag
4
Satu-satunya perbedaan antara keduanya adalah resolusi konstan (yang leksikal, dan dengan demikian jelas berbeda karena kedua snippet berbeda secara leksikal). Namun, sama sekali tidak ada perbedaan antara keduanya mengenai kelas yang terlibat. Hanya ada dua kelas yang sama sekali tidak terkait. Titik. Ruby tidak memiliki kelas bersarang / dalam. Definisi dari kelas bersarang adalah benar, tetapi itu hanya tidak berlaku untuk Ruby, karena Anda sepele dapat menguji: Car::Wheel.new. Ledakan. Saya baru saja membangun Wheelobjek yang tidak bersarang di dalam Carobjek.
Jörg W Mittag
10
Posting ini sangat menyesatkan tanpa membaca seluruh utas komentar.
Nathan
50

Di Ruby, mendefinisikan kelas bertingkat mirip dengan mendefinisikan kelas dalam sebuah modul. Itu tidak benar-benar memaksa hubungan antara kelas, itu hanya membuat namespace untuk konstanta. (Nama Kelas dan Modul adalah konstanta.)

Jawaban yang diterima tidak benar tentang apa pun. 1 Dalam contoh di bawah ini saya membuat turunan dari kelas tertutup secara leksikal tanpa turunan dari kelas penutup yang pernah ada.

class A; class B; end; end
A::B.new

Keuntungannya sama dengan yang ada pada modul: enkapsulasi, pengelompokan kode yang hanya digunakan di satu tempat, dan menempatkan kode lebih dekat ke tempat digunakannya. Proyek besar mungkin memiliki satu modul luar yang terjadi berulang-ulang di setiap file sumber dan berisi banyak definisi kelas. Ketika berbagai kerangka kerja dan kode perpustakaan semua melakukan ini, maka mereka hanya berkontribusi satu nama masing-masing ke tingkat atas, mengurangi kemungkinan konflik. Prosa, tentu saja, tapi itu sebabnya mereka digunakan.

Menggunakan kelas alih-alih modul untuk mendefinisikan namespace luar mungkin masuk akal dalam program atau skrip satu file, atau jika Anda sudah menggunakan kelas tingkat atas untuk sesuatu, atau jika Anda benar-benar akan menambahkan kode untuk menghubungkan kelas bersama-sama dalam gaya kelas batin sejati . Ruby tidak memiliki kelas dalam tetapi tidak ada yang menghentikan Anda untuk membuat perilaku yang sama dalam kode. Merujuk objek luar dari yang dalam masih akan membutuhkan titik-titik dari instance objek luar tetapi kelas bersarang akan menyarankan bahwa ini adalah apa yang mungkin Anda lakukan. Program termodulasi dengan hati-hati mungkin selalu membuat kelas-kelas yang melampirkan terlebih dahulu, dan mereka mungkin cukup didekomposisi dengan kelas bersarang atau batin. Anda tidak dapat memanggil newmodul.

Anda dapat menggunakan pola umum bahkan untuk skrip, di mana namespace tidak terlalu dibutuhkan, hanya untuk bersenang-senang dan berlatih ...

#!/usr/bin/env ruby

class A
  class Realwork_A
    ...
  end
  class Realwork_B
    ...
  end

  def run
    ...
  end

  self
end.new.run
DigitalRoss
sumber
15
Tolong, tolong, jangan anggap ini kelas dalam. Ini bukan. Kelas Bini tidak dalam kelas A. The konstan B -namespace dalam kelas A, tapi sama sekali tidak ada hubungan antara objek direferensikan oleh B(yang dalam hal ini hanya terjadi menjadi kelas) dan kelas direferensikan oleh A.
Jörg W Mittag
2
Oke, terminologi "bagian dalam" dihapus. Poin yang bagus. Bagi mereka yang tidak mengikuti argumen di atas, alasan perselisihan adalah bahwa ketika Anda melakukan sesuatu seperti ini di, katakanlah, Java, objek kelas batin (dan di sini saya menggunakan istilah kanonik) berisi referensi ke kelas luar dan variabel instance luar dapat dirujuk dengan metode kelas dalam. Tidak ada yang terjadi di Ruby kecuali Anda menautkannya dengan kode. Dan Anda tahu, jika kode itu ada di kelas, ahem, melampirkan, maka saya yakin Anda bisa menyebut Bar sebagai kelas dalam.
DigitalRoss
1
Bagi saya ini adalah pernyataan yang paling membantu saya ketika memutuskan antara modul dan kelas: You can't call new on a module.- jadi dalam istilah dasar jika saya hanya ingin namespace beberapa kelas dan tidak perlu benar-benar membuat turunan dari "kelas" luar, maka Saya akan menggunakan modul luar. Tetapi jika saya ingin instantiate instance dari "kelas" pembungkus / luar maka saya akan membuatnya menjadi Kelas bukan Modul. Setidaknya ini masuk akal bagi saya.
FireDragon
@FireDragon Atau use case lain mungkin adalah Anda menginginkan kelas yang merupakan Factory, yang mewarisi dari subclass, dan pabrik Anda bertanggung jawab untuk membuat instance dari subclass. Dalam situasi itu, Pabrik Anda tidak dapat menjadi modul karena Anda tidak dapat mewarisi dari modul, jadi ini adalah kelas induk yang tidak Anda instantiate (dan dapat 'namespace' itu anak-anak jika Anda mau, semacam modul)
rmcsharry
15

Anda mungkin ingin menggunakan ini untuk mengelompokkan kelas Anda ke dalam modul. Semacam hal namespace.

misalnya permata Twitter menggunakan ruang nama untuk mencapai ini:

Twitter::Client.new

Twitter::Search.new

Jadi keduanya Clientdan Searchkelas hidup di bawah Twittermodul.

Jika Anda ingin memeriksa sumbernya, kode untuk kedua kelas dapat ditemukan di sini dan di sini .

Semoga ini membantu!

Pablo Fernandez
sumber
3
Tautan Twitter gem diperbarui: github.com/sferik/twitter/tree/master/lib/twitter
kode
6

Ada perbedaan lain antara kelas bersarang dan modul bersarang di Ruby sebelum 2,5 yang jawaban lain gagal untuk menutupi yang saya rasa harus disebutkan di sini. Ini adalah proses pencarian.

Singkatnya: karena pencarian konstan tingkat atas di Ruby sebelum 2.5, Ruby mungkin akhirnya mencari kelas bersarang Anda di tempat yang salah ( Objectkhususnya) jika Anda menggunakan kelas bersarang.

Di Ruby sebelum 2.5:
Struktur kelas bersarang: Misalkan Anda memiliki kelas X, dengan kelas bertingkat Y, atau X::Y. Dan kemudian Anda memiliki kelas tingkat atas bernama juga Y. Jika X::Ytidak dimuat, maka hal berikut terjadi ketika Anda menelepon X::Y:

Setelah tidak ditemukan YdiX , Ruby akan mencoba untuk mencarinya di nenek moyang X. SejakXadalah kelas dan bukan modul, ia memiliki leluhur, di antaranya adalah [Object, Kernel, BasicObject]. Jadi, ia mencoba mencari , Ydi Objectmana ia berhasil menemukannya.

Namun itu adalah level atas Ydan bukan X::Y. Anda akan mendapatkan peringatan ini:

warning: toplevel constant Y referenced by X::Y


Struktur modul bersarang: Misalkan pada contoh sebelumnya Xadalah modul dan bukan kelas.

Modul hanya memiliki dirinya sebagai leluhur: X.ancestorsakan menghasilkan [X].

Dalam hal ini, Ruby tidak akan dapat mencari Ydi salah satu leluhur Xdan akan melempar NameError. Rails (atau kerangka kerja lainnya dengan autoloading) akan mencoba memuat X::Ysetelah itu.

Lihat artikel ini untuk informasi lebih lanjut: https://blog.jetbrains.com/ruby/2017/03/why-you-should-not-use-a--class-as-a-namespace-in-rails-applications/

In Ruby 2.5:
Pencarian konstan tingkat atas dihapus.
Anda dapat menggunakan kelas bersarang tanpa takut menemukan bug ini.

Timur Nugmanov
sumber
3

Selain jawaban sebelumnya: Modul di Ruby adalah kelas

$ irb
> module Some end
=> nil
> Some.class
=> Module
> Module.superclass
=> Object
demo
sumber
11
Anda akan lebih akurat untuk mengatakan bahwa 'kelas dalam Ruby adalah modul'!
Tim Diggins
2
Semua yang ada di Ruby dapat berupa objek, tetapi memanggil modul suatu kelas sepertinya tidak benar: irb (main): 005: 0> Class.ancestors.reverse => [BasicObject, Kernel, Object, Module, Class]
Chad M
dan di sini kita sampai pada definisi "adalah"
ahnbizcad
Perhatikan bahwa modul tidak dapat digunakan. Yaitu, tidak mungkin membuat objek dari modul. Jadi modul, tidak seperti kelas, tidak memiliki metode new. Jadi meskipun Anda dapat mengatakan Modul adalah kelas (huruf kecil), mereka tidak sama dengan Kelas (huruf besar) :)
rmcsharry