Mẫu Thiết Kế Builder: Tách Biệt Quá Trình Xây Dựng Và Biểu Diễn Đối Tượng

Tổng Quan Về Mẫu Builder

Mẫu thiết kế Builder (Xây dựng) giải quyết bài toán tách rời quá trình lắp ráp một đối tượng phức tạp khỏi cách thức biểu diễn cuối cùng của nó. Khi một lớp có quá nhiều tham số khởi tạo, đặc biệt là các tham số tùy chọn, việc sử dụng constructor truyền thống dễ dẫn đến "hiệu ứng kính viễn vọng" (telescoping constructor) – một chuỗi các constructor chồng chéo gây khó đọc, khó bảo trì và dễ phát sinh lỗi. Mẫu thiết kế này cung cấp một cơ chế từng bước để thiết lập các thuộc tính, đảm bảo mã nguồn sạch sẽ, an toàn và linh hoạt.

Các Thành Phần Cốt Lõi

  • Product (Sản phẩm): Đối tượng phức tạp cần được tạo ra, thường chứa nhiều thuộc tính và thành phần con.
  • Builder (Bộ xây dựng trừu tượng): Giao diện hoặc lớp trừu tượng định nghĩa các phương thức để tạo ra từng bộ phận của Product.
  • ConcreteBuilder (Bộ xây dựng cụ thể): Lớp hiện thực hóa giao diện Builder, trực tiếp lắp ráp các bộ phận, theo dõi trạng thái và trả về đối tượng Product hoàn chỉnh.
  • Director (Đạo diễn): Lớp điều phối, quy định trình tự và các bước gọi đến Builder để đảm bảo quá trình xây dựng diễn ra đúng logic nghiệp vụ.
  • Client (Khách hàng): Nơi khởi tạo Director và ConcreteBuilder, sau đó nhận về Product đã được lắp ráp hoàn thiện.

Biểu Đồ Cấu Trúc


classDiagram
    class Director {
        +assembleProduct()
    }
    class AbstractBuilder {
        <<interface>>
        +initializePartA()
        +initializePartB()
        +fetchProduct()
    }
    class SpecificBuilder {
        +initializePartA()
        +initializePartB()
        +fetchProduct()
    }
    class ComplexProduct {
        -componentA
        -componentB
    }
    Director --> AbstractBuilder
    AbstractBuilder <|-- SpecificBuilder
    SpecificBuilder --> ComplexProduct

Phương Pháp Triển Khai Hiện Đại: Fluent Interface

Trong lập trình hiện đại, đặc biệt là với Java, biến thể Fluent Interface được ưa chuộng hơn cả. Cách tiếp cận này lược bỏ lớp Director, đưa Builder vào làm static nested class bên trong Product và sử dụng method chaining (chuỗi phương thức) để tạo ra cú pháp mượt mà, dễ đọc.


public class DatabaseConnection {
    private final String host;
    private final int port;
    private final String dbName;
    private final int maxPoolSize;
    private final int timeoutMs;
    private final boolean useSsl;

    // Constructor riêng tư, bắt buộc khởi tạo thông qua Builder
    private DatabaseConnection(ConfigBuilder builder) {
        this.host = builder.host;
        this.port = builder.port;
        this.dbName = builder.dbName;
        this.maxPoolSize = builder.maxPoolSize;
        this.timeoutMs = builder.timeoutMs;
        this.useSsl = builder.useSsl;
    }

    public static class ConfigBuilder {
        // Các tham số bắt buộc
        private final String host;
        private final int port;
        private final String dbName;
        
        // Các tham số tùy chọn với giá trị mặc định
        private int maxPoolSize = 10;
        private int timeoutMs = 3000;
        private boolean useSsl = false;

        public ConfigBuilder(String host, int port, String dbName) {
            this.host = host;
            this.port = port;
            this.dbName = dbName;
        }

        public ConfigBuilder maxPoolSize(int size) {
            this.maxPoolSize = size;
            return this;
        }

        public ConfigBuilder timeout(int ms) {
            this.timeoutMs = ms;
            return this;
        }

        public ConfigBuilder enableSsl(boolean enable) {
            this.useSsl = enable;
            return this;
        }

        public DatabaseConnection build() {
            // Validate dữ liệu trước khi trả về đối tượng
            if (maxPoolSize <= 0) {
                throw new IllegalArgumentException("Pool size must be positive");
            }
            if (timeoutMs < 0) {
                throw new IllegalArgumentException("Timeout cannot be negative");
            }
            return new DatabaseConnection(this);
        }
    }
}

Cách sử dụng ở phía Client:


DatabaseConnection conn = new DatabaseConnection.ConfigBuilder("127.0.0.1", 5432, "production_db")
    .maxPoolSize(50)
    .timeout(5000)
    .enableSsl(true)
    .build();

Các Thực Hành Tốt Nhất

  • Ưu tiên Fluent Builder: Sử dụng method chaining cho các hệ thống hiện đại giúp tăng đáng kể tính đọc hiểu và giảm thiểu lỗi cấu hình.
  • Đóng gói Constructor: Đặt Builder làm static inner class và để constructor của Product ở mức private. Điều này ép buộc việc khởi tạo phải thông qua Builder, ngăn chặn việc tạo ra các đối tượng không hợp lệ.
  • Validate tập trung: Thực hiện kiểm tra tính toàn vẹn của dữ liệu ngay trong hàm build(). Nếu các ràng buộc logic bị vi phạm, hãy ném ra IllegalArgumentException hoặc IllegalStateException.
  • Thiết kế bất biến (Immutability): Khai báo tất cả thuộc tính của Product là final và chỉ gán giá trị một lần duy nhất. Điều này giúp đối tượng an toàn tuyệt đối trong môi trường đa luồng (thread-safe).
  • Phân biệt với Factory Pattern: Factory tập trung vào việc "tạo ra cái gì" (what) trong một bước và trả về ngay lập tức. Builder tập trung vào "tạo ra như thế nào" (how) qua nhiều bước cấu hình trước khi xuất ra kết quả.

Ứng Dụng Trong Các Framework Thực Tế

1. Lombok Annotation

Thư viện Lombok cung cấp annotation @Builder tự động sinh ra mã nguồn cho Fluent Builder ở thời điểm biên dịch, giúp tiết kiệm thời gian viết boilerplate code.


import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class PaymentTransaction {
    private final String transactionId;
    private final double amount;
    private final String currency;
    private final String status;
}

// Sử dụng
PaymentTransaction tx = PaymentTransaction.builder()
    .transactionId("TXN-98765")
    .amount(150.75)
    .currency("USD")
    .status("COMPLETED")
    .build();

2. Google Guava

Guava áp dụng mẫu Builder rất triệt để, điển hình là CacheBuilder cho phép lắp ráp một hệ thống cache phức tạp với các thông số tinh chỉnh chi tiết.


Cache<String, SessionData> sessionCache = CacheBuilder.newBuilder()
    .maximumSize(5000)
    .expireAfterAccess(30, TimeUnit.MINUTES)
    .recordStats()
    .build();

3. Java Core API

Các lớp như StringBuilderStringBuffer trong Java Core là những ví dụ sơ khai nhất. Mặc dù không tuân thủ nghiêm ngặt cấu trúc GoF với Director, nhưng chúng áp dụng hoàn hảo tư tưởng Builder: cho phép nối chuỗi từng bước qua các phương thức append() trước khi xuất ra kết quả cuối cùng thông qua toString().

Thẻ: Java design patterns Builder Pattern Lombok Guava

Đăng vào ngày 21 tháng 9 lúc 11:48