Module mengelompokkan konstanta, class, dan metode di bawah satu nama, dan Crystal memakainya dalam tiga peran, yaitu namespace, mixin, dan sarana meniru interface. Topik ini membahas ketiganya melalui module Geometri, module BisaDilaporkan, serta module PembuatNomor. Setelah membaca topik ini, kode yang berbagi perilaku dapat dikelompokkan tanpa memaksakan hubungan pewarisan yang tidak alami.
Module sebagai namespace#
Module didefinisikan dengan kata kunci module dan ditutup dengan end. Segala sesuatu yang berada di dalamnya menjadi bagian dari ruang namanya (namespace), sehingga class Lingkaran yang didefinisikan di dalam module Geometri bernama lengkap Geometri::Lingkaran. Konstanta mengikuti aturan yang sama, misalnya Geometri::PI. Penanda :: memisahkan nama namespace dari nama konstanta yang ada di dalamnya, dan penulisan nama lengkap itu wajib dari luar module.
Metode tingkat module didefinisikan dengan def self.nama dan dipanggil langsung melalui nama module-nya, misalnya Geometri.deskripsi. Perlu dicatat bahwa penanda :: tidak digunakan untuk metode, sehingga Geometri::deskripsi ditolak compiler dengan pesan expecting token 'CONST', not 'deskripsi'. Di dalam badan module, konstanta milik module tersebut dapat dipanggil tanpa awalan, sebagaimana PI digunakan langsung oleh class Lingkaran berikut.
module Geometri
PI = 3.14159
class Lingkaran
def initialize(@jari : Float64)
end
def luas : Float64
PI * @jari * @jari
end
end
def self.deskripsi : String
"Modul Geometri menyediakan konstanta dan bentuk dasar"
end
end
puts Geometri::Lingkaran.new(2.0).luas
puts Geometri::PI
puts Geometri.deskripsi# jalankan program geometri.cr
crystal run geometri.cr12.56636
3.14159
Modul Geometri menyediakan konstanta dan bentuk dasarNamespace mencegah bentrokan nama antara pustaka yang berbeda. Class Lingkaran milik Geometri dapat hidup berdampingan dengan class Lingkaran milik module lain tanpa saling menimpa, karena nama lengkapnya berbeda. Module juga boleh bersarang di dalam module lain, sehingga class yang didefinisikan di dalam module Bidang yang berada di dalam module Geometri bernama lengkap Geometri::Bidang::Kotak. Kedalaman itu bebas ditentukan sesuai kebutuhan pengelompokan, dengan konsekuensi nama lengkap yang semakin panjang. Pengelompokan di bawah satu nama ini juga menjadi fondasi peran module lainnya, yaitu mixin yang dibahas pada bagian berikut.
Include: mixin menjadi metode instans#
Apabila sebuah class melakukan include terhadap module, metode-metode instans milik module tersebut menjadi metode instans milik class itu. Pola ini disebut mixin, yaitu penyisipan perilaku dari module ke dalam class tanpa melewati pewarisan. Module yang disertakan boleh memuat abstract def sebagaimana class abstrak, dan setiap class yang menyertakannya wajib menyediakan implementasinya.
Program berikut mendefinisikan module BisaDilaporkan yang menuntut metode ringkasan dan menyediakan metode laporkan yang memakainya. Class Catatan menyertakan module itu, menyediakan ringkasan, lalu memperoleh laporkan secara gratis.
module BisaDilaporkan
abstract def ringkasan : String
def laporkan : Nil
puts "LAPORAN: #{ringkasan}"
end
end
class Catatan
include BisaDilaporkan
def initialize(@judul : String)
end
def ringkasan : String
"Catatan #{@judul}"
end
end
module PembuatNomor
def buat(jumlah : Int32) : Array(String)
(1..jumlah).map { |i| "Nomor #{i}" }
end
end
class Loket
extend PembuatNomor
end
Catatan.new("Rapat tim").laporkan
puts Loket.buat(3)LAPORAN: Catatan Rapat tim
["Nomor 1", "Nomor 2", "Nomor 3"]Baris pertama keluaran menunjukkan hasil mixin: laporkan tidak pernah ditulis di class Catatan, tetapi dapat dipanggil dari objeknya karena datang dari module yang disertakan. Metode itu tetap memanggil ringkasan milik class penerimanya, sehingga perilaku bersama dan data spesifik bertemu tanpa ketergantungan yang rumit.
Extend: metode tingkat class#
Pernyataan extend menyisipkan metode module ke arah yang berbeda, yaitu ke objek class-nya sendiri. Setelah class Loket melakukan extend PembuatNomor, metode buat dapat dipanggil langsung melalui nama class sebagai Loket.buat(3), tanpa membuat objek terlebih dahulu. Baris kedua keluaran di atas menunjukkan hasilnya berupa array berisi tiga nomor antrean.
Pilihan antara include dan extend mengikuti sasarannya. Gunakan include apabila perilaku diperlukan oleh setiap objek yang dibuat dari class, dan gunakan extend apabila perilakunya melekat pada class sebagai sebuah kesatuan, misalnya pembuatan data awal. Bentuk def self.deskripsi pada module Geometri mencapai efek yang sama untuk satu metode yang ditulis langsung, sedangkan extend memasang seluruh kumpulan metode dari sebuah module sekaligus.
Kontrak ala interface#
Bahasa pemrograman lain mengenal interface sebagai daftar metode yang wajib dimiliki sebuah class, dan Crystal tidak menyediakan kata kunci khusus untuk itu. Peran interface diperankan module yang memuat abstract def: class yang menyertakannya, baik class biasa maupun class abstrak, wajib menyediakan seluruh metode yang dideklarasikan. Kontrak pada module BisaDilaporkan di atas berlaku dengan cara itu terhadap class Catatan.
Kontrak yang dilanggar ditolak compiler sebelum program dijalankan. Program berikut menyertakan module yang sama tanpa menyediakan ringkasan.
module BisaDilaporkan
abstract def ringkasan : String
end
class CatatanKosong
include BisaDilaporkan
end
CatatanKosong.newShowing last frame. Use --error-trace for full trace.
In kontrak_rusak.cr:5:1
5 | class CatatanKosong
^
Error: abstract `def BisaDilaporkan#ringkasan()` must be implemented by CatatanKosongKontrak pada module berlaku berantai apabila penerimanya berupa class abstrak yang kemudian diturunkan. Module yang memuat abstract def di-include oleh class abstrak, dan kewajibannya turun ke setiap class konkret di bawahnya. Class konkret yang tetap tidak menyediakan metodenya ditolak compiler dengan pesan must be implemented, tanpa mengubah mekanisme apa pun. Dengan cara ini, kontrak module dan pewarisan class bekerja bersama: module menetapkan kemampuan yang dijanjikan, sedangkan hierarki class mengalirkannya ke seluruh turunan.
Pesan errornya berbunyi sama seperti pelanggaran kontrak pada class abstrak, karena mekanismenya memang sama. Perbaikannya adalah mendefinisikan ringkasan di dalam CatatanKosong. Dengan kontrak ini, module berperan sebagai interface yang menjamin setiap penerimanya dapat dilaporkan, dan janji itu diperiksa compiler pada setiap build.
Ringkasan#
- Module mengelompokkan konstanta, class, dan metode sebagai namespace, dan nama lengkapnya ditulis dengan penanda
::. includemenyisipkan metode instans module ke dalam class, sedangkanextendmenyisipkannya ke objek class sehingga menjadi metode tingkat class.- Module yang memuat
abstract defberperan sebagai interface, dan setiap class yang menyertakannya wajib mengimplementasikan metode tersebut. - Mixin menambah perilaku bersama tanpa menghabiskan kuota satu class dasar yang dimiliki setiap class.
Referensi#
- The Crystal Programming Language Reference — crystal-lang.org — Dokumentasi resmi sintaks dan semantik bahasa, termasuk module, include, dan extend.
- Dokumentasi API Crystal 1.22.1 — crystal-lang.org — Rujukan pustaka standar yang memuat banyak module siap pakai sebagai contoh mixin.