Pewarisan memungkinkan class baru dibangun di atas class yang sudah ada sambil mewarisi metodenya, sedangkan class abstrak menetapkan kontrak yang wajib dipenuhi turunannya. Topik ini membahas penulisan pewarisan dengan tanda <, kewajiban mengimplementasikan metode abstrak, serta dispatch subtipe (subtype dispatch), yaitu pemanggilan metode milik class asli dari variabel yang bertipe class dasar. Studi kasusnya adalah hierarki class Bentuk dengan perhitungan luasnya.
Pewarisan dengan tanda lebih kecil#
Relasi pewarisan ditulis dengan tanda < pada baris class, misalnya class PersegiPanjang < Bentuk yang menyatakan PersegiPanjang sebagai anak dari Bentuk. Class anak menerima seluruh metode milik class dasarnya, boleh menambah metode baru, dan boleh mendefinisikan ulang metode yang sudah ada dengan nama serta tipe yang sama. Definisi ulang semacam itu disebut override, dan kata kunci super memanggil versi milik class dasar dari dalamnya.
# tidak-dijalankan
class PersegiPanjang < Bentuk
def deskripsi : String
super + " (empat sisi)"
end
endMetode yang tidak didefinisikan ulang diwariskan tanpa perubahan apa pun, sehingga perilaku yang berlaku umum cukup ditulis satu kali pada class dasar. Konstanta milik class dasar ikut tersedia bagi turunannya, sehingga konstanta RODA yang didefinisikan di class Kendaraan dapat digunakan langsung di dalam class Mobil tanpa awalan. Satu class hanya boleh memiliki satu class dasar langsung, tetapi rantai pewarisannya boleh panjang, misalnya class yang menurunkan class yang menurunkan class lain. Relasi ini membentuk hierarki, dan hierarki itulah yang menjadi dasar dispatch subtipe pada bagian akhir topik.
Class abstrak dan abstract def#
Class dasar pada banyak hierarki tidak dirancang untuk dibuat objeknya, misalnya Bentuk yang tanpa panjang, lebar, atau jari-jari tidak memiliki arti luas. Crystal menyediakan abstract class untuk keperluan itu. Pembuatan objek dari class abstrak ditolak compiler dengan pesan can't instantiate abstract class Bentuk, sedangkan turunan konkret dari class tersebut tetap bebas dibuat. Peran class abstrak adalah menjadi penanda tipe sekaligus pemegang kontrak.
Kontrak itu ditulis sebagai abstract def, yaitu deklarasi metode dengan nama dan tipe lengkap tetapi tanpa badan. Setiap class turunan yang konkret wajib menyediakan implementasinya, dan compiler menolak program yang melanggarnya. Program berikut mendeklarasikan abstract def luas : Float64 pada class abstrak, lalu mendefinisikan class Segitiga yang tidak menyediakan metode itu.
abstract class Bentuk
abstract def luas : Float64
end
class Segitiga < Bentuk
def initialize(@alas : Float64, @tinggi : Float64)
end
end
Segitiga.new(6.0, 4.0)Showing last frame. Use --error-trace for full trace.
In bentuk_rusak.cr:5:1
5 | class Segitiga < Bentuk
^
Error: abstract `def Bentuk#luas()` must be implemented by SegitigaPesan error menunjuk langsung ke baris class yang menyalahi kontrak, sehingga pelanggarannya ketahuan sebelum program dijalankan. Perbaikannya satu-satunya, yaitu mendefinisikan luas di dalam Segitiga dengan tipe balik Float64. Mekanisme ini membuat kontrak pada class abstrak menjadi jaminan, bukan sekadar dokumen: selama program berhasil di-compile, setiap objek konkret di bawah Bentuk pasti dapat menghitung luasnya.
Dispatch subtipe#
Variabel yang bertipe class dasar boleh diisi objek dari salah satu turunannya. Compiler menangani keadaan itu dengan membentuk tipe virtual, yang ditulis Bentuk+ dan berarti objek bertipe Bentuk atau salah satu turunannya. Metode yang dipanggil melalui variabel semacam itu diarahkan ke implementasi milik class asli objeknya, sehingga pemanggilan yang sama menghasilkan perilaku berbeda sesuai isi variabel.
abstract class Bentuk
abstract def luas : Float64
def deskripsi : String
"#{self.class.name} dengan luas #{luas}"
end
end
class PersegiPanjang < Bentuk
def initialize(@panjang : Float64, @lebar : Float64)
end
def luas : Float64
@panjang * @lebar
end
end
class Lingkaran < Bentuk
PI = 3.14159
def initialize(@jari : Float64)
end
def luas : Float64
PI * @jari * @jari
end
end
bentuk : Bentuk = PersegiPanjang.new(4.0, 5.0)
puts bentuk.deskripsi
bentuk = Lingkaran.new(2.0)
puts bentuk.deskripsi
koleksi = [] of Bentuk
koleksi << PersegiPanjang.new(10.0, 2.0)
koleksi << Lingkaran.new(1.0)
koleksi.each do |b|
puts b.deskripsi
end# jalankan program bentuk.cr
crystal run bentuk.crPersegiPanjang dengan luas 20.0
Lingkaran dengan luas 12.56636
PersegiPanjang dengan luas 20.0
Lingkaran dengan luas 3.14159Variabel bentuk dinyatakan bertipe Bentuk, tetapi dua pemanggilan pertamanya menghasilkan deskripsi berbeda karena isinya berganti dari PersegiPanjang menjadi Lingkaran. Pada baris koleksi = [] of Bentuk, tipe elemen array menjadi tipe virtual yang sama, sehingga satu array menampung kedua class tanpa konversi apa pun. Metode deskripsi sendiri hanya ditulis satu kali di class dasar, sedangkan luas yang dipanggilnya diarahkan ke implementasi masing-masing, dan self.class.name menuliskan nama class asli objeknya.
Dispatch yang longgar di satu sisi tetap diperiksa ketat di sisi lain. Metode yang dipanggil melalui tipe virtual harus tersedia pada seluruh anggotanya, karena compiler tidak menjamin objek mana yang sedang dibaca. Program berikut menambahkan metode keliling hanya pada class PersegiPanjang, lalu memanggilnya melalui elemen array bertipe [] of Bentuk yang juga menampung Lingkaran.
abstract class Bentuk
abstract def luas : Float64
end
class PersegiPanjang < Bentuk
def initialize(@panjang : Float64, @lebar : Float64)
end
def luas : Float64
@panjang * @lebar
end
def keliling : Float64
2 * (@panjang + @lebar)
end
end
class Lingkaran < Bentuk
def initialize(@jari : Float64)
end
def luas : Float64
3.14159 * @jari * @jari
end
end
koleksi = [] of Bentuk
koleksi << PersegiPanjang.new(4.0, 5.0)
koleksi << Lingkaran.new(2.0)
koleksi.each do |b|
puts b.keliling
endShowing last frame. Use --error-trace for full trace.
In bentuk_keliling.cr:31:10
31 | puts b.keliling
^-------
Error: undefined method 'keliling' for Lingkaran (compile-time type is Bentuk+)Pesan error menyebut tipe virtual Bentuk+ secara eksplisit sebagai tipe waktu compile. Compiler menolak pemanggilan itu karena Lingkaran tidak mengenal keliling, dan penolakan ini mencegah error yang biasanya baru muncul saat program berjalan pada bahasa yang tidak memeriksa tipe. Perbaikannya mengikuti maksud desain, yaitu mendeklarasikan keliling pada class dasar bersama metode abstrak lainnya apabila seluruh bentuk memang memilikinya, atau memeriksa tipe objek terlebih dahulu sebelum memanggil metode khususnya.
Diagram berikut merangkum hierarki yang digunakan sepanjang topik ini beserta metode yang menjadi tanggung jawab masing-masing class.
Ringkasan#
- Pewarisan ditulis dengan tanda
<, dan metode yang tidak didefinisikan ulang diwariskan dari class dasar. - Class abstrak tidak dapat dibuat objeknya, sedangkan
abstract defmenjadi kontrak yang wajib diimplementasikan setiap turunan konkret. - Variabel bertipe class dasar memuat tipe virtual seperti
Bentuk+dan memanggil metode milik class asli objeknya. - Dispatch subtipe memungkinkan satu kumpulan objek beragam diperlakukan seragam tanpa menghilangkan perilaku spesifiknya.
Referensi#
- The Crystal Programming Language Reference — crystal-lang.org — Dokumentasi resmi sintaks dan semantik bahasa, termasuk pewarisan, class abstrak, dan tipe virtual.
- Dokumentasi API Crystal 1.22.1 — crystal-lang.org — Rujukan pustaka standar untuk memeriksa metode bawaan yang diwariskan setiap class.