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
// 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:
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
// 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.
// 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).
// import 'a_ext.dart';
// import 'b_ext.dart' as b;
// b.NamaExtension(obj).metode(); // panggil eksplisit2. 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.
extension RupiahInt on int {
String get rupiah {
// TODO
return '';
}
}
void main() {
print(1500000.rupiah); // Rp1.500.000
print(50000.rupiah); // Rp50.000
}