Objek dan kelas secara mendalam

Metode kelas, Class sebagai objek, dan mekanika sebenarnya di balik .new.

Daftar isi

Setelah kelas, metode, dan pewarisan dikuasai, pertanyaan tersisa tentang mekanikanya dapat dijawab, yaitu apa yang sebenarnya terjadi ketika new dipanggil dan di mana metode yang dipanggil pada kelas itu sendiri berada. Topik ini membahas metode kelas, kedudukan kelas sebagai objek dari kelas Class, mekanika Class#new yang menjalankan allocate lalu initialize, serta perkakas pemeriksaan seperti instance_methods, is_a?, dan instance_of?. Setelah membacanya, mekanika di balik pembuatan objek dan pemeriksaan jenis objek dapat dijelaskan tanpa dugaan.

Metode kelas#

Metode kelas adalah metode yang dipanggil pada kelasnya, bukan pada instansnya, dan didefinisikan dengan awalan self. di dalam tubuh kelas, misalnya def self.batas_minimum. Bentuk penulisan lain yang setara adalah def SensorSuhu.batas_minimum, tetapi bentuk self. lebih disukai karena tetap benar meskipun nama kelasnya kelak diganti. Metode semacam ini cocok untuk nilai atau layanan yang terikat pada kelas secara keseluruhan dan tidak menyangkut data satu instans tertentu.

ruby metode-kelas.rb
# Metode kelas didefinisikan dengan self di dalam kelas.
class SensorSuhu
  def self.batas_minimum
    -10
  end

  def self.batas_maksimum
    40
  end

  def validasi(celsius)
    celsius >= SensorSuhu.batas_minimum && celsius <= SensorSuhu.batas_maksimum
  end
end

p SensorSuhu.batas_minimum
p SensorSuhu.batas_maksimum

p SensorSuhu.new.validasi(28)
p SensorSuhu.new.validasi(45)
text Keluaran
-10
40
true
false

Panggilan SensorSuhu.batas_minimum menggunakan kelas sebagai penerimanya, dan metode instans validasi pun membaca kedua batas itu lewat nama kelasnya. Metode kelas dan metode instans tersimpan dalam dua daftar yang terpisah, sehingga SensorSuhu.new.batas_minimum memunculkan NoMethodError, sebagaimana SensorSuhu.validasi juga ditolak. Pembedaan ini menjelaskan mengapa Siswa.new sah, yaitu new memang metode kelas milik Class yang diwarisi setiap kelas.

Di dalam metode kelas, self adalah kelasnya sendiri, dan bukan instans apa pun. Fakta ini membuka pola yang lazim, yaitu metode kelas yang menjadi cara pembuatan objek dengan nama yang lebih deskriptif, misalnya metode kelas yang menyusun data lalu mengembalikan instans yang sudah siap pakai. Karena kelas dapat dipanggil metode seperti objek lain, pola semacam ini ditulis dengan perkakas yang sudah dikenal.

Kelas juga objek#

Nama kelas pada Ruby adalah konstanta yang menunjuk sebuah objek, dan kelas dari objek itu adalah Class. Konsekuensinya konsisten dengan prinsip bahwa segala sesuatu adalah objek, yaitu kelas dapat disimpan ke dalam variabel, diteruskan sebagai argumen, dan metodenya dapat dipanggil seperti objek lain. Bahkan kelas Class itu sendiri adalah objek yang bertipe Class, sehingga pertanyaan tentang kelasnya tidak berujung pada sesuatu yang bukan objek.

ruby kelas-objek.rb
# Setiap kelas adalah objek instans dari kelas Class.
class Siswa
  def initialize(nama)
    @nama = nama
  end
end

p Siswa.class
p String.class
p Integer.class
p Class.class

kelas = Siswa
siswa = kelas.new("Citra")

p siswa.class
text Keluaran
Class
Class
Class
Class
Siswa

Empat baris pertama menegaskan bahwa Siswa, String, Integer, bahkan Class semuanya adalah objek bertipe Class. Penugasan kelas = Siswa tidak menyalin apa pun, melainkan membuat dua nama menunjuk objek kelas yang sama, sehingga kelas.new membangun instans Siswa seperti biasa. Kemampuan memperlakukan kelas sebagai nilai inilah yang membuat kode dapat memilih kelasnya saat berjalan, misalnya berdasarkan data yang dibaca.

Karena nama kelas hanyalah konstanta yang menunjuk objek, tidak ada yang istimewa dari penerusannya sebagai argumen. Sebuah metode dapat menerima kelas, lalu memanggil new atau metode kelas lainnya pada argumen itu, dan pola ini muncul pada kode yang memilih kelas sesuai data masukannya. Dengan begitu, satu metode dapat melayani beberapa kelas tanpa mengulang logika pembuatannya.

