Builder Pattern — Design Patterns

Builder memisahkan proses konstruksi object kompleks dari representasi akhirnya. Cocok saat object punya banyak parameter optional, atau proses pembuatannya…

Builder Pattern merakit object yang punya banyak bagian opsional selangkah demi selangkah, lewat method yang namanya menyebut bagian itu, lalu build() di akhir memeriksa rakitannya dan menyerahkan hasilnya. Bayangin formulir kirim paket di loket. Kamu cuma mencentang kotak yang perlu: asuransi, packing kayu, COD, catatan. Tiap kotak ada labelnya. Petugas memeriksa formulirnya sekali sebelum stiker dicetak. Bandingkan dengan formulir berisi delapan kotak kosong tanpa label, yang artinya cuma ketahuan dari urutannya.

🧪 Tebakan cepat: penulis kode ini ingin paketnya pakai packing kayu: new Shipment("KCL-0001", "kargo", 12, true, false, 150000, 0, ["Isi kaca"]). Paketnya dapat packing kayu atau gak? Tebak dulu, jawabannya ada di bagian "Masalahnya: delapan argumen berurutan".

Masalahnya: delapan argumen berurutan

Kiriman Kurir Kancil punya tiga bagian wajib (resi, layanan, berat) dan lima bagian opsional:

class Shipment {
  constructor(resi, service, kg, asuransi, packingKayu, cod, nilaiBarang, catatan) {
    Object.assign(this, { resi, service, kg, asuransi, packingKayu, cod, nilaiBarang, catatan })
  }
}

const s = new Shipment("KCL-0001", "kargo", 12, true, false, 150000, 0, ["Isi kaca"])
console.log(s.asuransi, s.packingKayu) // true false

Argumen keempat adalah asuransi, kelima packingKayu. Penulisnya menukar dua true/false, jadi paketnya dapat asuransi dan gak dapat packing kayu. Itu jawaban tebakannya. Gak ada error: dua boolean yang tertukar tetap boolean.

Langkah pertama di JavaScript: satu object berisi opsi

JavaScript punya obat bawaan: kirim satu object dan ambil isinya dengan destructuring, lengkap dengan nilai bawaan.

function createShipment({ resi, service = "reguler", kg = 1, asuransi = false, nilaiBarang = 0, packingKayu = false, cod = 0, catatan = [] }) {
  return { resi, service, kg, asuransi, nilaiBarang, packingKayu, cod, catatan }
}

const s = createShipment({ resi: "KCL-0001", service: "kargo", kg: 12, packingKayu: true, cod: 150000 })
console.log(s.asuransi, s.packingKayu) // false true

Builder: dirakit selangkah demi selangkah

Builder baru berguna saat object-nya diisi bertahap (halaman checkout mengisi layanan dan berat, langkah berikutnya menambah asuransi, langkah terakhir COD), ada bagian yang bisa ditambah berkali-kali (catatan), dan ada aturan gabungan yang dicek sekali di akhir.

class ShipmentBuilder {
  constructor(resi) {
    this.data = { resi, service: "reguler", kg: 1, asuransi: false, nilaiBarang: 0, packingKayu: false, cod: 0, catatan: [] }
  }

  service(name) { this.data.service = name; return this }
  kg(n) { this.data.kg = n; return this }
  asuransi(nilaiBarang) { this.data.asuransi = true; this.data.nilaiBarang = nilaiBarang; return this }
  packingKayu() { this.data.packingKayu = true; return this }
  cod(nominal) { this.data.cod = nominal; return this }
  catatan(text) { this.data.catatan.push(text); return this }

  build() {
    if (this.data.packingKayu && this.data.service !== "kargo") {
      throw new Error("Packing kayu cuma untuk kargo")
    }
    return { ...this.data, catatan: [...this.data.catatan] }
  }
}

const shipment = new ShipmentBuilder("KCL-0001")
  .service("kargo")
  .kg(12)
  .packingKayu()
  .cod(150000)
  .catatan("Isi kaca")
  .catatan("Jangan dibanting")
  .build()

console.log(shipment.packingKayu, shipment.asuransi) // true false
console.log(shipment.catatan.join(", "))             // Isi kaca, Jangan dibanting

Jadi untuk object dengan banyak bagian opsional, builder memberi dua hal: tempat pemanggilannya terbaca (tiap bagian disebut namanya), dan pemeriksaannya terkumpul di build().

Contoh kecil yang sama prinsipnya: method add(x) yang mem-push x lalu return this, dipanggil .add("a").add("b").add("c"), lalu build() yang menjalankan join("-"), menghasilkan a-b-c.

Latihan: URLBuilder

Latihan nanti memakai bentuk yang sama untuk alamat URL. Starter sudah berisi class URLBuilder dengan method kosong protocol(p), host(h), path(p), query(k, v), dan build():

Hasil yang diharapkan: https://api.example.com/users, https://example.com/search?q=laravel&page=2, dan http://localhost/.

Kapan gak perlu builder

⚠️ Jebakan yang sering ketemu

TL;DR: Constructor dengan banyak argumen berurutan gampang tertukar tanpa error. Di JavaScript, langkah pertama adalah satu object berisi opsi dengan nilai bawaan. Builder dipakai saat object dirakit bertahap, ada bagian yang berulang, atau ada aturan yang dicek di akhir: constructor mengisi nilai bawaan, tiap method mengisi satu bagian lalu return this supaya bisa dirantai (fluent API), dan build() memeriksa semuanya di satu tempat lalu mengembalikan salinan. Untuk object dua bagian, cukup object literal.

Yang akan kamu pelajari