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!