Creational Patterns - Builder
Khái niệm
Builder Pattern là một mẫu thiết kế thuộc nhóm khởi tạo (Creational Pattern), được sử dụng để xây dựng các đối tượng phức tạp từng bước một. Nó cho phép bạn tạo ra nhiều loại khác nhau của một đối tượng bằng việc sử dụng cùng mã xây dựng.
Các thành phần chính
- Product: Đối tượng phức tạp cần tạo, bao gồm nhiều thuộc tính.
- Builder: Abstract class/interface khai báo các bước tạo đối tượng.
- Concrete Builder: Cài đặt chi tiết các bước tạo và trả về đối tượng cuối cùng.
- Director: Điều khiển trình tự các bước gọi Builder để tạo đối tượng (tùy chọn).
-
Ưu điểm
- Tạo đối tượng linh hoạt: Từng bước thiết lập các thuộc tính, chỉ cần thiết lập những gì cần thiết.
- Tránh Constructor khổng lồ: Giải quyết vấn đề constructor chứa quá nhiều tham số (telescoping constructor).
- Tuân thủ Nguyên tác Trách nhiệm đơn nhất: Tách biệt code khởi tạo khỏi logic nghiệp vụ, giúp mã nguồn gọn gàng, dễ đọc và quản lý.
Nhược điểm
- Độ phức tập tổng thể của code tăng lên vì sẽ cần tạo ra nhiều lớp mới.
Ví dụ thực tế
- Bài toán
Vẫn là một trong các bài toán trong lĩnh vực quản lý nhà hàng như mô tả trong các mẫu thiết kế factory. Ta có thêm bài toán về việc đặt bàn từ khách hàng.
Khi khách hàng đặt bàn, họ sẽ gọi món trong thực đơn mà ta cung cấp. Họ có thể gọi một hoặc nhiều món khác nhau.
Phần mềm cần quản lý việc gọi món theo bàn như trên.
- Phân tích bài toán
Ta có thể quản lý việc gọi món của khách hàng theo bàn. Vì việc gọi món của khách hàng là ngẫu nhiên, ta không biết trước được những món nào sẽ được gọi. Vì vậy nên có một cách để quản lý danh sách thực đơn. Và mỗi bàn sẽ tương ứng với một danh sách thực đơn đó.
- Vì sao có thể áp dụng mẫu thiết kế Builder cho bài toán này?
Hãy suy luận một chút về ý nghĩa của các thành phần trong mẫu thiết kế này. Ta có suy luận một cách đơn giản là: Builder là những người xây dựng, Product là sản phẩm mà người xây dựng làm tạo ra, và Director là giám đốc để chỉ đạo người xây dựng nên tạo ra sản phẩm gồm những gì.
Ví dụ: Giám đốc chỉ đạo người xây dựng xây một ngôi nhà có thêm sân chơi cho khách hàng A. Nhưng đối với khách hàng B thì giám đốc chỉ đạo xây một ngôi nhà có sân chơi, có cả bể bơi chẳng hạn.
Áp dụng với bài toán này ta sẽ có: Thành phần Product chính là thực đơn gồm nhiều loại món khác nhau, thành phần Builder chính là việc xây dựng lên cái bàn sẽ có thực đơn là gì. Cuối cùng thành phần Director có thể coi là người phục vụ, sẽ chỉ định việc bàn này có những món nào theo thực đơn.
- Cách triển khai
Đầu tiên ta cần tạo đối tượng Builder, ta sẽ tạo đối tượng này là một giao diện xây dựng nên bàn để phục vụ cho việc triển khai cụ thể. Đối tượng này sẽ liệt kê các mục để tạo nên thực đơn theo bàn.
public interface IBanBuilder
{
void BuildDoAn();
void BuildDoUong();
}
Tiếp theo, ta sẽ thực hiện cụ thể hóa (Concrete Builder) giao diện xây dựng bàn ở trên. Mục đích cần trả về thực đơn theo bàn.
public class BanBuilder : IBanBuilder
{
private ThucDon _thucDon = new ThucDon();
public BanBuilder()
{
Reset();
}
public void Reset()
{
_thucDon = new ThucDon();
}
public void BuildDoAn()
{
_thucDon.Add("Com Rang");
}
public void BuildDoUong()
{
_thucDon.Add("Tra Da");
}
public ThucDon LayThucDon()
{
var thucDon = _thucDon;
Reset();
return thucDon;
}
}
Tạo đối tượng thực đơn (thành phần Product) mà người xây dựng sẽ tạo ra.
public class ThucDon
{
private List<object> _dsThucDon = new List<object>();
public void Add(string thucDon)
{
_dsThucDon.Add(thucDon);
}
public string DanhSachThucDon()
{
var strBuilder = new StringBuilder();
strBuilder.AppendJoin(", ", _dsThucDon);
return "Thuc don da goi: " + strBuilder.ToString();
}
}
Cuối cùng là đối tượng Director, đối tượng này không thực sự cần thiết trong mẫu thiết kế này. Nhưng nó hữu ích khi xây dựng thực đơn theo bàn.
public class BanDirector
{
private IBanBuilder _builder;
public IBanBuilder Builder
{
set { _builder = value; }
}
public void BuildComboBinhThuong()
{
this._builder.BuildDoAn();
}
public void BuildComboDayDu()
{
this._builder.BuildDoAn();
this._builder.BuildDoUong();
}
}
Đây là hàm Main mô phỏng việc đặt bàn của khách hàng. Khách hàng 1 chỉ gọi món bình thường. Khách hàng 2 sẽ gọi món đầy đủ.
class KhachHangBuilder
{
public void Main()
{
var director = new BanDirector();
var builder = new BanBuilder();
director.Builder = builder;
Console.WriteLine("Khach hang 1: Goi mon binh thuong");
director.BuildComboBinhThuong();
Console.WriteLine(builder.LayThucDon().DanhSachThucDon());
Console.WriteLine();
Console.WriteLine("Khach hang 2: Goi mon day du");
director.BuildComboDayDu();
Console.WriteLine(builder.LayThucDon().DanhSachThucDon());
}
}
Kết quả sau khi chạy chương trình.
Kết luận
Như vậy, qua bài viết này, tôi hi vọng các bạn sẽ nắm được khái niệm cũng như biết được việc khi nào thì nên áp dụng mẫu thiết kế Builder vào trong ứng dụng của mình.
Ví dụ tôi đưa đôi khi không chính xác và thuyết phục hoàn toàn, rất mong sự góp ý từ phía các bạn để tôi có thể củng cố thêm kiến thức cho bản thân mình.