Mekanika di balik new#

Pemanggilan Siswa.new menjalankan dua langkah yang berurutan. Langkah pertama adalah allocate, yaitu penciptaan instans kosong tanpa variabel instans yang terisi. Langkah kedua adalah pemanggilan initialize pada instans baru itu untuk mengisi keadaannya. Keduanya adalah kerja Class#new, dan pemisahan dua langkah ini dapat dibuktikan dengan memanggil allocate secara langsung.

ruby mekanika-new.rb
# Class#new menjalankan allocate lalu initialize.
class Siswa
  def initialize(nama)
    @nama = nama
    puts "initialize dijalankan untuk #{nama}"
  end
end

siswa = Siswa.new("Citra")

kosong = Siswa.allocate

p kosong.class
p kosong.instance_variable_defined?(:@nama)
text Keluaran
initialize dijalankan untuk Citra
Siswa
false

Pesan initialize dijalankan untuk Citra hanya muncul sekali, yaitu melalui pemanggilan new. Objek kosong tetap bertipe Siswa karena allocate memang membuat instans kelas yang sama, tetapi instance_variable_defined?(:@nama) melaporkan false, yang berarti initialize tidak pernah berjalan padanya. Bukti ini menegaskan mekanikanya, yaitu new setara dengan allocate diikuti initialize, dan kelas tanpa initialize tetap bisa dibuat karena versi bawaannya tidak melakukan apa-apa.

Metode initialize sendiri tidak pernah dipanggil secara langsung oleh kode di luar, karena metode itu bersifat private sejak awal dan hanya dieksekusi oleh new. Perancang kelas cukup mengurus isi initialize, sedangkan urutan allocate lalu initialize dijalankan oleh Class#new secara konsisten untuk semua kelas. Pemisahan peran ini membuat perilaku pembuatan objek seragam di seluruh bahasa Ruby, tanpa terkecuali kelas buatan sendiri.

Mengintip isi kelas#

Ruby menyediakan perkakas pemeriksaan yang membuat isi kelas dapat diaudit saat program berjalan. Metode instance_methods(false) melaporkan metode instans yang didefinisikan di kelas itu saja, metode obj.class menyebut kelas milik objek, sedangkan is_a? dan instance_of? memeriksa jenis objek dengan tingkat ketelitian yang berbeda.

ruby intip-kelas.rb
# Mengintip metode milik kelas dan memeriksa jenis objek.
class Siswa
  attr_accessor :nama

  def initialize(nama)
    @nama = nama
  end

  def sapa
    "Halo, #{@nama}"
  end
end

p Siswa.instance_methods(false).sort

siswa = Siswa.new("Budi")

p siswa.class
p siswa.is_a?(Siswa)
p siswa.is_a?(Object)
p siswa.instance_of?(Siswa)
p siswa.instance_of?(Object)
text Keluaran
[:nama, :nama=, :sapa]
Siswa
true
true
true
false

Daftar [:nama, :nama=, :sapa] memuat tiga metode hasil definisi di kelas itu, termasuk sepasang metode dari attr_accessor, dan argumen false menjaga metode warisan agar tidak ikut tercantum. Pemeriksaan is_a?(Object) bernilai true karena Object termasuk dalam rantai leluhur Siswa, sedangkan instance_of?(Object) bernilai false karena metode ini hanya menerima kelas yang persis sama dengan kelas objeknya. Aturan praktisnya, gunakan is_a? ketika yang ditanyakan adalah keanggotaan rantai pewarisan dan instance_of? ketika kelasnya harus persis.

Perkakas pemeriksaan ini saling melengkapi pada pekerjaan sehari-hari. instance_methods(false) menjawab apa saja yang dapat dilakukan instans, obj.class menjawab dari kelas mana objek itu berasal, sedangkan is_a? dan instance_of? menjawab hubungan objek dengan kelas tertentu. Semuanya hanya membaca keadaan program dan tidak mengubah apa pun, sehingga aman dipanggil kapan saja untuk memeriksa dugaan.

Ringkasan#

  • Metode kelas didefinisikan dengan awalan self. dan dipanggil pada kelasnya, terpisah dari metode instans yang dipanggil pada objek.
  • Nama kelas adalah konstanta yang menunjuk objek bertipe Class, sehingga kelas dapat disimpan, diteruskan sebagai argumen, dan new dapat dipanggil dari nama mana pun.
  • Class#new membuat instans dengan allocate, lalu menjalankan initialize pada instans baru tersebut.
  • instance_methods(false), obj.class, is_a?, dan instance_of? melaporkan isi kelas serta jenis objek, dengan is_a? menerima seluruh leluhur dan instance_of? menuntut kelas persis.

Referensi#