Tác giả: Jorge Gamba
Ngày đăng: 22 tháng 9, 2025
Trong một bài viết trước về mẫu builder generic trong C#, tôi đã chia sẻ một cách tiếp cận đơn giản để tạo đối tượng sử dụng builder generic. Giải pháp đó hoạt động tốt trong các kịch bản kiểm thử hoặc công cụ nội bộ, nơi các nhà phát triển đã hiểu rõ các quy tắc.
Nhưng đối với mã sản xuất—đặc biệt là khi cung cấp API công khai—chúng ta cần một cái gì đó mạnh mẽ hơn. Một builder generic để lại quá nhiều khoảng trống cho lỗi: ghi đè thuộc tính, thiếu phụ thuộc, hoặc kết hợp các tùy chọn không chính xác. Trong những trường hợp này, an toàn kiểu dữ liệu và một giao diện được hướng dẫn không chỉ là những tính năng tốt—mà chúng là thiết yếu.
Đó là lúc Mẫu Optional Progressive Builder xuất hiện.
Mục lục
Vấn đề với Builders Generic
Hãy xem xét ví dụ này từ cách tiếp cận builder generic:
var myCar = Create<Car>(with => {
with.Make = "Toyota";
with.Model = "Corolla";
with.Year = 2021;
with.Make = "Honda"; // Ối! Chúng ta vừa ghi đè Make
});
Không có gì ngăn cản việc thiết lập cùng một thuộc tính nhiều lần. Các nhà phát triển không nhận được hướng dẫn tại thời điểm biên dịch, và không có chỉ dẫn rõ ràng về mối quan hệ thuộc tính hoặc các kết hợp hợp lệ. Đối với sử dụng nội bộ, điều đó có thể chấp nhận được. Đối với API công khai? Không hẳn vậy.
Chúng ta cần một cách để hướng dẫn người dùng qua quá trình xây dựng, thực thi các quy tắc tại thời điểm biên dịch và làm cho API tự tài liệu hóa.
Giải pháp Optional Progressive Builder
Mẫu Optional Progressive Builder giải quyết những vấn đề này bằng cách tạo ra một giao diện fluent an toàn kiểu dữ liệu mà:
- Cho phép thiết lập thuộc tính tùy chọn
- Ngăn chặn việc thiết lập thuộc tính nhiều lần
- Sử dụng hệ thống kiểu dữ liệu để thực thi quy tắc tại thời điểm biên dịch
- Cung cấp hướng dẫn IntelliSense, làm cho API tự tài liệu hóa
Mẫu này đặc biệt mạnh mẽ khi xây dựng các giao diện fluent yêu cầu thu hẹp lựa chọn tiến bộ.
Triển khai
Hãy triển khai mẫu này cho lớp Car của chúng ta. Lưu ý rằng chúng tôi đang giữ mã đơn giản để dễ hiểu – các triển khai sản xuất sẽ bao gồm xác thực và xử lý lỗi phù hợp.
public record Car(string? Make = null, string? Model = null,
int? Year = null, string? Color = null);
public abstract class CarBuilderBase
{
protected readonly Car _car;
protected CarBuilderBase(Car car) => _car = car;
public Car Build() => _car;
}
public class CarBuilder : CarBuilderBase
{
public CarBuilder() : base(new Car()) { }
public CarMakeBuilder WithMake(string make) =>
new(_car with { Make = make });
public CarModelBuilder WithModel(string model) =>
new(_car with { Model = model });
public CarYearBuilder WithYear(int year) =>
new(_car with { Year = year });
public CarColorBuilder WithColor(string color) =>
new(_car with { Color = color });
}
Builders Chuyên biệt
Mỗi builder chỉ hiển thị các tùy chọn còn lại có sẵn:
public class CarMakeBuilder : CarBuilderBase
{
public CarMakeBuilder(Car car) : base(car) { }
public CarMakeModelBuilder WithModel(string model) =>
new(_car with { Model = model });
public CarMakeYearBuilder WithYear(int year) =>
new(_car with { Year = year });
public CarMakeColorBuilder WithColor(string color) =>
new(_car with { Color = color });
}
Builders Kết hợp
Khi nhiều thuộc tính đã được thiết lập, tên builder phản ánh điều này:
public class CarMakeModelBuilder : CarBuilderBase
{
public CarMakeModelBuilder(Car car) : base(car) { }
public CarMakeModelYearBuilder WithYear(int year) =>
new(_car with { Year = year });
public CarMakeModelColorBuilder WithColor(string color) =>
new(_car with { Color = color });
}
// ... các triển khai tương tự cho tất cả 16 kết hợp
Ví dụ Sử dụng
Sử dụng builder này cảm thấy tự nhiên và ngăn chặn lỗi:
// Bắt đầu với Make
var car1 = new CarBuilder()
.WithMake("Toyota")
.WithModel("Corolla")
.Build();
// Bắt đầu với Year
var car2 = new CarBuilder()
.WithYear(2024)
.WithMake("Honda")
.WithColor("Blue")
.Build();
// Cấu hình tối thiểu
var car3 = new CarBuilder()
.WithModel("Civic")
.Build();
Trải nghiệm IntelliSense
Hỗ trợ IntelliSense trở thành một tính năng mạnh mẽ của mẫu này. Sau khi thiết lập các thuộc tính, chỉ các tùy chọn còn lại mới khả dụng, ngăn chặn các phép gán trùng lặp tại thời điểm biên dịch.
Ưu điểm
- An toàn Kiểu dữ liệu: Không thể vô tình ghi đè một thuộc tính
- API Tự tài liệu hóa: IntelliSense hướng dẫn các nhà phát triển qua các tùy chọn có sẵn
- Xác thực tại Thời điểm Biên dịch: Lỗi được phát hiện trước thời gian chạy
- Thu hẹp Tiến bộ: Mỗi bước giảm độ phức tạp bằng cách chỉ hiển thị các tùy chọn hợp lệ
- Giao diện Fluent: Cú pháp tự nhiên, dễ đọc để xây dựng đối tượng
- Không Thay đổi Phá vỡ: Thêm thuộc tính tùy chọn mới không phá vỡ mã hiện có
Nhược điểm
- Độ phức tạp Triển khai: Số lượng builders tăng theo cấp số nhân (2^n cho n thuộc tính), yêu cầu phát triển trước đáng kể và bảo trì liên tục
- Đường cong Học tập: Mẫu có thể xa lạ với một số nhà phát triển
- Quá mức cho Đối tượng Đơn giản: Không đáng với độ phức tạp cho các DTO cơ bản
Khi nào Sử dụng Mẫu này
Các ứng viên tốt:
- API công khai và SDK được tiêu thụ bởi các nhà phát triển bên ngoài
- Đối tượng cấu hình với các quy tắc xác thực phức tạp
- Builders truy vấn fluent (nghĩ về LINQ hoặc builders truy vấn SQL)
- Cấu hình HTTP client
- Đối tượng domain phức tạp với quy tắc kinh doanh
- Bất kỳ kịch bản nào mà việc ngăn chặn lạm dụng là quan trọng
Khi nào KHÔNG Sử dụng Mẫu này
Tránh mẫu này khi:
- Xây dựng DTO hoặc POCO đơn giản
- API nội bộ với các nhóm nhỏ, đáng tin cậy
- Đối tượng với ít thuộc tính (ví dụ: 3)
- Các kịch bản tạo mẫu nhanh
- Khi các thuộc tính không có sự phụ thuộc lẫn nhau
Hãy nhớ: khi tất cả những gì bạn có là một cái búa, mọi thứ trông giống như một cái đinh. Hãy xem xét các giải pháp thay thế đơn giản hơn sau đây:
- Đối tượng đơn giản: Sử dụng bộ khởi tạo đối tượng
- Kịch bản kiểm thử: Sử dụng builder generic từ bài viết trước của chúng tôi
- Công cụ nội bộ: Dựa vào quy ước nhóm và đánh giá mã
- Ít thuộc tính: Constructor hoặc phương thức factory có thể đủ
Điểm chính Rút ra
Mẫu Optional Progressive Builder cung cấp cho bạn một cách an toàn kiểu dữ liệu, được hướng dẫn và có thể khám phá để xây dựng đối tượng trong C#. Đó là một giải pháp thay thế tinh vi hơn cho builders generic—một giải pháp đánh đổi độ phức tạp cho sự rõ ràng, chính xác và trải nghiệm nhà phát triển.
Tại Trailhead, chúng tôi sử dụng các mẫu như thế này khi xây dựng API và SDK cần mạnh mẽ, trực quan và kiên cường trước việc sử dụng sai. Cho dù bạn đang hiện đại hóa một hệ thống cũ hay thiết kế một API mới, mẫu xây dựng phù hợp có thể làm cho mã của bạn không chỉ an toàn hơn—mà còn là niềm vui khi sử dụng.
Muốn nói thêm về các mẫu thiết kế API, giao diện fluent, hoặc hiện đại hóa ứng dụng .NET của bạn? Liên hệ với chúng tôi tại Trailhead Technology Partners.



