Objek dan companion

Singleton sebagai pengganti static dan pola factory dengan metode apply.

Daftar isi

Scala tidak memiliki kata kunci static seperti Java. Sebagai gantinya, bahasa ini menyediakan object, yaitu deklarasi yang sekaligus mendefinisikan satu-satunya instance dari tipenya, sebuah bentuk yang disebut singleton. Topik ini membahas deklarasi objek sebagai singleton, pasangan companion object yang berbagi akses privat dengan kelasnya, serta metode apply sebagai pola factory. Setelah topik ini dibaca, pembagian peran antara kelas dan objeknya dapat ditentukan dengan dasar yang jelas.

Objek sebagai singleton#

Kata kunci object mendeklarasikan sebuah tipe beserta satu-satunya instansinya sekaligus. Compiler membuat instance tersebut secara lazy, yaitu baru dibangun ketika salah satu anggotanya diakses pertama kali, dan seluruh program berbagi instance yang sama. Anggota objek dipanggil langsung melalui namanya tanpa new, sehingga object menjadi tempat alami untuk konstanta, utilitas, dan pengganti anggota static pada bahasa lain.

Program berikut mendefinisikan objek QueueStats untuk perhitungan antrean loket. Simpan sebagai queue-stats.scala.

scala queue-stats.scala
// Objek singleton untuk statistik antrean loket
object QueueStats:

  // metode pada objek dipanggil tanpa membuat instance apa pun
  def average(total: Int, count: Int): Double =
    if count == 0 then 0.0 else total.toDouble / count

  // field pada objek berlaku sebagai data milik satu tempat
  val maxPerCustomer: Int = 2

end QueueStats

@main def counter(): Unit =
  val durations = Vector(4, 6, 5)
  println(QueueStats.maxPerCustomer)
  println(QueueStats.average(durations.sum, durations.size))

Jalankan program tersebut dengan scala-cli.

bash
# mengompilasi dan menjalankan program queue-stats.scala
scala-cli run queue-stats.scala
text Keluaran
2
5.0

Metode average memeriksa pembagi sebelum membagi, sehingga antrean kosong menghasilkan 0.0 dan bukan pengecualian. Konversi total.toDouble dilakukan sebelum pembagian agar hasilnya bertipe Double, dan nilai 5.0 pada keluaran berasal dari 15 / 3, yaitu jumlah ketiga durasi antrean yang dibagi tiga. Field maxPerCustomer berlaku sebagai konstanta yang dimiliki objek QueueStats, dan kedua anggota dipanggil tanpa membuat instance apa pun.

Companion object#

Sebuah object yang namanya sama dengan sebuah class dan berada dalam berkas yang sama disebut companion object. Pasangan ini berbagi akses privat dua arah, yaitu objek dapat membaca anggota privat kelas dan sebaliknya. Peran lazimnya adalah menampung anggota yang pada bahasa lain diletakkan sebagai static, ditambah logika pembuatan instance yang perlu menyentuh bagian privat kelasnya.

Program berikut memasangkan class Ticket dengan companion-nya untuk tiket antrean loket. Constructor kelas ditandai private, sehingga satu-satunya cara membuat tiket adalah melalui objek Ticket. Simpan sebagai ticket.scala.

scala ticket.scala
// Tiket antrean loket dengan nomor urut otomatis
class Ticket private (val number: Int, val service: String):

  // metode kelas membaca field privat milik companion-nya
  def label(): String =
    s"Tiket $number untuk layanan $service (terbit: ${Ticket.counter})"

// companion object menjadi satu-satunya jalan membuat Ticket
object Ticket:
  private var counter: Int = 0

  // apply sebagai factory: Tiket("rekening") memanggil apply
  def apply(service: String): Ticket =
    counter += 1
    new Ticket(counter, service)

end Ticket

@main def serviceDesk(): Unit =
  val t1 = Ticket("rekening")
  println(t1.label())
  val t2 = Ticket("tabungan")
  println(t2.label())
