Backend

99 Ngày Java — Ngày 93: Builder và Prototype

SSite Admin
28 tháng 10, 2026 3 phút đọc 5 lượt xem
99 Ngày Java — Ngày 93: Builder và Prototype

Một constructor với tám tham số, trong đó năm cái cùng kiểu String, là lời mời viết nhầm thứ tự. Builder tạo đối tượng từng bước với tên rõ ràng cho từng giá trị. Prototype giải bài toán khác: tạo đối tượng mới bằng cách sao chép một đối tượng có sẵn. Trong Java, phần khó của Prototype là sao chép cho đúng.

Builder và Prototype

1. Vấn đề của constructor nhiều tham số

Khi một lớp có nhiều trường tùy chọn, người ta hay viết chuỗi constructor chồng nhau hoặc dùng setter. Constructor chồng nhau khó đọc ở chỗ gọi vì không biết tham số thứ tư là gì. Setter thì để đối tượng tồn tại ở trạng thái dở dang và không thể bất biến. Builder giữ được cả hai điều tốt: chỗ gọi dễ đọc và đối tượng kết quả bất biến.

// what is true? what is 3?
var request = new HttpCall("https://api.example", "POST", body, 3, true, null);

// with a builder
var request = HttpCall.builder("https://api.example")
        .method("POST")
        .body(body)
        .retries(3)
        .followRedirects(true)
        .build();

2. Viết một builder

Builder là một lớp lồng static giữ các giá trị tạm. Tham số bắt buộc đi vào phương thức tạo builder, tham số tùy chọn có giá trị mặc định. Mỗi phương thức gán giá trị rồi trả về this để nối chuỗi lời gọi. build() là nơi kiểm tra các ràng buộc liên quan tới nhiều trường, rồi tạo đối tượng bất biến. Với record, builder gọi canonical constructor, nơi kiểm tra từng trường.

record HttpCall(String url, String method, String body, int retries) {
    static Builder builder(String url) { return new Builder(url); }

    static final class Builder {
        private final String url;
        private String method = "GET";
        private String body;
        private int retries;

        private Builder(String url) { this.url = Objects.requireNonNull(url); }
        Builder method(String method) { this.method = method; return this; }
        Builder body(String body) { this.body = body; return this; }
        Builder retries(int retries) { this.retries = retries; return this; }

        HttpCall build() {
            if (body != null && method.equals("GET"))
                throw new IllegalStateException("GET must not have a body");
            return new HttpCall(url, method, body, retries);
        }
    }
}

3. Prototype: sao chép thay vì tạo mới

Prototype hữu ích khi đối tượng mẫu tốn công dựng hoặc khi bạn cần nhiều biến thể chỉ khác vài trường. Java có sẵn clone(), nhưng cơ chế này nhiều bẫy: Cloneable không khai báo phương thức nào, clone() mặc định chỉ sao chép nông và bỏ qua constructor. Cách được khuyên dùng là copy constructor hoặc static factory sao chép, nơi bạn quyết định rõ trường nào dùng chung, trường nào sao chép sâu.

final class ReportTemplate {
    private final String title;
    private final List<String> columns;

    ReportTemplate(String title, List<String> columns) {
        this.title = title;
        this.columns = List.copyOf(columns);       // defensive copy
    }

    ReportTemplate(ReportTemplate source) {        // copy constructor
        this(source.title, source.columns);
    }

    ReportTemplate withTitle(String newTitle) {    // variant of the prototype
        return new ReportTemplate(newTitle, columns);
    }
}

4. Nông, sâu và khi nào dùng

Sao chép nông tạo đối tượng mới nhưng các trường tham chiếu vẫn trỏ tới cùng đối tượng con; sửa danh sách ở bản sao là sửa luôn bản gốc. Sao chép sâu nhân bản cả đối tượng con. Nếu các thành phần đều bất biến, sao chép nông là đủ và an toàn, và đó là lý do nên thiết kế bất biến ngay từ đầu. Record với các phương thức with... chính là Prototype dạng gọn.

  • Dùng builder khi có từ bốn tham số trở lên hoặc nhiều tham số tùy chọn.

  • Hai ba tham số bắt buộc thì constructor hoặc static factory rõ hơn.

  • JDK dùng builder ở HttpRequest.newBuilder(), StringBuilder, Stream.builder().

  • Tránh implements Cloneable trong code mới; mảng là ngoại lệ, clone() của mảng dùng tốt.

Builder làm việc tạo đối tượng dễ đọc, Prototype làm việc sao chép có chủ đích. Ngày 94 chuyển sang mẫu hành vi: Strategy và Observer.

Tài liệu đối chiếu

  • https://refactoring.guru/design-patterns/builder

  • https://refactoring.guru/design-patterns/prototype

S

Site Admin

Engineer and writer. Building things with TypeScript and distributed systems.

Bình luận (0)

Bạn cần đăng nhập bằng Google để bình luận.

Hãy là người bình luận đầu tiên.

Bài viết liên quan

99 Ngày Spring — Ngày 93: Monitoring với Prometheus và Grafana

Phơi metric qua Actuator, Prometheus scrape, viết PromQL cho tỷ lệ lỗi và p99, dựng dashboard và alert cơ bản.

28 thg 10, 20263 phút7
99 Ngày Spring — Ngày 92: Structured Logging

Log dạng JSON, MDC và correlation id, key-value thay cho ghép chuỗi, và cách tập trung log từ nhiều instance.

27 thg 10, 20262 phút6
99 Ngày Java — Ngày 92: Singleton và Factory

Singleton thread-safe, enum singleton, static factory method, Factory Method và Abstract Factory với ví dụ Java.

27 thg 10, 20263 phút8