Pemrograman · Pola desain
Pola Builder dijelaskan dengan contoh
Jawaban singkat
Builder adalah pola desain creational yang memisahkan konstruksi objek kompleks dari representasi akhirnya, sehingga proses langkah demi langkah yang sama dapat menghasilkan hasil yang berbeda. Dalam kode modern, pola ini biasanya muncul sebagai API fluent: Anda memanggil method seperti .url(), .header(), dan .timeout() pada builder, lalu .build() mengembalikan objek jadi yang sudah divalidasi. Pola ini menggantikan konstruktor panjang dengan banyak parameter opsional.
Tujuan
Memisahkan konstruksi objek kompleks dari representasinya agar proses konstruksi yang sama dapat menghasilkan representasi yang berbeda. Pola Builder adalah salah satu dari lima pola creational dalam buku Gang of Four (GoF).
Masalah yang diselesaikan
Bayangkan objek permintaan HTTP dengan URL, method, sejumlah header, body opsional, timeout, dan jumlah percobaan ulang. Konstruktor untuk semua itu sulit dipanggil dengan benar:
class HttpRequest {
constructor(
readonly url: string,
readonly method: string,
readonly headers: Record<string, string>,
readonly body?: string,
readonly timeoutMs?: number,
readonly retries?: number,
) {}
}
// Which argument is which? Easy to swap 3 and 5000 by mistake.
const req = new HttpRequest("https://api.example.com", "POST", {}, undefined, 3, 5000);Ini disebut telescoping constructor. Konstruktor semacam itu juga tidak punya tempat untuk aturan seperti "permintaan GET tidak boleh punya body", dan tidak bisa dibangun bertahap, misalnya ketika header ditambahkan di beberapa tempat sebelum pengiriman.
Struktur dan peserta
| Peserta | Tanggung jawab | Dalam contoh |
|---|---|---|
| Builder | Mendeklarasikan langkah-langkah untuk membuat bagian-bagian produk. | Method RequestBuilder |
| ConcreteBuilder | Mengimplementasikan langkah-langkah, menyimpan state sebagian, dan mengembalikan hasil. | RequestBuilder |
| Director | Opsional. Menjalankan langkah dalam urutan tetap untuk menghasilkan konfigurasi yang umum. | jsonPost() |
| Product | Objek kompleks yang sedang dibangun. | HttpRequest |
Implementasi TypeScript
Setiap langkah menyimpan nilai dan mengembalikan this, sehingga pemanggilan bisa dirantai. build() memvalidasi kombinasinya dan mengembalikan produk yang immutable:
interface HttpRequest {
readonly url: string;
readonly method: "GET" | "POST";
readonly headers: Readonly<Record<string, string>>;
readonly body?: string;
readonly timeoutMs: number;
}
class RequestBuilder {
private method: "GET" | "POST" = "GET";
private headers: Record<string, string> = {};
private body?: string;
private timeoutMs = 10_000;
constructor(private readonly url: string) {}
post(body: string): this { this.method = "POST"; this.body = body; return this; }
header(name: string, value: string): this { this.headers[name] = value; return this; }
timeout(ms: number): this { this.timeoutMs = ms; return this; }
build(): HttpRequest {
if (this.timeoutMs <= 0) throw new Error("timeout must be positive");
return Object.freeze({
url: this.url, method: this.method, headers: { ...this.headers },
body: this.body, timeoutMs: this.timeoutMs,
});
}
}
const request = new RequestBuilder("https://api.example.com/orders")
.header("Accept", "application/json")
.timeout(5_000)
.build();
console.log(request.method, request.timeoutMs, request.headers);
// GET 5000 { Accept: 'application/json' }Director hanyalah kode yang tahu resep umum. Di TypeScript, Director sering berupa fungsi biasa yang menggerakkan builder:
interface Steps {
header(name: string, value: string): this;
post(body: string): this;
}
// Director: a reusable recipe that drives any builder with these steps.
function jsonPost<B extends Steps>(builder: B, data: unknown): B {
return builder
.header("Content-Type", "application/json")
.post(JSON.stringify(data));
}
class LogBuilder implements Steps {
log: string[] = [];
header(name: string, value: string): this { this.log.push(`${name}=${value}`); return this; }
post(body: string): this { this.log.push(`POST ${body}`); return this; }
}
console.log(jsonPost(new LogBuilder(), { id: 7 }).log);
// [ 'Content-Type=application/json', 'POST {"id":7}' ]Ini menunjukkan gagasan asli GoF: langkah konstruksi yang sama, digerakkan oleh director yang sama, dapat disalurkan ke builder berbeda dan menghasilkan representasi berbeda, di sini berupa log, bukan permintaan.
Kapan memakainya, dan kapan tidak
Gunakan pola Builder ketika:
- Sebuah objek punya banyak bagian opsional, atau bagian yang harus ditambahkan satu per satu.
- Konstruksi punya aturan yang melibatkan beberapa field dan sebaiknya diperiksa sekali saja, di
build(). - Anda ingin objek jadi bersifat immutable, tetapi tetap dirakit langkah demi langkah.
- Proses konstruksi yang sama harus menghasilkan representasi berbeda, seperti dokumen yang diekspor sebagai HTML, Markdown, atau PDF.
Hindari ketika:
- Objek hanya punya beberapa field. Konstruktor lebih singkat dan sama jelasnya.
- Bahasa Anda sudah punya parameter bernama atau opsional. Di TypeScript, satu objek opsi sering menghilangkan kebutuhan akan builder sama sekali.
interface RequestOptions {
url: string;
method?: "GET" | "POST";
headers?: Record<string, string>;
timeoutMs?: number;
}
function createRequest({ url, method = "GET", headers = {}, timeoutMs = 10_000 }: RequestOptions) {
return { url, method, headers, timeoutMs };
}
console.log(createRequest({ url: "/orders", timeoutMs: 5_000 }).method); // GETPemakaian di dunia nyata
- Java:
java.net.http.HttpRequest.newBuilder()mengembalikanHttpRequest.Builder; Anda merantaiuri(),header(), dantimeout(), lalu memanggilbuild(). - Java:
Locale.Buildermerakit dan memvalidasi locale dari bagian bahasa, wilayah, dan skrip. - Java dan C#:
StringBuildermerakit string dari banyak bagian yang ditambahkan sebelum menghasilkan string akhir yang immutable. - Banyak query builder SQL dan builder data uji memakai gaya fluent yang sama.
Pola terkait
- Pola Abstract Factory membuat seluruh keluarga produk dan langsung mengembalikan masing-masing; pola Builder membangun satu produk kompleks langkah demi langkah dan mengembalikannya di akhir.
- Pohon pola Composite sering dibuat dengan builder, karena membangun pohon simpul demi simpul adalah rangkaian langkah yang alami.
- Fluent interface adalah gaya API dengan merantai pemanggilan yang mengembalikan
this. Pola Builder sering memakainya, tetapi keduanya bukan hal yang sama. - Pola Prototype bisa menjadi alternatif ketika sebagian besar objek baru hanyalah variasi kecil dari templat yang sudah jadi.
FAQ
Mengapa setiap method builder mengembalikan this?
Mengembalikan builder memungkinkan Anda merantai pemanggilan dalam satu ekspresi, seperti .header(...).timeout(...).build(). Mengembalikan this di TypeScript juga menjaga tipe yang benar pada subkelas.
Apakah saya perlu kelas Director?
Tidak. Director bersifat opsional dalam deskripsi GoF. Dalam praktiknya, fungsi yang menerapkan urutan langkah yang umum biasanya sudah cukup.
Apakah pola Builder masih berguna di TypeScript?
Kadang-kadang. Objek opsi dengan nilai default mencakup sebagian besar kasus sederhana. Pola Builder tetap berguna ketika objek dirakit bertahap, ketika validasi lintas field diperlukan, atau ketika Anda menginginkan hasil yang immutable.
Apa beda pola Builder dan pola Factory Method?
Factory Method menentukan kelas mana yang diinstansiasi dan mengembalikan objek dalam satu panggilan. Builder membiarkan klien mengendalikan cara satu objek dirakit lewat beberapa panggilan.