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-dataTemplate 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ử.
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
ReportGeneratorvớ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áoFormatData(data string) string: chuyển đổi dữ liệu sang định dạng báo cáoOutputReport(formatted string) string: tạo thông báo đầu ra cuối cùng
Xây dựng một struct
ReportProcessorchứa một triển khai củaReportGenerator. Thêm phương thứcGenerate() 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:
SalesReportcó trườngRegion(chuỗi)GatherData()trả vềsales-data-[region]FormatData(data)trả vềSALES REPORT: [data]OutputReport(formatted)trả vềPrinted: [formatted]
InventoryReportcó trườngWarehouse(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 (
saleshoặcinventory) 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ộtReportProcessor, gọiGenerate()rồi in ra kết quả.
Đầu vào sẽ gồm:
- Dòng 1: Loại báo cáo (
saleshoặcinventory) - 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-NorthVà 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-WestLư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")
}
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
1Nền tảng OOP trong Go
Tệp bên ngoàiWorkspace và module trong GoPackage và importTên được export và không được exportGiới thiệu về OOP trong GoStruct như lớpĐịnh nghĩa phương thức cho structReceiver kiểu con trỏ và kiểu giá trịKhởi tạo structHàm khởi tạoÔn tập: máy tính đơn giản4Interface
Giới thiệu về interfaceTriển khai ngầm địnhInterface như một hợp đồngInterface rỗng (any)Khẳng định kiểuswitch kiểuKết hợp interfaceInterface Stringer và ErrorÔn tập: Máy tính hình học7Tính đóng gói
Trường được xuất và không được xuấtĐóng gói ở cấp packagePhương thức getter và setterẨn thông tin trong GoÔn tập: hồ sơ học sinh10Generics (Go 1.18 trở lên)
Giới thiệu về GenericsTham số kiểuRàng buộc kiểuStruct tổng quátGiải pháp thay thế cho phương thức tổng quátÔn tập: tập hợp tổng quát13Các mẫu thiết kế phần 1
Giới thiệu về mẫu thiết kếMẫu SingletonMẫu FactoryMẫu Abstract FactoryMẫu ObserverMẫu Strategy2Tìm hiểu chuyên sâu về kiểu dữ liệu và struct
Kiểu cơ bản và kiểu tổng hợpĐịnh nghĩa kiểu dữ liệu tùy chỉnhThẻ structStruct ẩn danhStruct lồng nhauGiá trị zero và giá trị mặc địnhÔn tập: sổ liên lạc5Ưu tiên kết hợp hơn kế thừa
Vì sao Go không có kế thừaNhập môn nhúng structTự động nâng phương thứcNhúng nhiều structNhúng và kết tậpChe khuất phương thức được nhúngÔn tập: hệ phân cấp nhân viên8Xử lý lỗi và lập trình hướng đối tượng
Giao diện errorCác kiểu lỗi tùy chỉnhBọc lỗi với fmt.ErrorfLỗi sentinelerrors.Is() và errors.As()Panic, defer và recoverÔn tập: trình phân tích tệp11Thư viện chuẩn và OOP
io.Reader và io.Writersort.Interfaceinterface fmt.Stringerencoding/json với structinterface http.HandlerÔn tập: mô hình REST API14Mẫu thiết kế phần 2
Mẫu CommandMẫu AdapterMẫu DecoratorMẫu Template MethodMẫu StateMẫu CompositeMiddleware dưới dạng Decorator3Con trỏ và bộ nhớ
Cơ bản về con trỏ trong GoCon trỏ đến structTruyền theo giá trị và tham chiếuHàm new()Thu gom rác trong GoÔn tập: xây dựng danh sách liên kết6Đa hình trong Go
Đa hình qua interfaceĐịnh kiểu vịt trong GoQuy tắc thỏa mãn interfaceCác tập hợp đa hìnhTiêm phụ thuộcÔn tập: bộ xử lý thanh toán9Lập trình đồng thời và lập trình hướng đối tượng
Cơ bản về goroutineChannel và giao tiếpChannel có bộ đệm và không có bộ đệmCâu lệnh selectsync.Mutex và sync.RWMutexsync.WaitGroupThiết kế struct an toàn luồngÔn tập: worker poolTự luyện tập: Trình biên dịch Go online