Design Pattern: Builder pattern

Chào các bạn, Ở những bài viết trước, mình đã đi qua 5 nguyên tắc trong SOLID. Ở bài viết này mình sẽ bắt đầu đi vào design pattern đầu tiên, đó là Builder Pattern. Builder pattern là một pattern nằm trong nhóm khởi tạo (Creational Pattern). Trước tiên mình sẽ đi vào khái niệm để biết builder pattern là cái gì.

Builder pattern là gì?

Giả sử chúng ta có một object với rất nhiều thuộc tính và việc khởi tạo những thuộc tính đó qua constructor sẽ làm cho constructor của chúng ra rườm rà và tăng khả năng lỗi. Hơn nữa không phải lúc nào những thuộc tính đó cũng được khởi tạo 1 cách đồng bộ. Có những thuộc tính được khởi tạo khi dịch chương trình, có những thuộc tính tùy theo yêu cầu người dùng và ngữ cảnh của chương trình. Builder ra đời để giải quyết vấn đề đó bằng việc chia nhỏ việc khởi tạo các tham số của object thành nhiều phương thức nhỏ.

Một ví dụ chúng ta thường hay gặp có sử dụng Builer pattern đó là StringBuilder. Chúng ta hãy cũng xem một ví dụ sử dụng StringBuilder nhé.

Thay vì nối chuỗi string thì chúng ta khai báo một StringBuilder và append thêm vào chuỗi ban đầu. Đây là một ví dụ nhỏ mà chúng ta thường hay bắt gặp. Ngoài lề chút là bạn hãy tìm hiểu sự khác nhau giữa String và StringBuilder 🙂 Sử dụng cái nào tốt hơn. Tham khảo bài viết này nhé. Chúng ta hãy cùng áp dụng thử builder để làm một ứng dụng nhỏ nhé.

Áp dụng vào ví dụ nhỏ

Đây là một class chứa thông tin của một thẻ HTML bao gồm tên của tên thẻ html và text. Chúng ta có phương thức ToStringImp() để in ra. Tiếp theo là class Builder

HtmlBuilder khởi tạo với một constructor nhận tham số là tên của root tag. Builder có phương thức AddChild() để thêm mới một HtmlElement con trong root. Phương thức Clear() để khỏi tạo lại root và đảm bảo vẫn giữ cái name.

Đoạn code thực thi chương trình ra kết quả như sau:

builder pattern

Xin nhắc lại đây chỉ là một ví dụ để thấy được tổng thể pattern. :)) Ví dụng mở rộng không thật sự tốt.

Fluent Builder

Thỉnh thoảng chúng ta thấy người ta sử dụng StringBuilder theo cách này

Nhìn đoạn code trên gọn gàng hơn và có vẻ bờ-rồ hơn. Làm thế éo nào mà họ làm được như vậy? Bạn có thể search với từ khóa Fluent Interface để hiểu rõ hơn. Mình sẽ chỉnh sửa một chút để biến HtmlBuilder thành một Fluent Builder. Sửa lại phương thức AddChild() như sau:

Và bây giờ chúng ta có thể gọi theo cách bờ rồ hơn: htmlBuilder.AddChild("li", "Hello").AddChild("li", "World"); 😆

Ví dụ khác về Fluent Builer

Giả sử chúng ta có một kịch bản như sau: Có một đối tượng Person cần lưu trữ lại thông tin bao gồm Name và Position. Chúng ta xây dựng 1 builder để set thuộc tính Name. Chúng ta có đoạn chương trình như sau:

Đoạn code hoàn toàn không có gì phức tạp 🙂 Một ngày nào đó nghiệp vụ thay đổi cần set thêm thuộc tính là Position. Để đảm bảo chữ O trong SOLID nên chúng ta không modify PersonInfoBuilder mà sẽ viết thêm 1 class kế thừa lại. Class PersonJobBuilder mới sẽ như sau:

Vấn đề thực sự bắt đầu, chúng ta không thể dùng gọi  PersonJobBuilder theo kiểu Fluent như ví dụ bên trên được nữa.

builder pattern fluent builder

Lý do đơn giản ở đây là phương thức Called() đã trả về kiểu PersonInfoBuildervà nó không biết được mặt mũi phương thức WorkAsA() như thế nào.

Fluent Builder Inheritance với Recursive Generic

Để giải quyết vấn đề đó chúng ta sẽ thay đổi đoạn chương trình như sau:

Nhìn đoạn code trên có vẻ phức tạp. Bình tĩnh tự tin đừng manh động. Hãy cùng phân tích đoạn code trên. Đầu tiên là tạo một class trừu tượng cho Builder để kế thừa mở rộng builder. Tiếp theo là sửa lại PersonInfoBuilder một chút để có khả năng mở rộng bằng việc kế thừa PersonBuilder. Ở đây chúng ta có sử dụng kỹ thuật truyền vào một kiểu Generic mà kiểu Generic đó chính là bản thân cái class đó. Bạn có thể tìm hiểu thêm kỹ thuật đó bằng từ khóa Recursive GenericPersonJobBuilder kế thừa PersonInfoBuilderPersonDOBBuilder kế thừa PersonJobBuilder. tạo thành một mắt xích (chain).

Nhưng vì kỹ thuật này chúng ta sẽ không thể nào áp dụng được cách khai báo builder như sau: var personBuilder = new PersonDOBBuilder<>();Chúng ta không biết kiểu để vào PersonDOBBuilder. Đó là lý do bạn thấy ở class Person có thêm một class lồng vào là Builder kế thừa từ PersonDOBBuilder<Builder> và phương thức khởi tạo New cho Builder. Có vẻ phức tạp nhưng để hiểu rõ thì bạn cứ code ra và chạy thử để hiểu rõ hơn.

Chưa kết thúc đâu nhé. Mình còn một vài ví dụ nữa về Builder pattern nhưng có lẽ bài viết đã quá dài nên mình hẹn gặp lại các bạn vào bài viết sau. Hy vọng các bạn tiếp tục ủng hộ và đóng góp ý kiến cho mình để những bài viết của mình thật sự tốt hơn. Mọi ý kiến thắc mắc các bạn cứ comment bên dưới. Mình sẽ cùng nhau giải quyết   🙂

 

Tài liệu tham khảo:

https://en.wikipedia.org/wiki/Builder_pattern

https://stackoverflow.com/questions/26304527/recursive-generic-and-fluent-interface

https://www.sitepoint.com/self-types-with-javas-generics/

Source code trên github

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.