Getter dan setter

Getter dan setter sebagai properti terhitung di balik sintaks bidang.

Daftar isi

Getter dan setter mengubah anggota kelas menjadi properti yang dapat dibaca dan ditulis seperti bidang, padahal di baliknya terdapat perhitungan atau pemeriksaan nilai. Topik ini membahas getter sebagai properti terhitung (computed property), setter dengan penulisan dua arah dan pemeriksaan nilai pada bidang privat, serta error yang muncul apabila sebuah properti hanya dapat dibaca. Setiap contoh berupa berkas utuh yang dijalankan dengan Dart SDK 3.13 dan disertai keluaran aslinya.

Properti terhitung dengan getter#

Getter (getter) adalah anggota kelas yang dideklarasikan dengan kata kunci get dan dipanggil tanpa tanda kurung, sehingga pemanggilannya tampak persis seperti membaca bidang. Nilainya tidak disimpan di dalam objek, melainkan dihitung setiap kali dibaca berdasarkan bidang lain atau ekspresi apa pun di dalam kelas. Bentuk yang lazim menggunakan sintaks panah => karena isinya hanya satu ekspresi.

dart suhu.dart
class Suhu {
  double celsius;

  Suhu(this.celsius);

  double get fahrenheit => celsius * 9 / 5 + 32;
}

void main() {
  final suhu = Suhu(30);
  print('${suhu.celsius} C = ${suhu.fahrenheit} F');
}
text Keluaran
30.0 C = 86.0 F

Kelas Suhu hanya menyimpan satu bidang, yaitu celsius, sedangkan nilai Fahrenheit selalu diturunkan dari bidang tersebut saat getter fahrenheit dibaca. Dengan pendekatan ini kedua nilai tidak mungkin saling berbeda, karena Fahrenheit tidak pernah disimpan sebagai data terpisah. Pemanggilan suhu.fahrenheit tanpa tanda kurung memang sengaja dipilih oleh Dart agar perubahan dari bidang biasa menjadi properti terhitung tidak mengubah tampilan kode di luar kelas. Getter juga boleh ditulis dengan badan kurawal biasa apabila perhitungannya memerlukan lebih dari satu langkah, misalnya variabel sementara sebelum nilai dikembalikan. Tipe kembaliannya dinyatakan secara eksplisit pada contoh di atas, dan tipe tersebut menentukan tipe nilai yang diterima pemanggil.

Setter dan penulisan dua arah#

Setter (setter) dideklarasikan dengan kata kunci set dan membawa satu parameter yang berisi nilai hasil penugasan. Badan setter dijalankan setiap kali properti tersebut menerima penugasan, sehingga setter menjadi tempat konversi nilai maupun pemeriksaan sebelum data disimpan. Properti yang memiliki getter dan setter sekaligus dapat dibaca dan ditulis dua arah.

dart suhu_dua_arah.dart
class Suhu {
  double celsius;

  Suhu(this.celsius);

  double get fahrenheit => celsius * 9 / 5 + 32;

  set fahrenheit(double nilai) {
    celsius = (nilai - 32) * 5 / 9;
  }
}

void main() {
  final suhu = Suhu(30);
  suhu.fahrenheit = 212;
  print('${suhu.celsius} C = ${suhu.fahrenheit} F');
}
text Keluaran
100.0 C = 212.0 F

Penugasan suhu.fahrenheit = 212 menjalankan badan setter, sehingga bidang celsius ikut berubah menjadi 100.0. Pembacaan berikutnya melalui getter menghitung ulang dari bidang yang telah berubah, dan kedua arah selalu menghasilkan nilai yang konsisten. Properti dua arah seperti ini menjaga satu sumber kebenaran, yaitu bidang celsius, sedangkan Fahrenheit hanya turunannya.

Setter dapat ditulis tanpa getter pendamping, dan pemanggilannya tetap berupa penugasan biasa. Kombinasi tersebut lazim untuk properti yang hanya menerima nilai, misalnya perintah penyetelan tanpa pembacaan ulang. Apabila kedua anggota ditulis berdampingan, tipe parameter setter umumnya disamakan dengan tipe kembalian getter agar penulisan dan pembacaan berjalan mulus tanpa kejutan tipe.

Bidang privat dan pemeriksaan nilai#

Pemeriksaan nilai pada setter bekerja paling tuntas apabila bidangnya tidak dapat dijangkau langsung dari luar kelas. Nama yang diawali garis bawah, seperti _jumlah, menandai anggota sebagai privat (private) untuk pustakanya, sehingga kode di luar pustaka tersebut tidak dapat membaca atau menulisnya. Getter dan setter menjadi satu-satunya pintu masuk, dan di situlah aturan bisnis ditempatkan.

dart stok.dart
class Stok {
  int _jumlah = 0;

  int get jumlah => _jumlah;

  set jumlah(int nilai) {
    if (nilai < 0) {
      throw ArgumentError('jumlah stok tidak boleh negatif');
    }
    _jumlah = nilai;
  }
}

void main() {
  final stok = Stok();
  stok.jumlah = 24;
  print(stok.jumlah);
}
text Keluaran
24

