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.
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)));
}masih menunggu penilaian
sudah dinilai dengan skor 88Fungsi 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.
# 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 analyzeAnalyzing 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.
// 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');
}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.
# 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 analyzeAnalyzing 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.
| Modifier | extends di luar library | implements di luar library | Catatan |
|---|---|---|---|
| tanpa modifier | diizinkan | diizinkan | perilaku bawaan Dart |
| sealed | ditolak | ditolak | subkelas hanya di library yang sama |
| final | ditolak | ditolak | menutup seluruh subtyping di luar library |
| base | diizinkan | ditolak | turunannya wajib base, final, atau sealed |
| interface | ditolak | diizinkan | kontrak 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.
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');
}Menyimpan "catatan harian" ke arsip teksKelas 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#
- Class modifiers — dart.dev — Dokumentasi resmi modifier kelas Dart beserta aturan kombinasi antarmodifier.
- Classes — dart.dev — Dokumentasi kelas Dart sebagai dasar konsep pewarisan dan implementasi.
- Patterns — dart.dev — Dokumentasi pattern yang menutup bagian pencocokan pada hierarki sealed.