Mẫu Strategy
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 90 trên 107.
Mẫu Strategy pattern cho phép bạn định nghĩa một nhóm thuật toán, đóng gói từng thuật toán và thay thế chúng cho nhau khi chương trình đang chạy. Khác với Observer, nơi nhiều đối tượng phản ứng với một sự kiện, Strategy cho phép một đối tượng chuyển đổi linh hoạt giữa các hành vi khác nhau.
Trong Go, chúng ta triển khai Strategy bằng cách định nghĩa một interface cho thuật toán và đưa các cách triển khai khác nhau vào một struct ngữ cảnh:
type PaymentStrategy interface {
Pay(amount float64) string
}
type CreditCard struct{}
func (c CreditCard) Pay(amount float64) string {
return fmt.Sprintf("Paid %.2f via Credit Card", amount)
}
type PayPal struct{}
func (p PayPal) Pay(amount float64) string {
return fmt.Sprintf("Paid %.2f via PayPal", amount)
}Cấu trúc ngữ cảnh giữ tham chiếu đến giao diện chiến lược, cho phép thay đổi thuật toán mà không cần sửa code của ngữ cảnh:
type Checkout struct {
strategy PaymentStrategy
}
func (c *Checkout) SetStrategy(s PaymentStrategy) {
c.strategy = s
}
func (c *Checkout) ProcessPayment(amount float64) string {
return c.strategy.Pay(amount)
}Giờ đây, bạn có thể thay đổi phương thức thanh toán trong khi chương trình đang chạy:
checkout := &Checkout{}
checkout.SetStrategy(CreditCard{})
fmt.Println(checkout.ProcessPayment(100.00)) // Đã thanh toán 100.00 bằng thẻ tín dụng
checkout.SetStrategy(PayPal{})
fmt.Println(checkout.ProcessPayment(50.00)) // Đã thanh toán 50.00 qua PayPalStrategy phù hợp khi bạn có nhiều cách thực hiện một thao tác và cần chọn một cách dựa trên đầu vào của người dùng, cấu hình hoặc điều kiện lúc chạy. Các trường hợp thường gặp gồm thuật toán sắp xếp, phương pháp nén và quy tắc xác thực.
Thử thách
DễHãy xây dựng một hệ thống định dạng văn bản bằng mẫu Strategy! Bạn sẽ tạo nhiều chiến lược định dạng khác nhau có thể thay đổi trong lúc chạy, giúp cùng một bộ xử lý văn bản tạo ra các kiểu đầu ra khác nhau mà không cần sửa mã cốt lõi.
Bạn sẽ sắp xếp code trong ba tệp:
formatter.go: Định nghĩa giao diện chiến lược và các chiến lược định dạng cụ thể.Tạo giao diện
TextFormattervới phương thứcFormat(text string) stringđể biến đổi văn bản theo quy tắc của chiến lược.Triển khai ba chiến lược định dạng:
UppercaseFormatter: chuyển toàn bộ văn bản thành chữ hoaSnakeCaseFormatter: thay dấu cách bằng dấu gạch dưới và chuyển văn bản thành chữ thườngTitleFormatter: viết hoa chữ cái đầu tiên của mỗi từ (các từ được phân tách bằng dấu cách)
processor.go: Tạo cấu trúc ngữ cảnh sử dụng chiến lược định dạng.Xây dựng cấu trúc
TextProcessorlưu giữ một chiến lượcTextFormatter. Thêm các phương thức sau:SetFormatter(f TextFormatter)để thay đổi chiến lược định dạng đang hoạt độngProcess(text string) stringđể áp dụng bộ định dạng hiện tại cho văn bản
Đồng thời tạo hàm khởi tạo
NewTextProcessor()trả về một bộ xử lý chưa có bộ định dạng ban đầu.main.go: Minh họa cách thay đổi chiến lược trong lúc chạy.Đọc một đoạn văn bản, sau đó đọc số lượng thao tác định dạng. Với mỗi thao tác, đọc kiểu định dạng (
upper,snakehoặctitle), đặt bộ định dạng phù hợp cho bộ xử lý, xử lý văn bản ban đầu rồi in ra kết quả.
Đầu vào sẽ có dạng sau:
- Dòng 1: Văn bản cần định dạng
- Dòng 2: Số lượng thao tác định dạng
- Các dòng tiếp theo: Kiểu định dạng cho từng thao tác
Ví dụ, với đầu vào:
hello world today
3
upper
snake
titleĐầu ra cần là:
HELLO WORLD TODAY
hello_world_today
Hello World TodayVới đầu vào:
Go Programming Language
2
snake
upperĐầu ra cần là:
go_programming_language
GO PROGRAMMING LANGUAGEVới đầu vào:
design patterns are useful
1
titleĐầu ra cần là:
Design Patterns Are UsefulHãy chú ý cách cùng một TextProcessor tạo ra các đầu ra hoàn toàn khác nhau chỉ bằng cách thay đổi chiến lược định dạng. Bộ xử lý không biết và cũng không cần quan tâm mình đang dùng bộ định dạng cụ thể nào. Nó chỉ chuyển việc xử lý cho chiến lược hiện được thiết lập!
Tự làm thử
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Đọc văn bản cần định dạng
text, _ := reader.ReadString('\n')
text = strings.TrimSpace(text)
// Đọc số thao tác
countStr, _ := reader.ReadString('\n')
count, _ := strconv.Atoi(strings.TrimSpace(countStr))
// Tạo bộ xử lý văn bản mới
processor := NewTextProcessor()
// Thực hiện từng thao tác định dạng
for i := 0; i < count; i++ {
formatType, _ := reader.ReadString('\n')
formatType = strings.TrimSpace(formatType)
// TODO: Dựa vào formatType ("upper", "snake" hoặc "title"),
// thiết lập bộ định dạng phù hợp cho bộ xử lý
// và in ra văn bản đã xử lý
}
}
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ệp3Con 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