cv.cm

Pemrograman · Pola desain

Pola Builder dijelaskan dengan contoh

4 menit baca

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

PesertaTanggung jawabDalam contoh
BuilderMendeklarasikan langkah-langkah untuk membuat bagian-bagian produk.Method RequestBuilder
ConcreteBuilderMengimplementasikan langkah-langkah, menyimpan state sebagian, dan mengembalikan hasil.RequestBuilder
DirectorOpsional. Menjalankan langkah dalam urutan tetap untuk menghasilkan konfigurasi yang umum.jsonPost()
ProductObjek 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); // GET

Pemakaian di dunia nyata

  • Java: java.net.http.HttpRequest.newBuilder() mengembalikan HttpRequest.Builder; Anda merantai uri(), header(), dan timeout(), lalu memanggil build().
  • Java: Locale.Builder merakit dan memvalidasi locale dari bagian bahasa, wilayah, dan skrip.
  • Java dan C#: StringBuilder merakit 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.

Semua tutorial pemrograman