Topik ini membahas tiga mekanisme yang menopang pemrograman berorientasi objek pada Groovy, yaitu konvensi POGO (Plain Old Groovy Object) untuk kelas data, trait sebagai wadah perilaku yang dapat dikomposisikan, dan metode operator yang menghubungkan tanda operator dengan metode kelas. Ketiganya berdiri di atas prinsip yang sama: kode yang ditulis secara ringkas diterjemahkan oleh kompilator menjadi anggota kelas yang lengkap. Setiap mekanisme didemonstrasikan dengan berkas yang dijalankan nyata beserta keluaran aslinya, sehingga perilaku yang dijelaskan dapat diperiksa ulang di terminal.
POGO dan konvensi properti#
POGO adalah Plain Old Groovy Object, yaitu kelas yang ditulis dalam Groovy dan mengikuti konvensi objek milik bahasa tersebut. Istilah ini mengacu pada POJO (Plain Old Java Object) milik Java, tetapi konvensinya lebih kaya. Kompilator Groovy menghasilkan sejumlah anggota kelas secara otomatis, sehingga kelas data tidak membutuhkan kode berulang yang biasa ditulis manual di Java. Deklarasi properti tanpa pengubah akses, misalnya String judul, memicu pembuatan field privat beserta pasangan metode getter dan setter publik. Field itulah yang menyimpan nilai, sedangkan metode pasangannya yang melayani akses dari luar kelas.
Akses properti pada POGO tidak pernah menyentuh field secara langsung. Ekspresi c.judul diterjemahkan menjadi panggilan c.getJudul(), sedangkan penugasan c.judul = 'X' menjadi panggilan c.setJudul('X'). Pemisahan ini menjaga enkapsulasi karena kode luar tetap melewati metode, dan kelas bebas mengganti isi kedua metode tersebut tanpa mengubah kode pemanggilnya. Tabel berikut merangkum terjemahan tersebut.
| Ekspresi yang ditulis | Panggilan yang diterjemahkan |
|---|---|
c.judul | c.getJudul() |
c.judul = 'X' | c.setJudul('X') |
Selain pasangan metode tersebut, kelas POGO otomatis mendapat dua konstruktor, yaitu satu tanpa argumen dan satu lagi yang menerima Map. Konstruktor Map inilah yang melayani pemanggilan dengan argumen bernama pada Groovy 5, misalnya new Catatan(judul: 'A'). Setiap pasangan nama dan nilai dipetakan ke setter properti yang sesuai, sehingga urutan argumen tidak lagi menjadi masalah dan properti yang tidak disebut mempertahankan nilai default-nya.
Properti dan konstruktor bernama dalam praktik#
Berkas berikut mendemonstrasikan konvensi properti sekaligus konstruktor bernama pada kelas Catatan. Kelas ini hanya berisi dua deklarasi properti tanpa pengubah akses, sedangkan seluruh anggota lainnya dihasilkan oleh kompilator. Jalankan berkasnya dengan perintah groovy, lalu bandingkan setiap baris keluaran dengan baris kode yang memicunya.
// tidak-dijalankan
// Konvensi POGO: properti tanpa modifier menjadi field privat
// beserta getter dan setter otomatis.
class Catatan {
String judul
String isi
}
// konstruktor bernama: setiap properti menjadi argumen bernama
def c = new Catatan(judul: 'Daftar belanja', isi: 'Kopi, gula, dan teh')
println c.judul
c.isi = 'Kopi, gula, teh, dan roti'
println c.isi
// getter dan setter juga dapat dipanggil secara eksplisit
println c.getJudul()
c.setIsi('Kopi, gula, teh, roti, dan keju')
println c.isi
// map constructor: konstruktor yang menerima Map secara utuh
def d = new Catatan(['judul': 'Jadwal piket', 'isi': 'Senin dan Kamis'])
println d.judul# menjalankan demo properti POGO
groovy pogo.groovyDaftar belanja
Kopi, gula, teh, dan roti
Daftar belanja
Kopi, gula, teh, roti, dan keju
Jadwal piketLima baris keluaran menegaskan dua hal. Pertama, sintaks titik pada properti dan pemanggilan eksplisit getJudul() serta setIsi() menghasilkan nilai yang sama karena keduanya berujung pada metode yang sama. Hal ini terlihat pada baris pertama dan baris ketiga yang sama-sama mencetak Daftar belanja. Kedua, konstruktor bernama mengisi properti tanpa menyentuh field secara langsung, dan pemanggilan dengan Map utuh pada baris terakhir berperilaku identik karena keduanya menumpang pada konstruktor Map yang dihasilkan otomatis.
Satu kehati-hatian menyertai konstruktor bernama: nama argumen yang tidak cocok dengan properti mana pun memicu MissingPropertyException saat program berjalan, lengkap dengan saran properti yang paling mirip. Pemeriksaan nama dilakukan pada saat eksekusi dan bukan saat kompilasi, sehingga salah ketik pada konstruktor bernama baru terlihat ketika barisnya dijalankan. Nama properti pada konstruktor perlu disamakan dengan deklarasi kelasnya, atau diperiksa lebih awal dengan anotasi pemeriksaan statis.
Trait dan komposisi perilaku#
Trait adalah unit perilaku yang dideklarasikan dengan kata kunci trait dan disusun ke dalam kelas melalui implements. Isinya dapat berupa implementasi default, deklarasi abstrak, properti, bahkan metode privat. Berbeda dari pewarisan kelas yang hanya boleh satu, sebuah kelas dapat menyusun beberapa trait sekaligus, sehingga kemampuan dari berbagai sumber dapat digabungkan tanpa membangun hierarki kelas yang dalam.
Berkas berikut memuat dua trait dengan karakter yang berbeda. Trait KonversiSuhu mendeklarasikan abstract double getCelsius() dan menyediakan dua metode konversi yang menghitung berdasarkan properti tersebut. Trait BerLabel memuat properti label, metode privat bungkus(), dan metode publik tampil() yang memakainya. Kelas SensorSuhu menyusun keduanya dan hanya tinggal menyediakan properti celsius yang dijanjikan.
// tidak-dijalankan
// Trait: perilaku yang dapat dikomposisikan ke banyak kelas.
trait KonversiSuhu {
// deklarasi abstrak: kelas penyusun wajib menyediakan properti celsius
abstract double getCelsius()
double keFahrenheit() {
return celsius * 9.0d / 5.0d + 32.0d
}
double keKelvin() {
return celsius + 273.15d
}
}
trait BerLabel {
String label = 'tanpa label'
// metode privat boleh ada di dalam trait
private String bungkus(String teks) {
return "[${label}] ${teks}"
}
String tampil(String teks) {
return bungkus(teks)
}
}
// satu kelas menyusun dua trait sekaligus
class SensorSuhu implements KonversiSuhu, BerLabel {
double celsius
}
def s = new SensorSuhu(celsius: 25.0d, label: 'Ruang server')
println s.keFahrenheit()
println s.keKelvin()
println s.tampil('pembacaan pukul 07.00')# menjalankan demo komposisi trait
groovy trait-sensor.groovy77.0
298.15
[Ruang server] pembacaan pukul 07.00Dua angka pertama berasal dari metode konversi yang memakai properti celsius milik kelas penyusun, yaitu 25 derajat Celsius yang menjadi 77 Fahrenheit dan 298,15 Kelvin. Baris ketiga menunjukkan bahwa metode privat di dalam trait dapat dipanggil dari metode publik trait yang sama, dan properti label dari trait ikut terisi melalui konstruktor bernama. Deklarasi abstrak pada trait berperan sebagai kontrak, dan kelas penyusun wajib menyediakan implementasi atau properti yang cocok.
Metode operator dengan plus() dan minus()#
Groovy menerjemahkan setiap operator menjadi panggilan metode, sehingga kelas dapat menentukan sendiri makna operator untuk tipe yang didefinisikannya. Mekanisme ini disebut operator overloading, yaitu pemberian makna tambahan pada operator. Kelas yang mendefinisikan metode plus() otomatis mendukung operator +, tanpa registrasi maupun anotasi tambahan.
Berkas berikut mendefinisikan kelas Vektor dengan metode plus() dan minus(). Keduanya mengembalikan objek Vektor baru, sehingga objek di sisi kiri operator tidak berubah nilainya. Metode toString() juga ditimpa agar hasil cetak terbaca sebagai pasangan koordinat, bukan sebagai nama kelas beserta kode identitas objek.
// tidak-dijalankan
// Metode operator: + dan - diterjemahkan menjadi plus() dan minus().
class Vektor {
int x
int y
Vektor plus(Vektor lain) {
return new Vektor(x: x + lain.x, y: y + lain.y)
}
Vektor minus(Vektor lain) {
return new Vektor(x: x - lain.x, y: y - lain.y)
}
String toString() {
return "(${x}, ${y})"
}
}
def a = new Vektor(x: 3, y: 4)
def b = new Vektor(x: 1, y: 2)
println a + b // memanggil a.plus(b)
println a - b // memanggil a.minus(b)# menjalankan demo metode operator
groovy operator-vektor.groovy(4, 6)
(2, 2)Keluaran (4, 6) dihasilkan oleh penjumlahan koordinat a dan b, sedangkan (2, 2) berasal dari pengurangannya. Kedua hasil dihitung oleh metode yang ditulis sendiri di dalam kelas, dan metode toString() menjadikan cetakannya terbaca. Tabel berikut merangkum operator yang paling sering ditimpa beserta metode sasarnya, sebagaimana didokumentasikan pada halaman operator resmi Groovy.
| Operator | Metode yang dipanggil |
|---|---|
a + b | a.plus(b) |
a - b | a.minus(b) |
a * b | a.multiply(b) |
++a dan a++ | a.next() |
--a dan a-- | a.previous() |
a < b, a > b, dan variasi perbandingan lainnya | a.compareTo(b) |
Ringkasan#
- Properti POGO tanpa pengubah akses menghasilkan field privat beserta getter dan setter publik, dan ekspresi titik diterjemahkan menjadi panggilan kedua metode tersebut.
- Konstruktor bernama dan konstruktor
Mapdihasilkan otomatis, sehingga objek dapat diisi melalui pasangan nama dan nilai tanpa menulis konstruktor manual. - Trait membawa implementasi default, properti, deklarasi abstrak, dan metode privat ke kelas penyusun, dan satu kelas dapat menyusun beberapa trait sekaligus.
- Setiap operator dipetakan ke metode seperti
plus(),minus(),next(),previous(), dancompareTo(), sehingga makna operator ditentukan oleh kelasnya.
Referensi#
- Object orientation — Apache Groovy — Dokumentasi resmi mengenai POGO, properti, konstruktor, dan trait.
- Operators — Apache Groovy — Daftar operator Groovy beserta metode yang menjadi sasaran terjemahannya.
- Trait (groovy.transform.Trait) — Apache Groovy — Dokumentasi API anotasi yang mendasari kompilasi trait.