Pemanggilan stok.jumlah = 24 berlangsung melalui setter, dan pada jalur tersebut nilai negatif ditolak dengan throw ArgumentError. Karena bidang _jumlah tidak dapat diakses dari luar pustaka, tidak ada jalur lain yang dapat mengabaikan pemeriksaan ini. Pola tersebut memindahkan validasi dari pemanggil ke dalam kelasnya, sehingga aturan yang sama berlaku untuk seluruh pengguna kelas. Pelanggaran pada jalur ini baru terungkap saat program berjalan dalam bentuk exception, karena pemeriksaan setter terjadi pada saat penugasan dilakukan dan bukan pada waktu analisis statis.

Bidang dan properti terlihat sama#

Setiap bidang sebenarnya sudah memiliki anggota akses secara implisit. Bidang yang dapat diubah otomatis memiliki getter dan setter, sedangkan bidang final hanya memiliki getter. Karena alasan itu, perubahan dari bidang publik menjadi properti terhitung tidak memaksa satu pun pemanggil menulis ulang kodenya, dan ekspresi seperti titik.x tetap ditulis sama apakah x berupa bidang, getter, atau pasangan getter dan setter.

Kesetaraan yang tampak ini menjadi dasar anjuran desain pada Effective Dart. Gunakan getter dan setter untuk anggota yang bersifat seperti atribut, sedangkan bentuk metode dengan awalan get... disimpan untuk operasi yang mahal atau yang mengubah keadaan objek secara terlihat. Dengan konvensi tersebut, pembaca kode tidak perlu mengetahui apakah sebuah properti dihitung setiap kali dibaca atau sekadar disimpan, karena kedua pilihan menghasilkan sintaks pemanggilan yang sama.

Konsekuensi praktisnya terletak pada evolusi kelas. Kelas dapat dimulai dari bidang sederhana, kemudian bidang tersebut diubah menjadi properti terhitung saat kebutuhan berubah, tanpa dampak pada kode pemanggil sama sekali. Fleksibilitas inilah yang membuat pemisahan antara penyimpanan data dan penyajian data menjadi keputusan internal kelas.

Kesalahan umum: penugasan pada properti pembaca saja#

Properti yang hanya memiliki getter tidak dapat ditugaskan nilai, dan kesalahan ini terungkap pada waktu analisis statis. Kasusnya sering muncul apabila kelas dirancang sebagai pembaca saja, tetapi kode pemanggil menyalin pola penugasan bidang biasa. Blok berikut menuliskan berkas bermasalah ke folder sementara, lalu menjalankan dart analyze atas berkas itu.

bash
rm -rf /tmp/uji/setter
mkdir -p /tmp/uji/setter
cat > /tmp/uji/setter/tanpa_setter.dart <<'EOF'
class Suhu {
  double celsius;

  Suhu(this.celsius);

  double get fahrenheit => celsius * 9 / 5 + 32;
}

void main() {
  final suhu = Suhu(30);
  suhu.fahrenheit = 212;
}
EOF
cd /tmp/uji/setter
export PATH=/home/kal/dart-sdk/bin:$PATH
dart analyze tanpa_setter.dart
text Keluaran
Analyzing tanpa_setter.dart...

  error - tanpa_setter.dart:11:8 - There isn't a setter named 'fahrenheit' in class 'Suhu'. Try correcting the name to reference an existing setter, or declare the setter. - assignment_to_final_no_setter

1 issue found.

Pesan error menyatakan bahwa kelas Suhu tidak memiliki setter bernama fahrenheit. Perbaikannya bergantung pada maksud pemanggil, yaitu menambahkan setter apabila konversi dua arah memang diperlukan, atau mengubah penugasan menjadi suhu.celsius = ... apabila yang dimaksud adalah menetapkan suhu langsung dalam Celsius. Properti pembaca saja tetap sah dipertahankan apabila perhitungannya memang satu arah dan tidak dimaksudkan untuk ditulis ulang.

Pola yang sama mudah muncul pada kelas pustaka bawaan maupun package pihak ketiga yang menyediakan getter tanpa setter. Menyalin pola penugasan dari kelas lain tanpa memeriksa anggotanya memicu error yang serupa, dan dart analyze sebelum program dijalankan menangkapnya dengan cepat. Setter yang ditambahkan kemudian sebaiknya konsisten dengan getternya, sehingga konversi dua arah tidak menimbulkan perbedaan nilai antara kedua arah.

Ringkasan#

  • Getter dideklarasikan dengan get, dihitung setiap kali dibaca, dan dipanggil tanpa tanda kurung seperti membaca bidang.
  • Setter dideklarasikan dengan set dan menjalankan badannya setiap kali properti ditugaskan nilai, sehingga menjadi tempat konversi maupun validasi.
  • Nama dengan awalan garis bawah menandai anggota privat untuk pustakanya, sehingga pemeriksaan pada setter tidak dapat dilewati dari luar pustaka.
  • Bidang secara implisit dilengkapi getter dan setter, sehingga bidang dan properti terhitung memiliki sintaks pemanggilan yang identik di luar kelas.

Referensi#