Menu
CoddyTech

Mẫu Template Method

Thuộc phần Lập trình hướng đối tượng trong hành trình GO của Coddy. Bài học 94 trên 107.

Mẫu Template Method định nghĩa khung của một thuật toán trong kiểu cơ sở, cho phép các kiểu con ghi đè những bước cụ thể mà không làm thay đổi cấu trúc tổng thể. Trong khi Decorator bổ sung hành vi bằng cách bọc các đối tượng, Template Method kiểm soát luồng của thuật toán và cho phép tùy chỉnh từng bước.

Trong Go, vì không có tính kế thừa, chúng ta triển khai mẫu này bằng cách kết hợp nhúng struct với interface. Struct “template” xác định cấu trúc thuật toán và gọi các phương thức có thể tùy chỉnh:

type DataProcessor interface {
    ReadData() string
    ProcessData(data string) string
    SaveData(result string) string
}

type BaseProcessor struct {
    Impl DataProcessor
}

func (b *BaseProcessor) Execute() string {
    data := b.Impl.ReadData()
    result := b.Impl.ProcessData(data)
    return b.Impl.SaveData(result)
}

Các phần triển khai cụ thể cung cấp phiên bản riêng cho từng bước, trong khi luồng thuật toán vẫn không thay đổi:

type CSVProcessor struct{}

func (c CSVProcessor) ReadData() string       { return "csv-data" }
func (c CSVProcessor) ProcessData(d string) string { return "processed-" + d }
func (c CSVProcessor) SaveData(r string) string    { return "Saved: " + r }

type JSONProcessor struct{}

func (j JSONProcessor) ReadData() string       { return "json-data" }
func (j JSONProcessor) ProcessData(d string) string { return "parsed-" + d }
func (j JSONProcessor) SaveData(r string) string    { return "Stored: " + r }

Phương thức mẫu Execute điều phối các bước theo một thứ tự cố định:

csvProc := &BaseProcessor{Impl: CSVProcessor{}}
fmt.Println(csvProc.Execute())  // Đã lưu: processed-csv-data

jsonProc := &BaseProcessor{Impl: JSONProcessor{}}
fmt.Println(jsonProc.Execute()) // Đã lưu trữ: parsed-json-data

Template Method phù hợp khi bạn có các thuật toán cùng cấu trúc nhưng khác nhau ở một số bước cụ thể, chẳng hạn như quy trình nhập/xuất dữ liệu, công cụ tạo báo cáo hoặc framework kiểm thử.

challenge icon

Thử thách

Dễ

Hãy xây dựng một hệ thống tạo báo cáo bằng mẫu Template Method! Bạn sẽ tạo một framework trong đó các loại báo cáo khác nhau tuân theo cùng một quy trình tạo báo cáo: thu thập dữ liệu, định dạng dữ liệu rồi xuất kết quả, nhưng mỗi loại báo cáo sẽ tùy chỉnh các bước này theo cách riêng.

Bạn sẽ sắp xếp code trong ba tệp:

  • report.go: Định nghĩa interface và bộ xử lý cơ sở điều phối thuật toán tạo báo cáo.

    Tạo một interface ReportGenerator với ba phương thức đại diện cho các bước tạo báo cáo:

    • GatherData() string: lấy dữ liệu thô cho báo cáo
    • FormatData(data string) string: chuyển đổi dữ liệu sang định dạng báo cáo
    • OutputReport(formatted string) string: tạo thông báo đầu ra cuối cùng

    Xây dựng một struct ReportProcessor chứa một triển khai của ReportGenerator. Thêm phương thức Generate() string để thực hiện lần lượt ba bước: thu thập, định dạng rồi xuất dữ liệu, sau đó trả về kết quả cuối cùng.

  • generators.go: Triển khai các bộ tạo báo cáo cụ thể để tùy chỉnh từng bước.

    Tạo hai loại báo cáo:

    • SalesReport có trường Region (chuỗi)
      • GatherData() trả về sales-data-[region]
      • FormatData(data) trả về SALES REPORT: [data]
      • OutputReport(formatted) trả về Printed: [formatted]
    • InventoryReport có trường Warehouse (chuỗi)
      • GatherData() trả về inventory-[warehouse]
      • FormatData(data) trả về *** [data] ***
      • OutputReport(formatted) trả về Exported: [formatted]
  • main.go: Minh họa cách cùng một cấu trúc thuật toán tạo ra các kết quả khác nhau tùy theo cách triển khai.

    Đọc loại báo cáo (sales hoặc inventory) và giá trị cấu hình (khu vực đối với báo cáo sales, kho hàng đối với báo cáo inventory). Tạo bộ tạo báo cáo phù hợp, bọc nó trong một ReportProcessor, gọi Generate() rồi in ra kết quả.

Đầu vào sẽ gồm:

  • Dòng 1: Loại báo cáo (sales hoặc inventory)
  • Dòng 2: Giá trị cấu hình (tên khu vực hoặc tên kho hàng)

Ví dụ, với đầu vào:

sales
North

Đầu ra cần là:

Printed: SALES REPORT: sales-data-North

Và với đầu vào:

inventory
MainHub

Đầu ra cần là:

Exported: *** inventory-MainHub ***

Và với đầu vào:

sales
West

Đầu ra cần là:

Printed: SALES REPORT: sales-data-West

Lưu ý rằng ReportProcessor luôn gọi cùng ba bước theo cùng một thứ tự, nhưng mỗi loại báo cáo có cách triển khai riêng cho các bước đó. Khung thuật toán luôn cố định, còn các chi tiết thì thay đổi: đó chính là mẫu Template Method!

Tự làm thử

package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	
	// Đọc loại báo cáo
	scanner.Scan()
	reportType := scanner.Text()
	
	// Đọc giá trị cấu hình (khu vực hoặc kho)
	scanner.Scan()
	configValue := scanner.Text()

	// TODO: Tạo bộ tạo phù hợp dựa trên reportType
	// - Nếu reportType là "sales", tạo SalesReport với Region được gán giá trị configValue
	// - Nếu reportType là "inventory", tạo InventoryReport với Warehouse được gán giá trị configValue
	
	// TODO: Tạo ReportProcessor với bộ tạo
	
	// TODO: Gọi Generate() và in kết quả
	_ = reportType
	_ = configValue
	fmt.Println("TODO: Generate and print the report")
}
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Trình biên dịch Go online