Mixin

Mixin sebagai perilaku bersama tanpa rantai pewarisan.

Daftar isi

Mixin menyediakan cara menggabungkan perilaku dari beberapa sumber tanpa membentuk rantai pewarisan. Topik ini membahas deklarasi mixin beserta penerapannya dengan with, urutan linearisasi apabila lebih dari satu mixin memuat anggota bernama sama, dan klausa on sebagai kendala penerapan, ditutup dengan error analyzer apabila mixin diterapkan pada kelas yang tidak memenuhi kendalanya. Setiap contoh berupa berkas utuh yang dijalankan dengan Dart SDK 3.13 dan disertai keluaran aslinya.

Deklarasi dan penerapan mixin#

Deklarasi mixin Nama { ... } mendefinisikan kumpulan anggota yang dapat dilekatkan ke banyak kelas sekaligus melalui kata kunci with. Kelas yang menerima mixin memperoleh seluruh bidang dan metodenya, dan pemanggilan anggota tersebut tidak berbeda dengan pemanggilan anggota kelas itu sendiri. Berbeda dengan kelas abstract, mixin tidak dapat dibuat menjadi objek dan tidak mendeklarasikan konstruktor, sehingga perannya murni menyumbangkan anggota.

dart pencatat.dart
mixin PencatatLog {
  final List<String> riwayat = [];

  void catat(String pesan) {
    riwayat.add(pesan);
    print('log: $pesan');
  }
}

class AntreanLoket with PencatatLog {
  void layani(String nama) {
    catat('melayani $nama');
  }
}

class SensorSuhu with PencatatLog {
  void baca(double nilai) {
    catat('suhu $nilai derajat');
  }
}

void main() {
  final loket = AntreanLoket();
  loket.layani('Budi');

  final sensor = SensorSuhu();
  sensor.baca(27.5);
}
text Keluaran
log: melayani Budi
log: suhu 27.5 derajat

Kelas AntreanLoket dan SensorSuhu tidak berada dalam satu hierarki pewarisan, tetapi keduanya memperoleh metode catat() beserta bidang riwayat dari mixin yang sama. Dibandingkan menyalin implementasi ke dua kelas atau menaikkan fungsionalitas ke kelas induk yang dipaksakan, mixin menjaga kode pencatatan tetap berada di satu tempat. Kelas penerima bebas menambahkan anggota miliknya sendiri selain anggota warisan mixin, dan bidang riwayat terlekat pada masing-masing objek sehingga setiap kelas menyimpan riwayatnya sendiri.

Isi mixin tidak berbeda jauh dari isi kelas biasa, karena mixin dapat memuat bidang, metode, getter, dan setter. Satu kelas juga dapat menyambung beberapa mixin sekaligus dengan menulis daftarnya setelah with, misalnya class Laporan with PelacakA, PelacakB. Kombinasi inilah yang menjadikan mixin pengganti praktis bagi pewarisan ganda yang memang tidak disediakan Dart.

Linearisasi: urutan with#

Apabila dua mixin memuat anggota dengan nama sama, Dart menentukan pemenangnya melalui linearisasi (linearization), yaitu pengurutan tetap atas seluruh anggota dari rantai kelas dan mixin. Pencarian anggota berjalan dari badan kelas, kemudian mixin dari yang paling kanan ke paling kiri, dan berakhir pada kelas induk. Akibatnya, mixin yang ditulis paling akhir pada daftar with mengalahkan mixin yang berada sebelumnya.

dart linearisasi.dart
mixin PelacakA {
  String pelacak() => 'pelacak A';
}

mixin PelacakB {
  String pelacak() => 'pelacak B';
}

class Laporan with PelacakA, PelacakB {}

void main() {
  final laporan = Laporan();
  print(laporan.pelacak());
}
text Keluaran
pelacak B

Keluaran pelacak B menunjukkan bahwa PelacakB menang atas PelacakA karena posisinya lebih kanan pada daftar with. Apabila kelas Laporan menulis metode pelacak() miliknya sendiri, badan kelas tersebut yang menang atas keduanya karena pencarian dimulai dari kelas. Panggilan super di dalam mixin mengikuti urutan yang sama, sehingga panggilan diteruskan ke anggota berikutnya pada rantai, yaitu mixin yang berada di kirinya atau kelas induknya.

Urutan linearisasi dihitung sekali pada saat kelas dideklarasikan, sehingga hasilnya tetap sama untuk setiap objek yang dibuat. Perhitungannya menyusun kelas induk lebih dahulu, kemudian mixin dari kiri ke kanan, dan badan kelas sendiri berada di posisi terakhir. Pencarian anggota berjalan sebaliknya, yaitu dari badan kelas menuju kelas induk, dan itulah sebabnya anggota kelas mengalahkan anggota mixin.

Kendala dengan klausa on#

Klausa on membatasi kelas mana yang boleh menerapkan sebuah mixin. Deklarasi mixin Pemeliharaan on Hewan menyatakan bahwa mixin hanya boleh dilekatkan pada kelas yang merupakan subkelas dari Hewan. Dengan kendala itu, badan mixin boleh memanggil anggota Hewan melalui super, karena Dart sudah mengetahui bahwa rantai di atas mixin tersusun dari kelas tersebut.

