Builder Pattern Giải Thích

Giới Thiệu
Hãy tưởng tượng bạn cần tạo một đối tượng phức tạp — một User với 10 trường tùy chọn, một HTTP request với headers, query params, body và timeout settings, hoặc một SQL query với joins, where clause và ordering.
Làm thế nào để tạo những đối tượng như vậy mà không phải viết các constructor khổng lồ hay làm đầy code với hàng loạt tham số null?
Builder Pattern giải quyết chính xác vấn đề này. Nó tách biệt quá trình xây dựng một đối tượng phức tạp ra khỏi đại diện của nó, cho phép cùng một quy trình xây dựng tạo ra các kết quả khác nhau.
Bạn Sẽ Học Được Gì
✅ Hiểu vấn đề mà Builder pattern giải quyết
✅ Triển khai Builder với fluent interface (method chaining)
✅ Sử dụng thành phần Director tùy chọn
✅ Áp dụng Builder trong TypeScript, Python và Java
✅ Nhận biết Builder pattern trong thực tế (query builders, HTTP clients)
✅ Biết khi nào nên dùng Builder so với các creational pattern khác
Điều Kiện Tiên Quyết
- Đã hoàn thành Factory và Abstract Factory Patterns
- Quen thuộc với SOLID Principles
- Thoải mái với classes và interfaces (Classes, Objects, and Abstraction)
Vấn Đề: Telescoping Constructors
Xem xét một class User với nhiều trường tùy chọn:
// ❌ Telescoping constructor anti-pattern
class User {
constructor(
name: string,
email: string,
age?: number,
phone?: string,
address?: string,
city?: string,
country?: string,
zipCode?: string,
profilePicture?: string,
bio?: string
) { ... }
}
// Gọi nó là cơn ác mộng
const user1 = new User("Alice", "[email protected]");
const user2 = new User("Bob", "[email protected]", 30, null, null, null, null, null, null, "Developer");
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ các tham số này là gì?Vấn đề với cách tiếp cận này:
- Call site khó đọc —
nullthứ 5 có nghĩa là gì? - Phụ thuộc thứ tự — dễ hoán đổi tham số nhầm
- Bùng nổ tổ hợp — 10 trường tùy chọn = rất nhiều constructor overloaded
- Khó mở rộng — thêm một trường phá vỡ tất cả call site hiện có
Giải Pháp Object Literal (và giới hạn của nó)
// Tốt hơn một chút — các trường có tên
const user = new User({
name: "Alice",
email: "[email protected]",
bio: "Developer"
});Cách này hoạt động với các trường hợp đơn giản, nhưng mất đi validation (bạn không thể validate từng bước), tính bất biến (object config có thể thay đổi), và khả năng đọc fluent (bạn không thể chain các lời gọi để diễn đạt pipeline).
Builder pattern giải quyết tất cả những vấn đề này.
Builder Pattern
Mục Đích
Tách biệt việc xây dựng một đối tượng phức tạp ra khỏi đại diện của nó để cùng một quy trình xây dựng có thể tạo ra các đại diện khác nhau.
Cấu Trúc
Các Thành Phần Chính
| Thành phần | Vai trò |
|---|---|
| Builder (interface) | Khai báo các bước xây dựng |
| ConcreteBuilder | Triển khai các bước xây dựng, giữ kết quả từng phần |
| Product | Đối tượng phức tạp đang được xây dựng |
| Director (tùy chọn) | Điều phối các bước xây dựng theo thứ tự cụ thể |
Triển Khai
TypeScript
// ✅ Product - đối tượng phức tạp
class User {
private constructor(
public readonly name: string,
public readonly email: string,
public readonly age?: number,
public readonly phone?: string,
public readonly address?: string,
public readonly bio?: string
) {}
// Static method để lấy builder
static builder(): UserBuilder {
return new UserBuilder();
}
toString(): string {
return `User(name=${this.name}, email=${this.email}, age=${this.age ?? 'N/A'})`;
}
}
// ✅ Builder - fluent interface với method chaining
class UserBuilder {
private _name!: string;
private _email!: string;
private _age?: number;
private _phone?: string;
private _address?: string;
private _bio?: string;
setName(name: string): UserBuilder {
this._name = name;
return this; // Cho phép chaining
}
setEmail(email: string): UserBuilder {
this._email = email;
return this;
}
setAge(age: number): UserBuilder {
if (age < 0 || age > 150) {
throw new Error(`Tuổi không hợp lệ: ${age}`);
}
this._age = age;
return this;
}
setPhone(phone: string): UserBuilder {
this._phone = phone;
return this;
}
setAddress(address: string): UserBuilder {
this._address = address;
return this;
}
setBio(bio: string): UserBuilder {
this._bio = bio;
return this;
}
build(): User {
// Validate các trường bắt buộc
if (!this._name) throw new Error("Tên là bắt buộc");
if (!this._email) throw new Error("Email là bắt buộc");
// Constructor private - chỉ builder mới có thể tạo User
return new (User as any)(
this._name,
this._email,
this._age,
this._phone,
this._address,
this._bio
);
}
}
// ✅ Cách dùng - fluent, dễ đọc, tự mô tả
const minimalUser = User.builder()
.setName("Alice")
.setEmail("[email protected]")
.build();
const fullUser = User.builder()
.setName("Bob")
.setEmail("[email protected]")
.setAge(30)
.setPhone("+1-555-0100")
.setAddress("123 Main St, New York")
.setBio("Full-stack developer với 5 năm kinh nghiệm")
.build();
console.log(minimalUser.toString()); // User(name=Alice, [email protected], age=N/A)
console.log(fullUser.name); // BobDirector (Tùy Chọn)
Director đóng gói các công thức xây dựng phổ biến:
// ✅ Director - thành phần tùy chọn cho các cấu hình preset
class UserDirector {
private builder: UserBuilder;
constructor(builder: UserBuilder) {
this.builder = builder;
}
// Xây dựng guest user với thông tin tối thiểu
buildGuestUser(name: string): User {
return this.builder
.setName(name)
.setEmail(`${name.toLowerCase().replace(' ', '.')}@guest.temp`)
.build();
}
// Xây dựng admin user đầy đủ
buildAdminUser(name: string, email: string): User {
return this.builder
.setName(name)
.setEmail(email)
.setAge(25)
.setBio("System Administrator")
.build();
}
}
const director = new UserDirector(User.builder());
const guest = director.buildGuestUser("Guest User");
const admin = director.buildAdminUser("Admin", "[email protected]");Python
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Optional
@dataclass
class User:
"""Product - đối tượng phức tạp đang được xây dựng."""
name: str
email: str
age: Optional[int] = None
phone: Optional[str] = None
address: Optional[str] = None
bio: Optional[str] = None
def __repr__(self) -> str:
return f"User(name={self.name!r}, email={self.email!r}, age={self.age!r})"
class UserBuilder:
"""Builder - xây dựng đối tượng User từng bước."""
def __init__(self) -> None:
self._name: Optional[str] = None
self._email: Optional[str] = None
self._age: Optional[int] = None
self._phone: Optional[str] = None
self._address: Optional[str] = None
self._bio: Optional[str] = None
def set_name(self, name: str) -> UserBuilder:
self._name = name
return self # Cho phép chaining
def set_email(self, email: str) -> UserBuilder:
self._email = email
return self
def set_age(self, age: int) -> UserBuilder:
if not 0 <= age <= 150:
raise ValueError(f"Tuổi không hợp lệ: {age}")
self._age = age
return self
def set_phone(self, phone: str) -> UserBuilder:
self._phone = phone
return self
def set_address(self, address: str) -> UserBuilder:
self._address = address
return self
def set_bio(self, bio: str) -> UserBuilder:
self._bio = bio
return self
def build(self) -> User:
if not self._name:
raise ValueError("Tên là bắt buộc")
if not self._email:
raise ValueError("Email là bắt buộc")
return User(
name=self._name,
email=self._email,
age=self._age,
phone=self._phone,
address=self._address,
bio=self._bio,
)
# Cách dùng - fluent và dễ đọc
minimal_user = (
UserBuilder()
.set_name("Alice")
.set_email("[email protected]")
.build()
)
full_user = (
UserBuilder()
.set_name("Bob")
.set_email("[email protected]")
.set_age(30)
.set_phone("+1-555-0100")
.set_address("123 Main St, New York")
.set_bio("Full-stack developer")
.build()
)
print(minimal_user) # User(name='Alice', email='[email protected]', age=None)
print(full_user.name) # BobJava
public class User {
// Các trường bất biến - chỉ được đặt qua builder
private final String name;
private final String email;
private final Integer age;
private final String phone;
private final String address;
private final String bio;
// Constructor private - chỉ Builder mới có thể tạo User
private User(Builder builder) {
this.name = builder.name;
this.email = builder.email;
this.age = builder.age;
this.phone = builder.phone;
this.address = builder.address;
this.bio = builder.bio;
}
// Getters
public String getName() { return name; }
public String getEmail() { return email; }
public Integer getAge() { return age; }
public String getPhone() { return phone; }
public String getAddress() { return address; }
public String getBio() { return bio; }
@Override
public String toString() {
return "User(name=" + name + ", email=" + email + ", age=" + age + ")";
}
// Static inner Builder class
public static class Builder {
// Các trường bắt buộc
private final String name;
private final String email;
// Các trường tùy chọn
private Integer age;
private String phone;
private String address;
private String bio;
public Builder(String name, String email) {
if (name == null || name.isEmpty()) throw new IllegalArgumentException("Tên là bắt buộc");
if (email == null || email.isEmpty()) throw new IllegalArgumentException("Email là bắt buộc");
this.name = name;
this.email = email;
}
public Builder age(int age) {
if (age < 0 || age > 150) throw new IllegalArgumentException("Tuổi không hợp lệ: " + age);
this.age = age;
return this;
}
public Builder phone(String phone) {
this.phone = phone;
return this;
}
public Builder address(String address) {
this.address = address;
return this;
}
public Builder bio(String bio) {
this.bio = bio;
return this;
}
public User build() {
return new User(this);
}
}
}
// Cách dùng
User minimalUser = new User.Builder("Alice", "[email protected]")
.build();
User fullUser = new User.Builder("Bob", "[email protected]")
.age(30)
.phone("+1-555-0100")
.address("123 Main St, New York")
.bio("Full-stack developer")
.build();
System.out.println(minimalUser); // User(name=Alice, [email protected], age=null)
System.out.println(fullUser.getName()); // BobLưu ý về style Java: Cách tiếp cận Java sử dụng static inner
Builderclass — đây là idiom Java thông thường (được phổ biến bởi Effective Java của Joshua Bloch). Các trường bắt buộc đặt trong constructorBuilder; các trường tùy chọn là chained setters.
Ví Dụ Thực Tế
1. HTTP Request Builder
Một use case thực tế phổ biến — xây dựng HTTP requests với nhiều phần tùy chọn:
class HttpRequest {
private constructor(
public readonly method: string,
public readonly url: string,
public readonly headers: Record<string, string>,
public readonly body?: string,
public readonly timeout: number = 5000,
public readonly retries: number = 0
) {}
static builder(method: string, url: string): HttpRequestBuilder {
return new HttpRequestBuilder(method, url);
}
}
class HttpRequestBuilder {
private _headers: Record<string, string> = {};
private _body?: string;
private _timeout = 5000;
private _retries = 0;
constructor(
private readonly _method: string,
private readonly _url: string
) {}
header(key: string, value: string): HttpRequestBuilder {
this._headers[key] = value;
return this;
}
bearerToken(token: string): HttpRequestBuilder {
return this.header("Authorization", `Bearer ${token}`);
}
jsonBody(data: object): HttpRequestBuilder {
this._body = JSON.stringify(data);
return this.header("Content-Type", "application/json");
}
timeout(ms: number): HttpRequestBuilder {
this._timeout = ms;
return this;
}
retries(count: number): HttpRequestBuilder {
this._retries = count;
return this;
}
build(): HttpRequest {
return new (HttpRequest as any)(
this._method,
this._url,
this._headers,
this._body,
this._timeout,
this._retries
);
}
}
// ✅ Dễ đọc, rõ ràng, có thể mở rộng
const request = HttpRequest.builder("POST", "https://api.example.com/users")
.bearerToken("abc123")
.jsonBody({ name: "Alice", role: "admin" })
.timeout(10_000)
.retries(3)
.build();2. SQL Query Builder
Một ví dụ kinh điển khác — xây dựng SQL queries động:
class QueryBuilder {
private table = "";
private conditions: string[] = [];
private columns: string[] = ["*"];
private orderColumn = "";
private orderDir: "ASC" | "DESC" = "ASC";
private limitVal?: number;
from(table: string): QueryBuilder {
this.table = table;
return this;
}
select(...columns: string[]): QueryBuilder {
this.columns = columns;
return this;
}
where(condition: string): QueryBuilder {
this.conditions.push(condition);
return this;
}
orderBy(column: string, direction: "ASC" | "DESC" = "ASC"): QueryBuilder {
this.orderColumn = column;
this.orderDir = direction;
return this;
}
limit(n: number): QueryBuilder {
this.limitVal = n;
return this;
}
build(): string {
if (!this.table) throw new Error("Tên bảng là bắt buộc");
let query = `SELECT ${this.columns.join(", ")} FROM ${this.table}`;
if (this.conditions.length > 0) {
query += ` WHERE ${this.conditions.join(" AND ")}`;
}
if (this.orderColumn) {
query += ` ORDER BY ${this.orderColumn} ${this.orderDir}`;
}
if (this.limitVal !== undefined) {
query += ` LIMIT ${this.limitVal}`;
}
return query;
}
}
// Cách dùng
const query = new QueryBuilder()
.from("users")
.select("id", "name", "email")
.where("age > 18")
.where("active = true")
.orderBy("name", "ASC")
.limit(10)
.build();
// SELECT id, name, email FROM users WHERE age > 18 AND active = true ORDER BY name ASC LIMIT 10
console.log(query);3. Notification Builder (Python)
from dataclasses import dataclass, field
from typing import List, Optional
from enum import Enum
class NotificationChannel(Enum):
EMAIL = "email"
SMS = "sms"
PUSH = "push"
@dataclass
class Notification:
title: str
body: str
channels: List[NotificationChannel]
recipient: str
priority: str = "normal"
scheduled_at: Optional[str] = None
metadata: dict = field(default_factory=dict)
class NotificationBuilder:
def __init__(self) -> None:
self._title: Optional[str] = None
self._body: Optional[str] = None
self._channels: List[NotificationChannel] = []
self._recipient: Optional[str] = None
self._priority = "normal"
self._scheduled_at: Optional[str] = None
self._metadata: dict = {}
def title(self, title: str) -> "NotificationBuilder":
self._title = title
return self
def body(self, body: str) -> "NotificationBuilder":
self._body = body
return self
def to(self, recipient: str) -> "NotificationBuilder":
self._recipient = recipient
return self
def via(self, *channels: NotificationChannel) -> "NotificationBuilder":
self._channels.extend(channels)
return self
def urgent(self) -> "NotificationBuilder":
self._priority = "high"
return self
def schedule(self, at: str) -> "NotificationBuilder":
self._scheduled_at = at
return self
def with_metadata(self, **kwargs) -> "NotificationBuilder":
self._metadata.update(kwargs)
return self
def build(self) -> Notification:
if not self._title:
raise ValueError("Tiêu đề là bắt buộc")
if not self._body:
raise ValueError("Nội dung là bắt buộc")
if not self._recipient:
raise ValueError("Người nhận là bắt buộc")
if not self._channels:
raise ValueError("Cần ít nhất một kênh")
return Notification(
title=self._title,
body=self._body,
channels=self._channels,
recipient=self._recipient,
priority=self._priority,
scheduled_at=self._scheduled_at,
metadata=self._metadata,
)
# Cách dùng - đọc như tiếng Anh tự nhiên
notification = (
NotificationBuilder()
.title("Đơn hàng mới")
.body("Đơn hàng #1234 của bạn đã được giao!")
.to("[email protected]")
.via(NotificationChannel.EMAIL, NotificationChannel.PUSH)
.urgent()
.with_metadata(order_id="1234", tracking="XYZ789")
.build()
)Builder So Với Các Creational Pattern Khác
| Pattern | Khi nào dùng | Tạo ra |
|---|---|---|
| Builder | Đối tượng phức tạp với nhiều phần tùy chọn | Một đối tượng phức tạp |
| Factory Method | Lớp con quyết định class nào để khởi tạo | Một đối tượng trong họ related objects |
| Abstract Factory | Các họ đối tượng liên quan mà không chỉ định class cụ thể | Nhiều đối tượng liên quan |
| Prototype | Sao chép một đối tượng hiện có | Bản sao của đối tượng |
| Singleton | Cần đúng một instance | Instance dùng chung duy nhất |
Dùng Builder khi:
- Đối tượng có 4+ tham số constructor (đặc biệt là các tham số tùy chọn)
- Xây dựng đối tượng cần nhiều bước
- Bạn muốn đối tượng bất biến với validation
- Bạn muốn API dễ đọc, tự mô tả
Không dùng Builder khi:
- Đối tượng chỉ có 1–3 trường bắt buộc đơn giản (quá phức tạp)
- Đối tượng có thể thay đổi (không có lợi ích bất biến)
- Một factory function đơn giản là đủ
Lỗi Phổ Biến
1. Product Có Thể Thay Đổi
// ❌ Builder tạo đối tượng mutable - đánh bại mục đích
class UserBuilder {
build(): User {
const user = new User();
user.name = this._name; // Setter public - ai cũng có thể thay đổi
user.email = this._email;
return user;
}
}
// ✅ Builder tạo đối tượng immutable
class UserBuilder {
build(): User {
return new User(this._name, this._email, ...); // Constructor private
}
}2. Quên Return this
// ❌ Phá vỡ chaining
class UserBuilder {
setName(name: string): void { // Trả về void!
this._name = name;
// quên return this
}
}
// ✅ Luôn return this từ các setter method
class UserBuilder {
setName(name: string): UserBuilder {
this._name = name;
return this;
}
}3. Bỏ Qua Validation Trong build()
// ❌ Không có validation - tạo đối tượng không hợp lệ một cách im lặng
class UserBuilder {
build(): User {
return new User(this._name!, this._email!); // ! bỏ qua null check
}
}
// ✅ Validate trong build()
class UserBuilder {
build(): User {
if (!this._name) throw new Error("Tên là bắt buộc");
if (!this._email || !this._email.includes("@")) {
throw new Error("Email hợp lệ là bắt buộc");
}
return new User(this._name, this._email);
}
}4. Builder Không Có Reset (Lỗi Tái Sử Dụng)
// ❌ Tái sử dụng builder tạo đối tượng bị ô nhiễm
const builder = new UserBuilder().setName("Alice").setEmail("[email protected]");
const alice = builder.build();
builder.setName("Bob"); // Quên đặt email!
const bob = builder.build(); // Bob có email của Alice!
// ✅ Tạo builder mới mỗi lần, hoặc thêm reset()
class UserBuilder {
reset(): UserBuilder {
this._name = undefined;
this._email = undefined;
this._age = undefined;
return this;
}
}Builder Trong Các Framework Bạn Đang Dùng
Bạn tương tác với Builder pattern liên tục:
Java Streams / StringBuilder:
String result = new StringBuilder()
.append("Xin chào")
.append(", ")
.append("Thế giới")
.toString();Java HttpClient (Java 11+):
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2)
.connectTimeout(Duration.ofSeconds(10))
.build();Spring Boot MockMvc (testing):
mockMvc.perform(
MockMvcRequestBuilders.post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content(json)
)Python requests session (kiểu builder):
session = requests.Session()
session.headers.update({"Authorization": "Bearer token"})
session.timeout = 30
response = session.get("https://api.example.com/data")TypeScript Prisma ORM:
const users = await prisma.user.findMany({
where: { active: true, age: { gte: 18 } },
select: { id: true, name: true, email: true },
orderBy: { name: "asc" },
take: 10,
});Tất cả đây đều là các biến thể của Builder pattern dưới các tên gọi khác nhau.
Tóm Tắt
Builder Pattern là một trong những creational pattern thực tế nhất — bạn sẽ sử dụng nó thường xuyên trong code thực tế.
Những điểm chính cần nhớ:
✅ Builder tách biệt xây dựng khỏi đại diện
✅ Fluent interface (method chaining với return this) làm cho việc xây dựng dễ đọc
✅ Validation thuộc về build(), không phải các setter riêng lẻ
✅ Constructor private đảm bảo chỉ builder mới có thể tạo product
✅ Director là tùy chọn — dùng khi bạn có các công thức xây dựng phổ biến
✅ Tạo instance builder mới cho mỗi đối tượng (hoặc thêm reset())
Khi nào nên dùng Builder:
- 4+ tham số constructor (đặc biệt là các tham số tùy chọn)
- Đối tượng cần xây dựng từng bước với validation
- Bạn muốn API bất biến, tự mô tả
Tiếp Theo Là Gì
Bạn đã hoàn thành Phase 3: Creational Patterns:
- ✅ Singleton Pattern
- ✅ Factory và Abstract Factory Patterns
- ✅ Builder Pattern (bài này)
- ➡️ Prototype Pattern — nhân bản đối tượng hiệu quả
Sau đó chúng ta chuyển sang Phase 4: Structural Patterns:
Phần 10 của series OOP & Design Patterns. Xây dựng trên Factory và Abstract Factory — creational pattern trước đó.
📬 Đăng ký nhận bản tin
Nhận những bài viết mới nhất qua email mỗi tuần. Không spam, hủy đăng ký bất cứ lúc nào.
Chúng tôi tôn trọng quyền riêng tư của bạn. Hủy đăng ký bất cứ lúc nào.
💬 Comments
Đăng nhập để bình luận
Chúng tôi sẽ không đăng bất cứ điều gì mà không có sự cho phép của bạn.