This episode dissects object-oriented programming in Dart: classes and constructors, inheritance, mixins, abstract classes, and enums, plus extension methods and operator overloading for immutable data modeling.

Dart is a fully object-oriented language: every value is an object, and every object is an instance of a class. Episode 5 brings you to the heart of Dart programming — designing clear, safe types through classes, inheritance, mixins, enums, and extensions.
You'll learn to build classes with modern constructors, organize shared behavior through abstract classes and mixins, enrich types with extension methods, and create immutable data models free of side effects. Mastering this part is what separates writing scripts from designing applications.
Define a class with a concise constructor and named parameters:
class Produk {
final String nama;
final double harga;
const Produk({required this.nama, required this.harga});
}
void main() {
const item = Produk(nama: 'Kopi', harga: 25000);
print('${item.nama} seharga ${item.harga}');
}Produk({required this.nama, required this.harga}) is a constructor that directly initializes the fields — the most common pattern in modern Dart codebases.
Dart lets you expose properties with getters and setters while hiding the internal representation:
class Suhu {
double _celsius;
Suhu(this._celsius);
double get fahrenheit => _celsius * 9 / 5 + 32;
set fahrenheit(double f) => _celsius = (f - 32) * 5 / 9;
}The getter fahrenheit computes the conversion without storing duplicate data, and the _celsius field with the underscore prefix is private per library.
Derive a class with extends and override methods using the @override annotation:
abstract class Binatang {
void bersuara();
}
class Kucing extends Binatang {
@override
void bersuara() => print('Meow');
}
void main() {
Binatang hewan = Kucing();
hewan.bersuara();
}abstract class Binatang defines a contract without implementation; the Kucing class implements the bersuara method. Polymorphism allows Kucing to be treated as Binatang.
Mixins share behavior across many classes without a vertical inheritance relationship. Use the mixin and with keywords:
mixin Logger {
void log(String pesan) => print('[LOG] $pesan');
}
class Aplikasi with Logger {}Aplikasi with Logger immediately gets the log method without inheriting from a specific class. This is the right tool for cross-cutting concerns like logging and auditing.
Enums represent a limited set of values. Dart 2.17 added enhanced enums that can carry fields and methods:
enum StatusPesanan {
menunggu('Menunggu pembayaran'),
diproses('Sedang diproses'),
selesai('Selesai');
final String label;
const StatusPesanan(this.label);
}StatusPesanan.diproses.label accesses the label field stored per enum value. Enhanced enums are very useful for domain statuses that need extra data.
extension Kebalikan on String {
String get dibalik => split('').reversed.join();
}'Dart'.dibalik works as if String had a dibalik getter, even though the String class is never modified. Extensions are highly effective for adding utilities to types from other packages.
Dart allows redefining operators for your own types:
class Vektor {
final int x;
final int y;
const Vektor(this.x, this.y);
Vektor operator +(Vektor lain) => Vektor(x + lain.x, y + lain.y);
@override
String toString() => '($x, $y)';
}
void main() {
var a = Vektor(1, 2) + Vektor(3, 4);
print(a);
}The overridden + operator adds two Vectors. Overloading makes domain types feel like primitives, as long as it's used with discipline.
For data that represents pure values, build immutable classes: all fields final, the constructor const, and no mutation:
class Alamat {
final String jalan;
final String kota;
const Alamat(this.jalan, this.kota);
Alamat copyWith({String? jalan, String? kota}) {
return Alamat(jalan ?? this.jalan, kota ?? this.kota);
}
}
void main() {
const a = Alamat('Jl. Melati', 'Bandung');
var b = a.copyWith(kota: 'Jakarta');
print('$a -> $b');
}The copyWith pattern produces a new instance with some fields changed, without modifying the original instance — the key to avoiding bugs caused by shared state in large applications.
Key takeaways:
const constructor with named parameters is the standard Dart class pattern.extends for inheritance; abstract class for contracts; mixin for horizontal reuse.copyWith prevent shared-state bugs.In the next episode 6, we'll cover null safety and the type system — nullable versus non-nullable, the ?. and ?? operators, late and type promotion, and sound null safety and migration tools. This is the mechanism that makes Dart feel safe from compile time onward.