Pemrograman Dart
Constructor di Dart: Generative, Named, Constant, dan Factory
Pelajari berbagai jenis constructor di Dart 3 mulai dari initializing formal (this.), named constructor, initializer list, const constructor, hingga factory constructor.

Pada materi sebelumnya, kita terpaksa memberi nilai awal kosong seperti String nama = ''; pada setiap properti class, lalu mengisinya satu per satu setelah objek dibuat. Cara tersebut memiliki celah berbahaya: bagaimana jika pemrogram lupa mengisi nomorRekening sebelum memanggil setorTunai()? Objek akan berada dalam keadaan tidak valid (inconsistent state). Di bawah aturan Sound Null Safety Dart 3, cara yang benar untuk menjamin setiap objek langsung memiliki data yang lengkap dan valid sejak detik pertama diciptakan adalah menggunakan Constructor.
Yang akan dipelajari
- Membuat generative constructor menggunakan sintaks ringkas initializing formal (
this.namaField) dan named parameters. - Menggunakan initializer list (
:) dan redirecting constructor (this(...)) untuk menyiapkan nilai awal atau memvalidasi parameter denganassert. - Memahami manfaat
constconstructor dan propertifinaluntuk membuat objek immutable yang hemat memori. - Menggunakan named constructor dan factory constructor untuk skenario pembuatan objek tingkat lanjut (seperti konversi dari
Map/JSON).
Generative constructor dan initializing formal (this.)
Constructor adalah method khusus yang namanya persis sama dengan nama class-nya dan otomatis dijalankan saat objek dibuat. Di banyak bahasa pemrograman lain, kita harus menulis pengisian properti secara berulang di dalam badan constructor (this.nama = nama;). Dart menyederhanakan pola ini dengan fitur initializing formal: cukup tulis this.namaField langsung di dalam daftar parameter constructor.
Kita juga sangat dianjurkan menggabungkan this. dengan named parameter {required this.nama, ...} agar pembuatan objek menjadi jelas dan tahan terhadap kesalahan urutan argumen:
// File: lib/mahasiswa.dart
class Mahasiswa {
final String nim;
String nama;
int semester;
double ipk;
// Generative constructor dengan named parameter dan initializing formal (this.)
Mahasiswa({
required this.nim,
required this.nama,
this.semester = 1,
this.ipk = 0.0,
});
void cetakProfil() {
print('[$nim] $nama | Semester $semester | IPK: ${ipk.toStringAsFixed(2)}');
}
}
Sekarang perhatikan betapa rapi dan amannya pembuatan objek di bin/belajar_dart_oop.dart:
// File: bin/belajar_dart_oop.dart
import 'package:belajar_dart_oop/mahasiswa.dart';
void main() {
// Tidak mungkin lupa mengisi nim atau nama karena dijaga oleh 'required'
var mhs1 = Mahasiswa(
nim: '2401001',
nama: 'Aulia Rahman',
semester: 3,
ipk: 3.85,
);
// semester dan ipk otomatis memakai nilai default (1 dan 0.0)
var mhs2 = Mahasiswa(
nim: '2601045',
nama: 'Bagas Saputra',
);
mhs1.cetakProfil();
mhs2.cetakProfil();
}
Keluaran program:
[2401001] Aulia Rahman | Semester 3 | IPK: 3.85
[2601045] Bagas Saputra | Semester 1 | IPK: 0.00
Karena nim ditandai sebagai final, nilainya wajib diisi satu kali saat constructor dipanggil dan tidak akan pernah bisa diubah lagi sepanjang umur objek tersebut.
Named constructor, initializer list (:), dan redirecting
Di sejumlah bahasa OOP lain, satu class boleh memiliki beberapa constructor dengan nama yang sama asalkan daftar parameternya berbeda (constructor overloading). Dart tidak menggunakan overloading; sebagai gantinya, Dart menyediakan Named Constructor dengan format NamaClass.namaKhusus(...). Cara ini jauh lebih eksplisit karena nama constructor langsung menjelaskan tujuan pembuatan objeknya.
Selain itu, kita dapat menambahkan initializer list (:) sebelum badan constructor untuk menghitung nilai properti final atau memeriksa validitas argumen dengan assert(...), serta meneruskan pembuatan objek ke constructor utama menggunakan redirecting constructor (: this(...)):
| Jenis Constructor | Sintaks | Kegunaan Utama |
|---|---|---|
| Generative utama | Produk(this.nama, this.harga); |
Cara standar membuat objek baru dengan nilai properti lengkap. |
| Named constructor | Produk.gratis(this.nama) : harga = 0; |
Menyediakan cara alternatif pembuatan objek dengan nama yang deskriptif. |
| Redirecting constructor | Produk.tanpaDiskon(String n, int h) : this(n, h, 0); |
Meneruskan (delegate) pembuatan objek ke constructor lain di class yang sama. |
| Constant constructor | const Titik(this.x, this.y); |
Membuat objek compile-time constant (semua field wajib final). |
| Factory constructor | factory Produk.dariJson(Map data) { ... } |
Mengembalikan objek yang diproses khusus, objek dari cache, atau subclass. |
Mari kita lihat penggunaan named constructor, initializer list, dan redirecting constructor dalam satu contoh utuh:
class Tagihan {
final String nomorInvoice;
final double nominalPokok;
final double pajakRupiah;
final double totalBayar;
// 1. Constructor utama dengan Initializer List (:)
// Menghitung pajakRupiah dan totalBayar sebelum badan constructor berjalan
Tagihan({
required this.nomorInvoice,
required this.nominalPokok,
double persenPajak = 11.0,
}) : assert(nominalPokok >= 0, 'Nominal pokok tidak boleh negatif'),
pajakRupiah = nominalPokok * (persenPajak / 100),
totalBayar = nominalPokok + (nominalPokok * (persenPajak / 100));
// 2. Redirecting Constructor: meneruskan ke constructor utama dengan persenPajak = 0
Tagihan.bebasPajak({
required String nomorInvoice,
required double nominalPokok,
}) : this(
nomorInvoice: nomorInvoice,
nominalPokok: nominalPokok,
persenPajak: 0.0,
);
void cetak() {
print('$nomorInvoice | Pokok: Rp${nominalPokok.toStringAsFixed(0)} '
'| Pajak: Rp${pajakRupiah.toStringAsFixed(0)} '
'| Total: Rp${totalBayar.toStringAsFixed(0)}');
}
}
void main() {
var invReguler = Tagihan(nomorInvoice: 'INV-001', nominalPokok: 100000);
var invPendidikan = Tagihan.bebasPajak(nomorInvoice: 'INV-002', nominalPokok: 100000);
invReguler.cetak();
invPendidikan.cetak();
}
Keluaran program:
INV-001 | Pokok: Rp100000 | Pajak: Rp11000 | Total: Rp111000
INV-002 | Pokok: Rp100000 | Pajak: Rp0 | Total: Rp100000
Constant (const) constructor dan factory constructor
Dua jenis constructor terakhir ini memiliki peran yang sangat penting dalam pengembangan aplikasi Dart dan Flutter modern:
1. Constant (const) constructor
Jika sebuah class dirancang agar datanya tidak pernah berubah setelah dibuat (immutable), jadikan seluruh field-nya final dan tambahkan kata kunci const di depan constructor-nya. Ketika dipanggil dengan const dan argumen yang bernilai sama, Dart akan menggunakan ulang satu objek yang sama di memori (canonicalization).
2. factory constructor
Berbeda dengan constructor biasa yang selalu otomatis membuat instance baru, constructor yang diawali kata kunci factory wajib mengembalikan objek menggunakan pernyataan return. Dengan factory, kita bebas melakukan validasi kompleks, mengurai (parsing) data dari Map<String, dynamic>, atau mengembalikan objek yang sudah ada di cache:
// Class dengan const constructor (seluruh field wajib final)
class WarnaRgb {
final int merah;
final int hijau;
final int biru;
const WarnaRgb(this.merah, this.hijau, this.biru);
// Factory constructor untuk mengonversi kode hex atau nama warna dari Map
factory WarnaRgb.dariMap(Map<String, dynamic> data) {
int r = (data['r'] as int?) ?? 0;
int g = (data['g'] as int?) ?? 0;
int b = (data['b'] as int?) ?? 0;
// Memastikan nilai berada di rentang 0..255 sebelum membuat objek
return WarnaRgb(
r.clamp(0, 255),
g.clamp(0, 255),
b.clamp(0, 255),
);
}
}
void main() {
// Menguji efisiensi memori const constructor
const warna1 = WarnaRgb(255, 0, 0);
const warna2 = WarnaRgb(255, 0, 0);
var warnaNonConst = WarnaRgb(255, 0, 0);
print('warna1 dan warna2 identik di memori? ${identical(warna1, warna2)}');
print('warna1 dan warnaNonConst identik? ${identical(warna1, warnaNonConst)}');
// Menguji factory constructor dari data Map
var warnaDariServer = WarnaRgb.dariMap({'r': 300, 'g': 128, 'b': -10});
print('Hasil factory dariMap: R=${warnaDariServer.merah}, '
'G=${warnaDariServer.hijau}, B=${warnaDariServer.biru}');
}
Keluaran program:
warna1 dan warna2 identik di memori? true
warna1 dan warnaNonConst identik? false
Hasil factory dariMap: R=255, G=128, B=0
Kaitannya dengan Flutter / Dunia Nyata: Di Flutter, Anda akan melihat peringatan linter "Prefer const with constant constructors" pada komponen seperti
const Text('Halo')atauconst SizedBox(height: 16). Karena widget tersebut memilikiconstconstructor, Flutter tidak perlu membangun ulang (rebuild) widget tersebut setiap kali layar diperbarui! Sementara itu, polafactory User.fromJson(Map<String, dynamic> json)adalah standar industri di Dart untuk mengubah respons API JSON menjadi objek model yang type-safe.
Coba sendiri
Buka proyek VS Code Anda atau DartPad, salin kode WarnaRgb di atas, lalu lakukan eksperimen berikut:
- Tambahkan beberapa named constant siap pakai di dalam
class WarnaRgbatau buat named constructorconst WarnaRgb.putih() : merah = 255, hijau = 255, biru = 255;danconst WarnaRgb.hitam() : this(0, 0, 0);. - Di dalam
main(), buat dua variabelconst p1 = WarnaRgb.putih();danconst p2 = WarnaRgb.putih();, lalu periksa denganidentical(p1, p2). - Coba hapus kata kunci
finalpadafinal int merah;di dalamWarnaRgb. Amati pesan error kompilasi yang menjelaskan bahwaconstconstructor tidak diizinkan jika class memiliki field yang bisa berubah (non-final).
Kesalahan yang sering terjadi
| Kesalahan Umum | Penyebab | Solusi yang Benar |
|---|---|---|
| Error: Can't define a const constructor for a class with non-final fields | Menambahkan kata kunci const pada constructor padahal masih ada field di dalam class yang belum ditandai final. |
Tambahkan kata kunci final pada seluruh field instance di dalam class tersebut. |
Mengakses this. di sebelah kanan initializer list (: total = this.harga * 2) |
Saat initializer list dieksekusi, objek belum selesai dibentuk sehingga referensi this belum boleh dibaca. |
Gunakan nama parameter constructor secara langsung (tanpa this.) di sebelah kanan tanda =. |
Menggunakan this.namaField pada parameter factory constructor (factory A.buat(this.x)) |
factory constructor tidak otomatis menginisialisasi field baru, melainkan harus mengembalikan objek lewat return. |
Gunakan parameter biasa (int x) lalu kembalikan objek dengan return A(x);. |
Menambahkan badan { ... } pada redirecting constructor (A.khusus() : this(0) { ... }) |
Di Dart, redirecting constructor yang memanggil : this(...) tidak boleh memiliki badan fungsi { ... }. |
Akhiri redirecting constructor langsung dengan titik koma ; setelah : this(...). |
Latihan
Kerjakan tiga soal latihan berikut untuk memantapkan penguasaan constructor:
- Class Koordinat Geografis (
const& Named Constructor): Buatclass TitikKoordinatdengan fieldfinal double lintangdanfinal double bujur. Buatconstconstructor utama dengan parameter posisi, serta redirecting const constructorconst TitikKoordinat.monas()yang mengarah ke titik(-6.1754, 106.8272). - Konversi Suhu dengan Initializer List: Buat
class Suhuyang menyimpan dua fieldfinal double celciusdanfinal double fahrenheit. Buat constructor utamaSuhu.dariCelcius(this.celcius)yang menghitungfahrenheitmenggunakan initializer list, serta named constructorSuhu.dariFahrenheit(this.fahrenheit)yang menghitungcelciusmelalui initializer list. - Model Pengguna dari JSON (
factoryConstructor): Buatclass Penggunadengan fieldfinal int id,final String nama, danfinal String peran. Buat generative constructor utama denganrequirednamed parameter. Lalu buatfactory Pengguna.dariJson(Map<String, dynamic> json)yang memvalidasi data: jikajson['peran']bernilainullatau kosong, isiperandengan'member'; jikajson['id']bukanintpositif, lempar atau ganti menjadi0.
Kunci jawaban dan pembahasan
Latihan 1
class TitikKoordinat {
final double lintang;
final double bujur;
const TitikKoordinat(this.lintang, this.bujur);
const TitikKoordinat.monas() : this(-6.1754, 106.8272);
void tampilkan() {
print('Koordinat: ($lintang, $bujur)');
}
}
void main() {
const lokasi1 = TitikKoordinat.monas();
const lokasi2 = TitikKoordinat(-6.1754, 106.8272);
lokasi1.tampilkan();
print('Apakah lokasi1 dan lokasi2 objek yang sama di memori? '
'${identical(lokasi1, lokasi2)}');
}
Pembahasan: Karena TitikKoordinat.monas() meneruskan nilai (-6.1754, 106.8272) ke const constructor utama, Dart mengenali bahwa lokasi1 dan lokasi2 memiliki nilai konstanta yang identik dan menunjuk ke alamat memori yang sama (identical menghasilkan true).
Latihan 2
class Suhu {
final double celcius;
final double fahrenheit;
Suhu.dariCelcius(double c)
: celcius = c,
fahrenheit = (c * 9 / 5) + 32;
Suhu.dariFahrenheit(double f)
: fahrenheit = f,
celcius = (f - 32) * 5 / 9;
void cetak() {
print('${celcius.toStringAsFixed(1)} °C = ${fahrenheit.toStringAsFixed(1)} °F');
}
}
void main() {
var suhuRuang = Suhu.dariCelcius(25.0);
var suhuTubuh = Suhu.dariFahrenheit(98.6);
suhuRuang.cetak();
suhuTubuh.cetak();
}
Pembahasan: Kedua field celcius dan fahrenheit bertipe final dan non-nullable, sehingga keduanya wajib diinisialisasi sebelum badan constructor berjalan. Initializer list (:) memungkinkan kita menghitung field kedua berdasarkan parameter yang masuk.
Latihan 3
class Pengguna {
final int id;
final String nama;
final String peran;
const Pengguna({
required this.id,
required this.nama,
required this.peran,
});
factory Pengguna.dariJson(Map<String, dynamic> json) {
int idMentah = (json['id'] is int) ? json['id'] as int : 0;
int idValid = idMentah > 0 ? idMentah : 0;
String namaValid = (json['nama'] as String?)?.trim() ?? 'Tanpa Nama';
String? peranMentah = (json['peran'] as String?)?.trim();
String peranValid =
(peranMentah != null && peranMentah.isNotEmpty) ? peranMentah : 'member';
return Pengguna(
id: idValid,
nama: namaValid,
peran: peranValid,
);
}
void info() => print('ID: $id | Nama: $nama | Peran: $peran');
}
void main() {
var u1 = Pengguna.dariJson({'id': 101, 'nama': 'Citra', 'peran': 'admin'});
var u2 = Pengguna.dariJson({'id': -5, 'nama': 'Dodi', 'peran': ''});
u1.info();
u2.info();
}
Pembahasan: Dengan memisahkan logika pembersihan data ke dalam factory Pengguna.dariJson(...), constructor utama Pengguna tetap bersih dan murni menerima data yang sudah pasti valid.
Discussion (0)
We store your optional name, your comment, and technical hashes (not your raw IP address) to prevent spam. A functional cookie lets you edit or delete your own comments from this browser. To request deletion, use the contact page.
No comments yet. Start the discussion.