dart pemeliharaan.dart
class Hewan {
  void bernapas() => print('Hewan bernapas');
}

mixin Pemeliharaan on Hewan {
  void periksa() {
    super.bernapas();
    print('Kesehatan diperiksa');
  }
}

class Sapi extends Hewan with Pemeliharaan {}

void main() {
  final sapi = Sapi();
  sapi.periksa();
}
text Keluaran
Hewan bernapas
Kesehatan diperiksa

Panggilan super.bernapas() di dalam periksa() menjalankan implementasi Hewan karena Sapi memperpanjang Hewan sebelum menyambung Pemeliharaan. Tanpa klausa on, mixin hanya boleh mengasumsikan anggota miliknya sendiri beserta anggota Object, sehingga panggilan semacam ini justru menimbulkan error pada waktu analisis. Kendala on menukar kebebasan penerapan dengan jaminan tipe yang boleh digunakan badan mixin.

Klausa on juga dapat menyebut lebih dari satu kelas, misalnya mixin Pemeliharaan on Hewan, Kendali, dan mixin tersebut hanya boleh dilekatkan pada kelas yang mengimplementasikan keduanya. Kendala ganda semacam ini lazim untuk utilitas yang membutuhkan kemampuan dari dua sumber sekaligus. Kata kunci with ditulis sebelum implements apabila kelas penerapnya juga mengimplementasikan antarmuka lain, karena urutan klausa tersebut tidak boleh dibalik.

Kesalahan umum: penerapan pada kelas yang tidak memenuhi kendala#

Mixin dengan klausa on yang dilekatkan pada kelas yang tidak memperpanjang kelas kendalanya menimbulkan dua error sekaligus pada waktu analisis. Keduanya berasal dari satu penyebab, yaitu kelas penerima tidak menyediakan antarmuka yang dijanjikan oleh mixin. Blok berikut menuliskan berkas bermasalah ke folder sementara, lalu menjalankan dart analyze atas berkas itu.

bash
rm -rf /tmp/uji/mixin-analisis
mkdir -p /tmp/uji/mixin-analisis
cat > /tmp/uji/mixin-analisis/pelanggaran_klausa_on.dart <<'EOF'
class Hewan {
  void bernapas() => print('Hewan bernapas');
}

mixin Pemeliharaan on Hewan {
  void periksa() => print('Kesehatan diperiksa');
}

class Alat with Pemeliharaan {}
EOF
cd /tmp/uji/mixin-analisis
export PATH=/home/kal/dart-sdk/bin:$PATH
dart analyze pelanggaran_klausa_on.dart
text Keluaran
Analyzing pelanggaran_klausa_on.dart...

  error - pelanggaran_klausa_on.dart:9:7 - Missing concrete implementation of 'Hewan.bernapas'. Try implementing the missing method, or make the class abstract. - non_abstract_class_inherits_abstract_member
  error - pelanggaran_klausa_on.dart:9:17 - 'Pemeliharaan' can't be mixed onto 'Object' because 'Object' doesn't implement 'Hewan'. Try extending the class 'Pemeliharaan'. - mixin_application_not_implemented_interface

2 issues found.

Error pertama menyatakan bahwa Alat tidak menyediakan implementasi konkret Hewan.bernapas, sedangkan error kedua menyatakan bahwa Pemeliharaan tidak dapat dilekatkan pada Object karena Object tidak mengimplementasikan Hewan. Perbaikannya menyesuaikan deklarasi kelas menjadi class Alat extends Hewan with Pemeliharaan, atau melepas klausa on apabila mixin memang tidak membutuhkan anggota Hewan. Analyzer menolak kode ini sebelum program sempat dijalankan, sehingga pelanggaran kendala tidak pernah lolos ke tahap runtime.

Kedua error pada keluaran saling berkaitan karena berakar pada penyebab yang sama, yaitu kelas penerima yang tidak memperpanjang Hewan. Menambahkan extends Hewan pada Alat menyelesaikan keduanya sekaligus, sedangkan melepas klausa on menyelesaikan keduanya dengan mengorbankan akses super di dalam badan mixin. Pemilihan perbaikannya mengikuti kebutuhan mixin, yaitu kendala dipertahankan apabila anggota kelas kendala benar-benar dipanggil dan dilepas apabila tidak.

Ringkasan#

  • Mixin dideklarasikan dengan mixin dan dilekatkan dengan with, membawa bidang dan metode ke setiap kelas penerapnya tanpa rantai pewarisan.
  • Linearisasi mengurutkan pencarian anggota dari badan kelas, kemudian mixin dari kanan ke kiri, sehingga mixin paling kanan menang saat terjadi benturan nama.
  • Klausa on membatasi penerapan mixin pada subkelas tertentu dan membuka akses super ke anggota kelas tersebut.
  • Penerapan mixin yang melanggar klausa on ditolak analyzer dengan dua error yang berasal dari satu penyebab yang sama.

Referensi#