dartoopinterfaceMenengah4 mnt baca

Interface & Implements

Kontrak murni dengan implements: banyak interface, tanpa warisan kode.

Kontrak tanpa warisan

extends mewarisi kode sekaligus kontrak. Tapi kadang kamu hanya butuh kontraknya: "class ini BISA diserialisasi ke JSON" tanpa mewarisi implementasi apa pun. implements menjadikan class sebagai interface murni.

Analogi: sertifikasi profesi. Sertifikat "bisa mengemudi" tidak memberimu mobil (implementasi), ia hanya menjamin kamu memenuhi standar. Banyak orang bisa punya sertifikat yang sama tanpa berbagi mobil.

Kenapa implements ada?

Dua alasan: multiple interface (satu class bisa memenuhi banyak kontrak, padahal extends hanya satu induk) dan decoupling (kode bergantung pada kontrak, bukan implementasi konkret, sehingga mudah diganti/mock saat testing).

Bentuk dasar

dart
// Interface: class biasa yang dipakai sebagai kontrak
abstract class BisaDisimpan {
  Map<String, dynamic> keJson();
  void dariJson(Map<String, dynamic> json);
}

abstract class BisaDicetak {
  String cetakRapi();
}

class Pengguna implements BisaDisimpan, BisaDicetak {
  String nama;
  int umur;

  Pengguna(this.nama, this.umur);

  @override
  Map<String, dynamic> keJson() => {'nama': nama, 'umur': umur};

  @override
  void dariJson(Map<String, dynamic> json) {
    nama = json['nama'];
    umur = json['umur'];
  }

  @override
  String cetakRapi() => '$nama ($umur thn)';
}

void main() {
  var p = Pengguna('Budi', 17);
  print(p.keJson());
  print(p.cetakRapi());

  // Bisa diperlakukan sebagai interface-nya
  BisaDisimpan s = p;
  print(s.keJson());
}

Pengguna memenuhi DUA kontrak sekaligus. Tidak ada kode yang diwarisi; semua ditulis ulang. Sebagai gantinya, kamu bebas: Pengguna bisa tetap extends Manusia sambil implements banyak interface.

Contoh kedua, decoupling untuk testing:

dart
abstract class Penyimpanan {
  void simpan(String kunci, String nilai);
  String? baca(String kunci);
}

// Implementasi asli: ke file
class PenyimpananFile implements Penyimpanan {
  final _data = <String, String>{};
  @override
  void simpan(String kunci, String nilai) => _data[kunci] = nilai;
  @override
  String? baca(String kunci) => _data[kunci];
}

// Implementasi palsu: untuk testing
class PenyimpananPalsu implements Penyimpanan {
  @override
  void simpan(String kunci, String nilai) {} // tidak ngapa-ngapain
  @override
  String? baca(String kunci) => 'palsu';
}

void uji(Penyimpanan s) {
  s.simpan('nama', 'Budi');
  print(s.baca('nama'));
}

void main() {
  uji(PenyimpananFile());  // Budi
  uji(PenyimpananPalsu()); // palsu
}

Fungsi uji hanya tahu kontrak Penyimpanan, tidak peduli implementasinya. Inilah fondasi dependency injection dan mock testing.

extends vs implements vs with

dart
abstract class Hewan {
  void makan() => print('nyam');
  void bersuara();
}

class Kucing extends Hewan {
  @override
  void bersuara() => print('Meong');
  // makan() diwarisi, tidak perlu tulis ulang
}

class Robot implements Hewan {
  @override
  void makan() => print('Robot isi baterai'); // wajib tulis SEMUA
  @override
  void bersuara() => print('Bip');
}
extendsimplementswith
Warisi kodeYaTidakYa
Jumlah1 indukBanyakBanyak
Wajib override abstrakYaSemua methodTidak

Kesalahan umum

1. implements lalu kaget harus tulis semua

dart
abstract class A {
  void satu() => print('satu');
  void dua();
}

// SALAH: implements wajib implementasikan 'satu' juga!
// class B implements A {
//   @override
//   void dua() => print('dua');
// }
dart
// BENAR
class B implements A {
  @override
  void satu() => print('satu (versi B)');
  @override
  void dua() => print('dua');
}

2. Memakai extends untuk "bisa" padahal bukan "adalah"

class Burung extends BisaTerbang salah secara konsep jika BisaTerbang bukan entitas. Hubungan kemampuan → implements atau with (mixin).

3. Interface berubah merusak semua implementasi

Menambah method ke interface memaksa SEMUA class implements mengimplementasikannya (compile error). Ini fitur (konsistensi), tapi desain interface harus stabil sejak awal.

Kesimpulan

implements = kontrak murni tanpa warisan kode, bisa banyak sekaligus. Pakai untuk kemampuan lintas-hierarki dan decoupling (testing/mock). Ingat tabel: extends untuk "adalah", implements untuk "bisa (kontrak)", with untuk "bisa (pakai kode)".

Catatan teknis: Di Dart 3, interface class memaksa class hanya dipakai sebagai interface (tidak bisa di-extends). Ini berguna untuk API publik yang ingin kamu kunci dari pewarisan.

Tantangan

Kontrak pembayaran

Buat abstract BisaDibayar dengan method bayar(int jumlah) dan String struk(). Buat 2 class yang implements: Tunai dan QRIS dengan struk berbeda. Uji keduanya lewat variabel bertipe BisaDibayar.

text
abstract class BisaDibayar {
  void bayar(int jumlah);
  String struk();
}

// TODO: class Tunai implements BisaDibayar
// TODO: class QRIS implements BisaDibayar

void main() {
  // TODO: uji lewat List<BisaDibayar>
}