Builder adalah pola desain yang memudahkan kami dan WiseSob merakit objek kompleks tanpa pusing konstruktor panjang. Di sini kami bahas konsepnya, contoh lintas bahasa, kapan dipakai, perbandingan, serta best practice agar implementasi terasa natural.
Builder adalah pola untuk merakit objek kompleks secara bertahap
Intinya sederhana: kita memisahkan cara merakit dari bentuk akhirnya. Dengan begitu, proses pembuatan objek bisa diatur langkah demi langkah, urutannya jelas, dan variasi konfigurasinya tetap rapi. Pendekatan ini berasal dari pola “Builder” yang diperkenalkan dalam buku klasik Gang of Four dan masih relevan sampai sekarang. Ringkasnya, Builder adalah solusi ketika konstruktor penuh parameter mulai bikin sakit kepala.
Builder adalah jawaban untuk telescoping constructor
Telescoping constructor itu kondisi saat class punya banyak sekali konstruktor dengan kombinasi parameter berbeda—semuanya valid, tapi bikin kode susah dibaca dan rentan salah urut. Builder adalah jalan keluar: kita kumpulkan opsi ke dalam builder, lalu panggil build() saat semuanya siap. Alhasil, kode jadi ekspresif:
// Java - gaya fluent dengan Builder manual
public class User {
private final String name; // wajib
private final String email; // wajib
private final String phone; // opsional
private final boolean newsletter; // opsional
private User(Builder b) {
this.name = b.name;
this.email = b.email;
this.phone = b.phone;
this.newsletter = b.newsletter;
}
public static class Builder {
private final String name;
private final String email;
private String phone;
private boolean newsletter;
public Builder(String name, String email) {
this.name = name;
this.email = email;
}
public Builder phone(String phone) { this.phone = phone; return this; }
public Builder newsletter(boolean n) { this.newsletter = n; return this; }
public User build() { return new User(this); }
}
}
// Pemakaian
User u = new User.Builder("Rani", "rani@contoh.id")
.phone("0812xxxxxxx")
.newsletter(true)
.build();
Builder adalah pemisahan peran yang rapi antara Director, Builder, dan Product
| Peran | Tugas | Catatan |
|---|---|---|
| Product | Objek akhir yang ingin dibuat | Contoh: User, Invoice, Report |
| Builder | Langkah-langkah perakitan | Metode fluent seperti phone(...), newsletter(...) |
| Concrete Builder | Implementasi spesifik | Misal builder PDF vs builder HTML |
| Director | Urutan merakit | Opsional; berguna saat step harus baku |
Builder adalah beda tujuan dibanding Factory dan Abstract Factory
- Factory memilih tipe objek mana yang dibuat berdasarkan kondisi.
- Abstract Factory membuat keluarga objek terkait.
- Builder fokus ke proses perakitan bertahap hingga jadi satu objek kompleks.
Seringnya builder dipakai bareng factory. Factory menentukan jenis produk, lalu builder merakit detailnya secara fleksibel.
Builder adalah pilihan tepat saat kondisi berikut muncul
- Objek punya banyak opsi opsional dan default yang bervariasi.
- Urutan konstruksi perlu dikontrol (ada step wajib vs opsional).
- Representasi akhir bisa berbeda-beda tapi prosesnya mirip (PDF, HTML, JSON).
- Kita ingin API yang mudah dibaca dan sulit disalahgunakan.
Builder adalah kurang pas bila kasusnya sederhana
- Objek hanya punya 1–2 parameter wajib dan jarang berubah.
- Tak ada kebutuhan variasi output atau urutan konstruksi.
- Tim butuh time-to-market cepat untuk fitur minim konfigurasi.
Contoh praktis TypeScript karena Builder adalah enak dibaca
type ShippingType = "REG" | "YES" | "SAME_DAY";
class Order {
constructor(
public readonly items: string[],
public readonly address: string,
public readonly shipping: ShippingType,
public readonly voucher?: string,
public readonly giftNote?: string
) {}
}
class OrderBuilder {
private items: string[] = [];
private address = "";
private shipping: ShippingType = "REG";
private voucher?: string;
private giftNote?: string;
addItem(name: string) { this.items.push(name); return this; }
setAddress(addr: string) { this.address = addr; return this; }
setShipping(s: ShippingType) { this.shipping = s; return this; }
setVoucher(v: string) { this.voucher = v; return this; }
setGiftNote(n: string) { this.giftNote = n; return this; }
build() {
if (!this.items.length) throw new Error("Item wajib ada");
if (!this.address) throw new Error("Alamat wajib ada");
return new Order(this.items, this.address, this.shipping, this.voucher, this.giftNote);
}
}
// Pemakaian
const order = new OrderBuilder()
.addItem("Kopi")
.addItem("Gelas")
.setAddress("Jl. Mawar No. 10, Bandung")
.setShipping("SAME_DAY")
.setGiftNote("Semangat kerja!")
.build();
Contoh di atas memperjelas niat. Tanpa melihat dokumentasi pun, kita bisa “menebak” alurnya. Itulah salah satu alasan Builder adalah API yang ramah pembaca.
Contoh Python yang singkat karena Builder adalah soal kejelasan
from dataclasses import dataclass
from typing import Optional, List
@dataclass(frozen=True)
class Report:
title: str
authors: List[str]
include_charts: bool
format: str # "pdf" | "html" | "md"
watermark: Optional[str] = None
class ReportBuilder:
def __init__(self, title: str):
self._title = title
self._authors = []
self._include_charts = False
self._format = "pdf"
self._watermark = None
def authors(self, names: List[str]):
self._authors = names; return self
def charts(self, flag: bool = True):
self._include_charts = flag; return self
def as_format(self, fmt: str):
self._format = fmt; return self
def watermark(self, text: str):
self._watermark = text; return self
def build(self) -> Report:
if not self._authors:
raise ValueError("Penulis minimal 1 orang")
return Report(
title=self._title,
authors=self._authors,
include_charts=self._include_charts,
format=self._format,
watermark=self._watermark
)
rep = (ReportBuilder("Laporan Q3")
.authors(["Mira", "Andi"])
.charts(True)
.as_format("html")
.watermark("Internal")
.build())
Builder adalah praktik yang mudah dipadukan dengan Lombok
Di Java, anotasi @Builder dari Lombok membuat boilerplate berkurang drastis. Kita cukup menandai class dan Lombok akan menghasilkan builder otomatis. Dokumentasi resminya bisa dilihat di projectlombok.org. Meski ringkas, tetap lakukan validasi di build() atau gunakan @Builder.Default untuk nilai default yang aman.
Builder adalah pola yang enak untuk menjaga invarian domain
Salah satu manfaat besar builder adalah kemampuan memastikan data “jadi” dalam kondisi valid. Kita tak membiarkan objek tercipta dengan kombinasi setengah matang. Validasi precondition di build() membuat bug lebih cepat ketahuan. Contoh lain, kita bisa mencegah kombinasi saling eksklusif (misalnya shipping SAME_DAY tidak boleh untuk alamat luar kota).
Checklist implementasi karena Builder adalah soal kebersihan API
- Nama method builder harus jelas maknanya, gunakan gaya fluent.
- Parameter wajib dipaksa terisi, entah via konstruktor builder atau validasi di
build(). - Set default yang aman agar pemakaian minimal tetap menghasilkan objek valid.
- Jangan buat builder untuk class sederhana; simpan untuk objek yang memang kompleks.
- Dokumentasikan contoh pemakaian singkat di README agar tim baru langsung paham.
Pengujian unit yang simpel karena Builder adalah mudah dites
- Uji jalur bahagia: semua parameter benar menghasilkan objek sesuai harapan.
- Uji precondition: ketidakhadiran parameter wajib melempar exception/menolak build.
- Uji nilai default dan kombinasi borderline (misalnya voucher dengan minimal belanja).
- Jika ada Director, uji urutan step benar-benar dieksekusi sesuai skenario.
Perbandingan singkat agar jelas kapan Builder adalah pilihan terbaik
| Kebutuhan | Builder | Factory | Telescoping Constructor |
|---|---|---|---|
| Objek dengan banyak opsi | Sangat cocok, fluent dan jelas | Boleh, tapi kurang ekspresif | Membingungkan, rawan salah urut |
| Variasi output dari proses mirip | Cocok, tinggal ganti Concrete Builder | Bisa, tapi pengaturan detail terbatas | Tidak cocok |
| Objek sederhana 1–2 parameter | Overkill | Cukup | Cukup |
Anti-pattern yang perlu dihindari meski Builder adalah fleksibel
- God Builder: builder menangani terlalu banyak tanggung jawab; pecah jadi beberapa builder kecil.
- Builder tanpa validasi: objek tetap bisa “jadi” dalam keadaan invalid—tujuan builder jadi hilang.
- Fluent antarmuka yang membingungkan: nama method kurang jelas, urutan membingungkan.
- Ketergantungan silang: builder mencampur logika domain kompleks; pisahkan ke service bila perlu.
Strategi migrasi karena Builder adalah cara merapikan kode lama
- Identifikasi class dengan konstruktor panjang atau banyak overload.
- Perkenalkan builder paralel tanpa memutus kompatibilitas lama.
- Tambahkan validasi dan default di builder, tulis tes untuk menutup bug lama.
- Depresiasi konstruktor lama bertahap; dokumentasikan pemakaian builder.
Referensi singkat karena Builder adalah pola yang sudah mapan
- Wikipedia Builder pattern untuk gambaran umum cepat.
- Refactoring.Guru Builder untuk penjelasan visual dan contoh.
- Lombok @Builder untuk Java supaya boilerplate berkurang.
Catatan ringan dari kami karena Builder adalah tool, bukan tujuan
Kami di WiseWebster sering melihat builder disalahgunakan untuk kasus yang sebenarnya bisa selesai dengan konstruktor biasa atau factory sederhana. Ingat, Builder adalah alat saat konfigurasi banyak dan urutan penting. Kalau kebutuhannya simpel, jangan memaksa.
Kesimpulan
Builder adalah pola desain yang membuat pembuatan objek kompleks terasa rapi, terbaca, dan aman dari kombinasi yang salah. Pakai saat ada banyak opsi dan urutan perakitan perlu dikontrol. Hindari berlebihan. Dengan praktik yang tepat, kode jadi lebih nyaman dirawat dan mudah dikerjakan tim.