Học Design Pattern: Builder Pattern
Đặt vấn đề
Trong quá trình phát triển phần mềm, chúng ta thường xuyên đối mặt với việc xây dựng các đối tượng phức tạp. Các đối tượng này có thể bao gồm nhiều thành phần khác nhau, với các bước tạo ra chúng có thể khác nhau.
Hãy tưởng tượng rằng bạn đang xây dựng một tính năng để tạo ra các báo cáo khác nhau. Một báo cáo có thể bao gồm tiêu đề, nội dung văn bản, bảng biểu, biểu đồ và chân trang. Việc sắp xếp và định dạng các thành phần này có thể khác nhau tùy thuộc vào loại báo cáo (ví dụ: tài chính, hiệu suất, …).
Thách thức ở đây là làm sao để tách biệt quá trình tạo ra các thành phần của báo cáo với cách chúng được sắp xếp và trình bày. Nếu không, mã nguồn của chúng ta sẽ trở nên rối rắm, khó bảo trì và mở rộng.
Code Không Dùng Builder Pattern
data class Report(
val title: String,
val textContent: String,
val table: String?,
val chart: String?,
val footer: String?
)
class ReportGenerator {
fun generateReport(
title: String,
textContent: String,
table: String? = null,
chart: String? = null,
footer: String? = null,
reportType: String
): Report {
return Report(title, textContent, table, chart, footer)
}
}
fun main() {
val generator = ReportGenerator()
val financialReport = generator.generateReport(
title = "Báo Cáo Tài Chính Quý 3",
textContent = "Doanh thu tăng trưởng mạnh...",
table = "Bảng 1: Kết quả kinh doanh",
footer = "© 2024"
)
val performanceReport = generator.generateReport(
title = "Báo Cáo Hiệu Suất Tháng 10",
textContent = "Hiệu suất làm việc tăng 15%...",
chart = "Biểu đồ 1: Hiệu suất nhân viên",
footer = "Phòng Nhân Sự"
)
}
Ở đây, hàm generateReport
phải xử lý việc tạo ra tất cả các loại báo cáo khác nhau, dẫn đến nhiều tham số tùy chọn và logic phức tạp.
Code Dùng Builder Pattern
data class Report(
val title: String,
val textContent: String,
val table: String? = null,
val chart: String? = null,
val footer: String? = null
)
interface ReportBuilder {
fun setTitle(title: String): ReportBuilder
fun setTextContent(textContent: String): ReportBuilder
fun setTable(table: String): ReportBuilder
fun setChart(chart: String): ReportBuilder
fun setFooter(footer: String): ReportBuilder
fun build(): Report
}
class ReportBuilderImpl : ReportBuilder {
private var title: String = ""
private var textContent: String = ""
private var table: String? = null
private var chart: String? = null
private var footer: String? = null
override fun setTitle(title: String): ReportBuilder {
this.title = title
return this
}
override fun setTextContent(textContent: String): ReportBuilder {
this.textContent = textContent
return this
}
override fun setTable(table: String): ReportBuilder {
this.table = table
return this
}
override fun setChart(chart: String): ReportBuilder {
this.chart = chart
return this
}
override fun setFooter(footer: String): ReportBuilder {
this.footer = footer
return this
}
override fun build(): Report {
return Report(title, textContent, table, chart, footer)
}
}
class ReportDirector {
fun constructFinancialReport(builder: ReportBuilder) : Report {
return builder.build()
}
fun constructPerformanceReport(builder: ReportBuilder) : Report {
return builder.build()
}
}
fun main() {
val director = ReportDirector()
val financialReport = director.constructFinancialReport(ReportBuilderImpl()
.setTitle("Báo Cáo Tài Chính Quý 3")
.setTextContent("Doanh thu tăng trưởng mạnh...")
.setTable("Bảng 1: Kết quả kinh doanh")
.setFooter("© 2024"))
val performanceReport = director.constructPerformanceReport(ReportBuilderImpl()
.setTitle("Báo Cáo Hiệu Suất Tháng 10")
.setTextContent("Hiệu suất làm việc tăng 15%...")
.setChart("Biểu đồ 1: Hiệu suất nhân viên")
.setFooter("Phòng Nhân Sự"))
println(financialReport)
println(performanceReport)
}
Ở đây, chúng ta tách việc tạo báo cáo thành các builder cụ thể FinancialReport
, PerformanceReport
và một director ReportDirector
để điều phối quá trình xây dựng.
Bài Học Từ Builder Pattern
Khi thiết kế một tính năng, hãy chú ý đến những đối tượng phức tạp và cách chúng được tạo ra. Nếu quá trình tạo ra chúng có nhiều bước hoặc có thể tạo ra nhiều biến thể của đối tượng, hãy cân nhắc sử dụng Builder Pattern.
Builder Pattern giúp chúng ta:
- Tăng tính linh hoạt và khả năng mở rộng của những hàm tạo.
- Mục đích cao nhất vẫn là: Làm cho mã nguồn dễ đọc và dễ bảo trì hơn.
Chúc anh em thành công!