Module

Module sebagai namespace, mixin yang disertakan, dan sarana meniru interface.

Daftar isi

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.

crystal geometri.cr
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
bash
# jalankan program geometri.cr
crystal run geometri.cr
text Keluaran
12.56636
3.14159
Modul Geometri menyediakan konstanta dan bentuk dasar

Namespace 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.

crystal kontrak.cr
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)
text Keluaran
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.

crystal kontrak_rusak.cr
module BisaDilaporkan
  abstract def ringkasan : String
end

class CatatanKosong
  include BisaDilaporkan
end

CatatanKosong.new
text Keluaran
Showing 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 CatatanKosong

Kontrak 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 ::.
  • include menyisipkan metode instans module ke dalam class, sedangkan extend menyisipkannya ke objek class sehingga menjadi metode tingkat class.
  • Module yang memuat abstract def berperan 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#