Modifier kelas

Modifier sealed, final, base, dan interface untuk mengendalikan pembuatan subkelas.

Daftar isi

Modifier kelas (class modifiers) adalah kata kunci yang ditulis sebelum deklarasi kelas untuk mengendalikan siapa yang boleh menurunkan atau mengimplementasikannya. Topik ini membahas modifier sealed, final, base, dan interface, cara masing-masing mengatur akses dari luar library, serta kombinasi dengan kata abstract. Setiap aturan disertai contoh yang dapat dijalankan, termasuk pesan error analyzer yang muncul apabila aturannya dilanggar.

Modifier sealed#

Modifier sealed menutup hierarki kelas pada satu library, sehingga subkelas hanya boleh dideklarasikan di berkas yang sama dengan kelas induknya. Kelas sealed bersifat abstract secara implisit, sehingga tidak dapat diinstansiasi langsung, tetapi subkelunasnya tetap kelas biasa yang dapat dibuat. Karena compiler mengenal seluruh subkelas yang mungkin ada, switch atas hierarki sealed diperiksa kelengkapannya, sebagaimana digunakan pada topik switch eksaustif. Program berikut menggunakan hierarki sealed untuk menyatakan status penilaian ujian.

dart Contoh: hierarki sealed untuk status ujian
sealed class StatusUjian {}

class BelumDinilai extends StatusUjian {}

class Dinilai extends StatusUjian {
  final int skor;
  Dinilai(this.skor);
}

String label(StatusUjian status) => switch (status) {
      BelumDinilai() => 'masih menunggu penilaian',
      Dinilai(:final skor) => 'sudah dinilai dengan skor $skor',
    };

void main() {
  print(label(BelumDinilai()));
  print(label(Dinilai(88)));
}
text Keluaran
masih menunggu penilaian
sudah dinilai dengan skor 88

Fungsi label mencocokkan kedua subkelas tanpa cabang cadangan, dan kecocokan itu dijamin oleh compiler karena StatusUjian tidak mungkin memiliki subkelas lain di luar berkasnya. Apabila subkelas ketiga ditambahkan di berkas yang sama, switch di atas langsung ditolak sampai kasusnya ditulis. Penulisan hierarki seperti ini menggantikan enum yang tidak dapat membawa data berbeda per anggota, karena setiap subkelas menyimpan bidangnya sendiri. Subkelas kelas sealed dapat diberi modifier lain sesuai kebutuhan, misalnya final untuk menutup cabangnya lebih jauh, karena larangan sealed hanya menyatakan lokasi deklarasi subkelas. Kelas sealed juga tidak perlu ditulis dengan kata abstract, karena sifat abstractnya sudah menyatu pada modifier.

Modifier final dan pewarisan yang sah#

Modifier final menutup seluruh subtyping di luar library, baik pewarisan melalui extends maupun implementasi melalui implements. Kelas final tetap dapat diturunkan di dalam library yang sama, tetapi setiap subkelasnya wajib diberi tanda base, final, atau sealed agar jaminan kelasnya tetap terjaga. Blok berikut membuat dua berkas yang menurunkan kelas final dari library lain, lalu menjalankan dart analyze.

bash
# buat kelas final dan kelas yang menurunkannya dari library lain, lalu dart analyze
rm -rf /tmp/uji/modifier-final && mkdir -p /tmp/uji/modifier-final
cat > /tmp/uji/modifier-final/kelas_final.dart <<'EOF'
final class SuhuCelsius {
  final double nilai;
  SuhuCelsius(this.nilai);
}
EOF
cat > /tmp/uji/modifier-final/turunan.dart <<'EOF'
import 'kelas_final.dart';

class SuhuKelvin extends SuhuCelsius {
  SuhuKelvin(double nilaiCelsius) : super(nilaiCelsius + 273.15);
}
EOF
cd /tmp/uji/modifier-final && dart analyze
text Keluaran
Analyzing modifier-final...

  error - turunan.dart:3:26 - The class 'SuhuCelsius' can't be extended outside of its library because it's a final class. - invalid_use_of_type_outside_library

1 issue found.

Pesan error menyatakan larangan tersebut dengan kode invalid_use_of_type_outside_library, dan perbaikannya bukan menghapus modifier, melainkan memindahkan pewarisan ke library yang sama. Alasan larangan ini adalah menjaga asumsi penulis kelas, misalnya invarian atau perilaku metode yang tidak boleh dilanggar oleh subkelas tak dikenal. Contoh berikut memperlihatkan pewarisan yang sah, yaitu kelas final beserta subkelunasnya dalam satu berkas, dengan subkelas diberi tanda final agar memenuhi syarat modifier induknya.

dart sensor.dart
// Program log sensor suhu dengan kelas final dan pewarisan yang sah.
// Pewarisan diizinkan karena berada di library yang sama.

final class Suhu {
  final double celsius;
  Suhu(this.celsius);

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

final class SuhuKalibrasi extends Suhu {
  final double offset;
  SuhuKalibrasi(super.celsius, this.offset);

