Menu
CoddyTech

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 PayPal

Strategy 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.

challenge icon

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 TextFormatter với phương thức Format(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ữ hoa
    • SnakeCaseFormatter: thay dấu cách bằng dấu gạch dưới và chuyển văn bản thành chữ thường
    • TitleFormatter: 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 TextProcessor lưu giữ một chiến lược TextFormatter. Thêm các phương thức sau:

    • SetFormatter(f TextFormatter) để thay đổi chiến lược định dạng đang hoạt động
    • Process(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, snake hoặc title), đặ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 Today

Với đầu vào:

Go Programming Language
2
snake
upper

Đầu ra cần là:

go_programming_language
GO PROGRAMMING LANGUAGE

Với đầu vào:

design patterns are useful
1
title

Đầu ra cần là:

Design Patterns Are Useful

Hã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ý
		
	}
}
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