Lắp ráp object phức tạp từng bước, đặt tên rõ ràng cho từng tham số, chỉ khai báo những gì khác mặc định.Assemble a complex object step by step, naming each parameter and setting only what differs from defaults.
Gọi một ly trà sữa: size L, 50% đường, ít đá, thêm trân châu, thêm pudding. Bạn nói từng lựa chọn có tên rõ ràng, cái gì không nói thì quán dùng mặc định. Cuối cùng quán mới 'build' ra ly trà sữa hoàn chỉnh — không ai phải nhớ thứ tự 12 tham số.Ordering bubble tea: size L, 50% sugar, less ice, add pearls, add pudding. You state each choice by name; anything unsaid uses the shop's default. Only at the end does the shop 'build' the finished drink — nobody has to remember the order of 12 parameters.
02
Vấn đề và giải phápProblem and solution
Không có patternWithout the pattern
Đơn giao hàng có mã đơn, SĐT, địa chỉ, danh sách hàng, tiền COD, hàng dễ vỡ, ghi chú... Constructor 8 tham số kiểu new Shipment("DH1", "0909", addr, items, null, false, true, null) thì không ai đọc nổi, dễ truyền nhầm vị trí hai tham số cùng kiểu, và cứ thêm field là phải thêm một constructor nạp chồng (telescoping constructor).A shipment has an order id, phone, address, items, COD amount, fragile flag, notes... An 8-arg constructor like new Shipment("DH1", "0909", addr, items, null, false, true, null) is unreadable, makes it easy to swap two same-typed args, and every new field means another overloaded constructor (telescoping constructors).
Với BuilderWith Builder
Builder cho phép đặt từng giá trị qua method có tên (.address(...), .fragile(true)), có sẵn giá trị mặc định, và chỉ tạo object bất biến khi gọi build() — nơi duy nhất kiểm tra hợp lệ. Trong Java, Lombok @Builder sinh sẵn builder cho class/record. Trong Go, ta dùng struct literal có tên field hoặc mẫu functional options New(required, opts...).A Builder sets each value through a named method (.address(...), .fragile(true)), provides defaults, and only creates the immutable object on build() — the single place for validation. In Java, Lombok @Builder generates the builder for a class/record. In Go, use named-field struct literals or the functional options pattern New(required, opts...).
Thành phần tham giaParticipants
Builder
Giữ giá trị tạm thời, mỗi setter trả về chính nó để gọi nối chuỗi.Holds temporary values; each setter returns itself for chaining.
Product
Object kết quả, thường bất biến (immutable).The resulting object, usually immutable.
build()
Bước cuối: áp mặc định, kiểm tra hợp lệ, tạo Product.Final step: apply defaults, validate, create the Product.
Client
Gọi chuỗi setter có tên rồi build().Calls a chain of named setters, then build().
03
Sơ đồ và luồng thực thiDiagram and execution flow
Dùng Tự chạy hoặc phím mũi tên để đi qua từng bước gọi giữa các object. Console hiển thị output tương ứng của chương trình.Use Autoplay or the arrow keys to step through each call between objects. The console shows the matching program output.
1Lúc compile, Lombok đọc @Builder và sinh class Shipment.Builder — bạn không viết dòng nào.At compile time Lombok reads @Builder and generates Shipment.Builder — you write zero lines of it.
implementsimplementsextendsextendsgọi / dùngcalls / usestạo racreatesgửi qua channelsends via channel1thứ tự chạy (bấm để nhảy bước)execution order (click to jump)
04
Code: Java Spring Boot ⇄ GoCode: Java Spring Boot ⇄ Go
Cùng một bài toán, hai ngôn ngữ. Rê chuột hoặc focus vào chú thích (số màu cam) để làm nổi bật dòng code tương ứng.Same problem, two languages. Hover or focus an annotation (orange number) to highlight its line.
package com.shop.shipping;import java.time.Duration;// Hand-written builder: what Lombok generates, plus custom rulespublic final class RetryPolicy { private final int maxAttempts; private final Duration backoff; private final boolean jitter; private RetryPolicy(Builder b) {1 this.maxAttempts = b.maxAttempts; this.backoff = b.backoff; this.jitter = b.jitter; } public static Builder builder() { return new Builder(); } public int maxAttempts() { return maxAttempts; } public static final class Builder { private int maxAttempts = 3; // sensible defaults2 private Duration backoff = Duration.ofMillis(200); private boolean jitter; public Builder maxAttempts(int n) { this.maxAttempts = n; return this; }3 public Builder backoff(Duration d) { this.backoff = d; return this; } public Builder jitter(boolean j) { this.jitter = j; return this; } public RetryPolicy build() {4 if (maxAttempts < 1) throw new IllegalStateException("maxAttempts must be >= 1"); return new RetryPolicy(this); } }}
GoGo 1.23 · thư viện chuẩnGo 1.23 · standard library
package ghnimport ( "net/http" "time")type Client struct { baseURL string token string timeout time.Duration retries int http *http.Client}// Option configures a Client during construction.type Option func(*Client)1func WithTimeout(d time.Duration) Option {2 return func(c *Client) { c.timeout = d }}func WithRetries(n int) Option { return func(c *Client) { c.retries = n }}func WithHTTPClient(h *http.Client) Option { return func(c *Client) { c.http = h }}// New takes required args positionally and optional ones as variadic Options.func New(baseURL, token string, opts ...Option) *Client {3 c := &Client{ baseURL: baseURL, token: token, timeout: 5 * time.Second,4 retries: 3, } for _, opt := range opts { opt(c) } if c.http == nil { c.http = &http.Client{Timeout: c.timeout} } return c}func (c *Client) Timeout() time.Duration { return c.timeout }
Khác biệt chính giữa Java và GoKey differences between Java and Go
01Java dùng class Builder riêng (viết tay hoặc Lombok sinh). Go hay dùng functional options: New(required, opts ...Option) — mỗi option là một closure.Java uses a separate Builder class (hand-written or Lombok-generated). Go favours functional options: New(required, opts ...Option) — each option is a closure.
02Go có struct literal với tên field và zero value, nên dữ liệu đơn giản không cần builder. Java không có tham số có tên nên builder phổ biến hơn nhiều.Go has named-field struct literals and zero values, so plain data needs no builder. Java lacks named arguments, which is why builders are far more common there.
03Lombok sinh code lúc compile qua annotation processor; Go không có 'phép thuật' đó — mọi thứ viết tường minh.Lombok generates code at compile time via an annotation processor; Go has no such magic — everything is explicit.
04Record Java bất biến thật sự. Struct Go có thể sửa sau khi tạo; muốn 'bất biến' thì để field unexported và chỉ mở getter.Java records are truly immutable. Go structs can be modified after creation; for 'immutability' keep fields unexported and expose getters only.
HttpClient.newBuilder().connectTimeout(...).build() và Stream.builder() là builder chuẩn của JDK.HttpClient.newBuilder().connectTimeout(...).build() and Stream.builder() are standard JDK builders.
grpc.NewClient(addr, grpc.WithTransportCredentials(...)) là functional options kinh điển; strings.Builder ghép chuỗi từng bước.grpc.NewClient(addr, grpc.WithTransportCredentials(...)) is the classic functional options API; strings.Builder assembles strings step by step.
Request tạo đơn giao hàng, test fixtureShipping requests, test fixtures
Payload GHN/GHTK nhiều field tuỳ chọn; trong test, builder giúp tạo dữ liệu mẫu chỉ khác mặc định một vài field.GHN/GHTK payloads have many optional fields; in tests a builder creates sample data differing from defaults in only a few fields.
06
Khi nào dùng và khi nào khôngWhen to use it, and when not
Nên dùng khiUse it when
Object có nhiều tham số, đặc biệt là nhiều tham số tuỳ chọn hoặc cùng kiểu.Objects with many parameters, especially optional or same-typed ones.
Muốn object bất biến nhưng vẫn tạo dễ đọc, có validate tập trung.You want immutable objects that are still readable to create, with centralized validation.
API thư viện/client cần thêm tuỳ chọn mới mà không phá code người dùng cũ (Go functional options).A library/client API needs new options without breaking existing callers (Go functional options).
Không nên dùng khiAvoid it when
Object chỉ 2–3 field bắt buộc — constructor hoặc record là đủ.Objects with only 2–3 required fields — a constructor or record is enough.
Trong Go, struct dữ liệu thuần không cần logic khởi tạo — dùng struct literal thay vì functional options.In Go, plain data structs with no init logic — use a struct literal instead of functional options.
Ưu điểmPros
Code tạo object dễ đọc, không nhầm thứ tự tham số.Readable creation code, no mixed-up argument order.
Hỗ trợ giá trị mặc định và kiểm tra hợp lệ tại một chỗ (build/New).Supports defaults and validation in one spot (build/New).
Thêm field tuỳ chọn mới không phá các lời gọi cũ.Adding a new optional field doesn't break existing calls.
Nhược điểmCons
Builder viết tay rất dài dòng (đó là lý do Lombok tồn tại).Hand-written builders are very verbose (that's why Lombok exists).
Thiếu field bắt buộc chỉ bị phát hiện lúc chạy build(), không phải lúc compile.Missing required fields are caught only at build() runtime, not at compile time.
07
Anti-pattern: lỗi thường gặpAnti-pattern: common mistake
Builder không kiểm tra gì cảA builder that validates nothing
Dùng @Builder trên class mutable có @Data và không validate: builder tạo ra object thiếu mã đơn/địa chỉ, lỗi chỉ lộ ra khi gọi API hãng vận chuyển. Builder nên tạo object bất biến và kiểm tra ở build()/constructor.Using @Builder on a mutable @Data class with no validation: the builder happily creates shipments with no order id/address, and the bug only surfaces when calling the carrier API. A builder should produce immutable objects and validate in build()/the constructor.
@Data // ❌ setters everywhere: "built" object can be changed later@Builderpublic class Shipment { private String orderId; // ❌ required, but nothing enforces it private String address; private List<String> items; private boolean fragile;}Shipment s = Shipment.builder().fragile(true).build(); // compiles, invalids.setAddress(null); // and still mutable
Thêm tuỳ chọn 'giao giờ hành chính' (officeHoursOnly, mặc định false) và 'ghi chú cho shipper' (note) cho Shipment (Java) và tuỳ chọn WithUserAgent cho ghn.Client (Go).Add an 'office hours only' option (officeHoursOnly, default false) and a 'note for the shipper' (note) to Shipment (Java), plus a WithUserAgent option for ghn.Client (Go).
09
Kiểm tra kiến thứcKnowledge check
Câu 1 / 5Question 1 of 5
Builder giải quyết vấn đề chính nào?What main problem does Builder solve?
Phím 1–4 để chọn đáp án, Enter để kiểm tra.Press 1–4 to choose an answer, Enter to check.
10
Thẻ ghi nhớFlashcards
Ôn tập ngắt quãng (spaced repetition): thẻ được lặp lại sau 1 → 3 → 7 → 14 → 30 ngày để củng cố trí nhớ dài hạn.Spaced repetition: cards are repeated after 1 → 3 → 7 → 14 → 30 days to build long-term memory.
Dùng trong bài toánUsed in problems
2 bài toán Backend dùng pattern này2 backend problems use this pattern