  @override
  String toString() => '${(celsius + offset).toStringAsFixed(1)} °C (terkalibrasi)';
}

void main() {
  final mentah = Suhu(26.0);
  final terkalibrasi = SuhuKalibrasi(26.0, 0.4);
  print('Pembacaan mentah: ${mentah.celsius} °C = ${mentah.fahrenheit} °F');
  print('Pembacaan terkalibrasi: $terkalibrasi');
}
text Keluaran
Pembacaan mentah: 26.0 °C = 78.8 °F
Pembacaan terkalibrasi: 26.4 °C (terkalibrasi)

Modifier base dan interface#

Modifier base mengizinkan pewarisan dari luar library tetapi menolak implementasi, sedangkan modifier interface mengizinkan implementasi tetapi menolak pewarisan. Kelas base menjamin bahwa konstruktor induknya selalu dipanggil untuk setiap subkelas dan anggota baru tidak merusak subkelas yang sudah ada. Syarat tambahannya ada pada subkelas, karena kelas yang menurunkan kelas base wajib bertanda base, final, atau sealed, sebagaimana ditunjukkan blok berikut.

bash
# buat kelas base beserta subkelas tanpa tanda, lalu dart analyze
rm -rf /tmp/uji/modifier-base && mkdir -p /tmp/uji/modifier-base
cat > /tmp/uji/modifier-base/sensor_tanpa_tanda.dart <<'EOF'
base class Sensor {}

class SensorSuhu extends Sensor {}
EOF
cd /tmp/uji/modifier-base && dart analyze
text Keluaran
Analyzing modifier-base...

  error - sensor_tanpa_tanda.dart:3:7 - The type 'SensorSuhu' must be 'base', 'final' or 'sealed' because the supertype 'Sensor' is 'base'. - subtype_of_base_or_final_is_not_base_final_or_sealed

1 issue found.

Kode error subtype_of_base_or_final_is_not_base_final_or_sealed menyatakan syarat turunan kelas base secara eksplisit, dan perbaikannya adalah menambahkan salah satu tanda tersebut pada SensorSuhu. Modifier interface bekerja pada arah yang berlawanan, karena kelas interface boleh diimplementasikan dari library mana pun tetapi tidak boleh diturunkan. Kombinasi kedua modifier dengan abstract akan dibahas pada bagian berikut, dan tabel di bawah merangkum seluruh aturan lintas modifier.

Modifierextends di luar libraryimplements di luar libraryCatatan
tanpa modifierdiizinkandiizinkanperilaku bawaan Dart
sealedditolakditolaksubkelas hanya di library yang sama
finalditolakditolakmenutup seluruh subtyping di luar library
basediizinkanditolakturunannya wajib base, final, atau sealed
interfaceditolakdiizinkankontrak implementasi tanpa pewarisan

Kombinasi dengan abstract#

Kata abstract dapat digabungkan dengan modifier untuk membentuk kontrak yang ketat. Kombinasi abstract interface menghasilkan antarmuka murni, yaitu kelas yang hanya dapat diimplementasikan dan tidak dapat diturunkan dari luar library, sedangkan abstract base menghasilkan kelas yang dapat diturunkan tetapi tidak dapat diimplementasikan. Program berikut menggunakan kedua kombinasi sekaligus untuk membangun penyimpanan arsip kecil.

dart Contoh: kombinasi abstract interface dan abstract base
abstract interface class Penyimpanan {
  void simpan(String isi);
}

abstract base class SimpananDasar {
  String get nama;
}

final class ArsipTeks extends SimpananDasar implements Penyimpanan {
  @override
  String get nama => 'arsip teks';

  @override
  void simpan(String isi) => print('Menyimpan "$isi" ke $nama');
}

void main() {
  final arsip = ArsipTeks();
  arsip.simpan('catatan harian');
}
text Keluaran
Menyimpan "catatan harian" ke arsip teks

Kelas Penyimpanan bertindak sebagai kontrak yang dapat diterapkan dari mana pun, karena implementasi atas kelas abstract interface tetap diizinkan. Kelas SimpananDasar menjadi dasar pewarisan yang terkendali, dan satu-satunya jalannya adalah subkelas yang bertanda final seperti ArsipTeks. Pemilihan kombinasi mengikuti kebutuhan, yaitu kontrak yang luas menggunakan abstract interface, hierarki implementasi yang terjaga menggunakan abstract base, dan penutupan total menggunakan abstract final yang tidak dapat diwariskan dari luar library. Kombinasi abstract final tetap berguna untuk menyimpan anggota statis atau konstanta yang digunakan bersama tanpa membuka jalur subtyping apa pun. Pemetaan lengkapnya mengikuti tabel pada bagian sebelumnya, karena kata abstract hanya menambah larangan instansiasi di atas larangan modifier yang dipilih.

Ringkasan#

  • Modifier sealed menutup hierarki pada satu library dan membuat switch atas subkelasnya diperiksa kelengkapannya saat kompilasi.
  • Modifier final menolak pewarisan dan implementasi dari luar library, tetapi tetap mengizinkannya di dalam library dengan subkelas yang bertanda base, final, atau sealed.
  • Modifier base mengizinkan pewarisan dan menolak implementasi di luar library, sedangkan modifier interface mengizinkan implementasi dan menolak pewarisan.
  • Kata abstract digabungkan dengan modifier untuk membentuk kontrak yang tidak dapat diinstansiasi, misalnya abstract interface dan abstract base.

Referensi#