9.3.8 الگو Strategy

9.3.8 الگو Strategy

9.3.8.1 - توضیح الگوی Strategy #

الگوی Strategy یک الگوی طراحی رفتاری است که به شما امکان می‌دهد یک خانواده از الگوریتم‌ها را تعریف کرده، هر یک را در یک کلاس جداگانه کپسوله کنید و آن‌ها را قابل تعویض کنید. این الگو اجازه می‌دهد الگوریتم‌ها مستقل از کلاینت‌هایی که از آن‌ها استفاده می‌کنند، تغییر کنند. با استفاده از این الگو، می‌توانید رفتار یک کلاس را در زمان اجرا بدون تغییر ساختار آن کلاس تغییر دهید. Strategy وابستگی بین کلاینت و الگوریتم‌ها را از بین برده و اصل “Open/Closed” را رعایت می‌کند، به طوری که می‌توانید استراتژی‌های جدیدی اضافه کنید بدون اینکه کد موجود را تغییر دهید.

9.3.8.2 - مثال عملی #

package main

import "fmt"

// Strategy Interface
type PaymentStrategy interface {
    Pay(amount float64) string
}

// Concrete Strategies
type CreditCardPayment struct {
    cardNumber string
    cvv        string
}

func NewCreditCardPayment(cardNumber, cvv string) *CreditCardPayment {
    return &CreditCardPayment{
        cardNumber: cardNumber,
        cvv:        cvv,
    }
}

func (c *CreditCardPayment) Pay(amount float64) string {
    return fmt.Sprintf("Paid $%.2f using Credit Card ending with %s", amount, c.cardNumber[len(c.cardNumber)-4:])
}

type PayPalPayment struct {
    email string
}

func NewPayPalPayment(email string) *PayPalPayment {
    return &PayPalPayment{email: email}
}

func (p *PayPalPayment) Pay(amount float64) string {
    return fmt.Sprintf("Paid $%.2f using PayPal account %s", amount, p.email)
}

type CryptoPayment struct {
    walletAddress string
}

func NewCryptoPayment(walletAddress string) *CryptoPayment {
    return &CryptoPayment{walletAddress: walletAddress}
}

func (c *CryptoPayment) Pay(amount float64) string {
    return fmt.Sprintf("Paid $%.2f using Crypto wallet %s", amount, c.walletAddress[:8]+"...")
}

type BankTransferPayment struct {
    accountNumber string
}

func NewBankTransferPayment(accountNumber string) *BankTransferPayment {
    return &BankTransferPayment{accountNumber: accountNumber}
}

func (b *BankTransferPayment) Pay(amount float64) string {
    return fmt.Sprintf("Paid $%.2f using Bank Transfer to account %s", amount, b.accountNumber)
}

// Context
type PaymentProcessor struct {
    strategy PaymentStrategy
}

func NewPaymentProcessor() *PaymentProcessor {
    return &PaymentProcessor{}
}

func (p *PaymentProcessor) SetStrategy(strategy PaymentStrategy) {
    p.strategy = strategy
}

func (p *PaymentProcessor) ProcessPayment(amount float64) string {
    if p.strategy == nil {
        return "Error: No payment strategy set"
    }
    return p.strategy.Pay(amount)
}

// Alternative simpler approach using function
func ProcessPayment(strategy PaymentStrategy, amount float64) string {
    return strategy.Pay(amount)
}

func main() {
    // Using the Context approach
    processor := NewPaymentProcessor()
    
    fmt.Println("=== Using PaymentProcessor Context ===")
    
    // Credit Card payment
    creditCard := NewCreditCardPayment("4111111111111111", "123")
    processor.SetStrategy(creditCard)
    fmt.Println(processor.ProcessPayment(99.99))
    
    // PayPal payment
    paypal := NewPayPalPayment("[email protected]")
    processor.SetStrategy(paypal)
    fmt.Println(processor.ProcessPayment(49.50))
    
    // Crypto payment
    crypto := NewCryptoPayment("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa")
    processor.SetStrategy(crypto)
    fmt.Println(processor.ProcessPayment(150.00))
    
    fmt.Println("\n=== Using Direct Function Approach ===")
    
    // Using the function approach
    bankTransfer := NewBankTransferPayment("123456789")
    fmt.Println(ProcessPayment(bankTransfer, 75.25))
    fmt.Println(ProcessPayment(creditCard, 29.99))
    
    fmt.Println("\n=== Dynamic Strategy Switching ===")
    
    // Demonstrating runtime strategy changes
    shoppingCart := []float64{25.00, 60.00, 15.50}
    
    // Process with different strategies
    strategies := []PaymentStrategy{
        creditCard,
        paypal,
        crypto,
    }
    
    for i, amount := range shoppingCart {
        strategy := strategies[i%len(strategies)]
        processor.SetStrategy(strategy)
        fmt.Printf("Item $%.2f: %s\n", amount, processor.ProcessPayment(amount))
    }
    
    fmt.Println("\n=== Error Handling ===")
    emptyProcessor := NewPaymentProcessor()
    fmt.Println(emptyProcessor.ProcessPayment(100.00))
}

در این پیاده‌سازی الگوی Strategy، رابط PaymentStrategy به عنوان استراتژی پایه تعریف شده که متد Pay را شامل می‌شود. چهار استراتژی بتن CreditCardPayment، PayPalPayment، CryptoPayment و BankTransferPayment این رابط را پیاده‌سازی کرده و هر کدام منطق پرداخت خاص خود را ارائه می‌دهند. کلاس PaymentProcessor به عنوان Context عمل می‌کند که یک استراتژی پرداخت را نگهداری کرده و با متد SetStrategy امکان تغییر استراتژی در زمان اجرا را فراهم می‌کند. متد ProcessPayment در Context، کار را به استراتژی فعلی واگذار می‌کند. همچنین یک تابع ساده به نام ProcessPayment به عنوان رویکرد جایگزین پیاده‌سازی شده است. در تابع main، استفاده از هر دو رویکرد (Context و تابع مستقیم) نمایش داده شده و نحوه تغییر پویای استراتژی‌ها در زمان اجرا نشان داده شده است. همچنین سناریویی برای پرداخت‌های متعدد با استراتژی‌های مختلف و مدیریت خطا برای زمانی که استراتژی تنظیم نشده، پیاده‌سازی شده است.