bash
# mengompilasi dan menjalankan program ticket.scala
scala-cli run ticket.scala
text Keluaran
Tiket 1 untuk layanan rekening (terbit: 1)
Tiket 2 untuk layanan tabungan (terbit: 2)

Metode label pada instance membaca Ticket.counter yang privat, sedangkan apply pada companion menaikkan penghitung lalu memanggil new Ticket(counter, service). Field counter bertipe var dan tersimpan di dalam companion, sehingga penghitungan nomor urut terpusat pada satu tempat dan tidak dapat diubah dari luar. Keluaran memperlihatkan nomor tiket beserta nomor urut penerbitan yang bertambah satu pada setiap pemanggilan, dan tidak ada cara lain membuat Ticket tanpa melewati penghitung tersebut.

Metode apply sebagai factory#

Konvensi apply membuat pemanggilan terlihat seperti constructor. Apabila nama sebuah objek dipanggil seolah-olah fungsi, compiler menerjemahkan pemanggilan itu menjadi pemanggilan metode apply pada objek tersebut.

scala Contoh: pemanggilan tanpa new diterjemahkan menjadi apply
val t1 = Ticket("rekening")
// bentuk di atas diterjemahkan compiler menjadi
val t2 = Ticket.apply("rekening")

Dengan demikian, Ticket("rekening") dan Ticket.apply("rekening") menghasilkan nilai yang persis sama. Pada contoh tiket, apply bertindak sebagai factory, yaitu titik tunggal pembuatan instance yang menaikkan penghitung lalu meneruskan nomor urutnya ke constructor privat. Pustaka standar menggunakan pola yang sama, misalnya List(1, 2, 3) pada kenyataannya memanggil List.apply(1, 2, 3). Scala 3 memperluas konvensi ini ke kelas biasa karena compiler otomatis menyediakan apply pada companion yang mengarah ke constructor utama, sehingga new tidak lagi wajib ditulis.

Peran apply sebagai factory memberi kendali penuh atas proses pembuatan instance. Validasi, penomoran, pengambilan sumber daya, atau pengembalian instance yang sudah ada semuanya dapat ditempatkan pada satu metode, dan setiap pemanggil melewati jalur yang sama. Apabila aturan pembuatan berubah, satu tempat itulah yang disesuaikan.

Kapan menggunakan objek atau kelas#

Perbedaan keduanya dirangkum pada tabel berikut.

Aspekobjectclass
Jumlah instanceSatu, dibuat compilerBanyak, dibuat pemanggil
Waktu pembuatanSaat akses pertama (lazy)Pada setiap pemanggilan constructor
Padanan anggota staticYaTidak ada
Kegunaan lazimKonstanta, utilitas, factory, titik masuk programData dan perilaku yang berulang

Pembagian perannya mengikuti sifat tersebut. Hal yang hanya ada satu dan tidak memerlukan keadaan per instance diletakkan pada objek, sedangkan bentuk data yang muncul berulang kali dengan keadaan berbeda dideklarasikan sebagai kelas. Keduanya sering membentuk pasangan companion, dengan kelas memuat perilaku per instance dan objek memuat pembuatan instance beserta utilitas yang menyertainya.

Ringkasan#

  • object mendeklarasikan singleton, yaitu satu instance yang dibuat compiler secara lazy saat pertama kali diakses dan dipanggil tanpa new.
  • Companion object adalah objek yang namanya sama dengan kelasnya dan berada dalam berkas yang sama, dengan akses privat dua arah di antara keduanya.
  • Constructor privat bersama companion membatasi pembuatan instance melalui satu titik kendali, seperti penghitung nomor tiket pada contoh.
  • Pemanggilan tanpa new pada nama objek diterjemahkan compiler menjadi pemanggilan metode apply, dan Scala 3 menyediakan apply yang sama untuk kelas biasa.

Referensi#