dartoopextensionMahir4 mnt baca

Extension Method

Menambah kemampuan ke class yang sudah ada tanpa mengubahnya.

Superpower tanpa warisan

Kamu ingin 'budi'.kapital() menghasilkan 'Budi', tapi String adalah class bawaan yang tidak bisa kamu ubah. Extension method memungkinkanmu "menempelkan" method baru ke class yang sudah ada, tanpa inheritance, tanpa wrapper.

Analogi: extension itu seperti stiker modifikasi di laptop. Laptopnya (class asli) tidak dibongkar, tapi dapat fungsi baru yang terlihat menyatu.

Kenapa extension ada?

Tiga alasan: mempercantik API (method terasa native), tanpa boilerplate wrapper (tidak perlu class pembungkus), dan terbatas lingkupnya (hanya aktif saat diimpor, tidak mencemari global).

Bentuk dasar

dart
// Extension pada String
extension KapitalString on String {
  String get kapital {
    if (isEmpty) return this;
    return this[0].toUpperCase() + substring(1);
  }

  String get kapitalTiapKata {
    return split(' ').map((k) => k.kapital).join(' ');
  }
}

void main() {
  print('budi'.kapital); // Budi
  print('budi santoso'.kapitalTiapKata); // Budi Santoso
  print('dart'.kapital); // Dart
}

Di dalam extension, this merujuk ke objek String yang ditempeli. Method terasa seperti bawaan String.

Contoh kedua, extension pada int dan List:

dart
extension WaktuInt on int {
  Duration get detik => Duration(seconds: this);
  Duration get menit => Duration(minutes: this);
  Duration get jam => Duration(hours: this);
}

extension JumlahList on List<int> {
  int get total => fold(0, (a, b) => a + b);
  double get rata => isEmpty ? 0 : total / length;
}

Future<void> main() async {
  await Future.delayed(2.detik);
  print('2 detik berlalu');

  var nilai = [80, 90, 70];
  print('Total: ${nilai.total}, Rata: ${nilai.rata}');
}

2.detik jauh lebih ekspresif daripada Duration(seconds: 2). Ini gaya yang dipakai package-package Dart populer.

Extension bisa dibatasi tipe generik

dart
// Hanya untuk List yang elemennya num
extension StatistikNum<T extends num> on List<T> {
  T get jumlah => fold(0 as T, (a, b) => (a + b) as T);
}

void main() {
  print([1, 2, 3].jumlah); // 3, OK karena int extends num
  // print(['a'].jumlah); // ERROR: String bukan num
}

Batasan generik membuat extension hanya muncul untuk tipe yang sesuai, mencegah penyalahgunaan.

Extension butuh import

Extension tidak global otomatis: ia aktif hanya di file yang mengimpornya. Ini disengaja agar tidak bentrok.

dart
// File: string_ext.dart
extension KapitalString on String {
  String get kapital => isEmpty ? this : this[0].toUpperCase() + substring(1);
}

// File: main.dart
import 'string_ext.dart';

void main() {
  print('budi'.kapital); // OK karena diimpor
}

Kesalahan umum

1. Konflik nama extension

Jika dua extension mendefinisikan method sama pada tipe sama, yang diimpor terakhir (atau dengan prefix) menang. Solusi: import dengan prefix atau sembunyikan salah satu (hide).

dart
// import 'a_ext.dart';
// import 'b_ext.dart' as b;
// b.NamaExtension(obj).metode(); // panggil eksplisit

2. Mengira extension bisa akses private

Extension TIDAK bisa mengakses anggota privat (_x) class yang ditempeli jika beda file. Ia hanya bisa memakai API publik. Butuh akses privat? Ubah class-nya langsung.

3. Extension untuk logika bisnis berat

Extension ideal untuk helper kecil (format, konversi). Logika bisnis kompleks lebih baik di class/method biasa agar mudah di-test dan ditemukan.

Kesimpulan

Extension method = tempel kemampuan baru ke tipe yang sudah ada, terasa native, aktif lewat import. Pakai untuk helper kecil yang membuat kode ekspresif (2.detik, 'budi'.kapital).

Catatan teknis: Extension diselesaikan secara statis (compile-time), bukan dinamis. Artinya tidak ada overhead runtime, tapi juga tidak bisa di-override secara polimorfik seperti method biasa.

Tantangan

Extension rupiah

Buat extension RupiahInt pada int dengan getter rupiah yang memformat 1500000 menjadi 'Rp1.500.000'. Petunjuk: ubah ke string, lalu sisipkan titik tiap 3 digit dari belakang.

text
extension RupiahInt on int {
  String get rupiah {
    // TODO
    return '';
  }
}

void main() {
  print(1500000.rupiah); // Rp1.500.000
  print(50000.rupiah); // Rp50.000
}