Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 101 additions & 1 deletion content/chapter 9/behavioral patterns/9.3.6-observer.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,104 @@ title: '9.3.6 الگو Observer'
slug: go-observer-pattern
weight: 176006
---
الگو Observer...

## 9.3.6.1 - توضیح الگوی Observer

الگوی Observer یک الگوی طراحی رفتاری است که رابطه‌ی یک-به-چند بین اشیا را تعریف می‌کند، به طوری که وقتی یک شیء (سوژه) حالت خود را تغییر می‌دهد، تمام اشیای وابسته به آن (ناظرها) به طور خودکار مطلع و به روز می‌شوند. این الگو برای ایجاد سیستم‌های event-driven ایده‌آل است و وابستگی قوی بین سوژه و ناظرها را از بین می‌برد، زیرا سوژه نیازی ندارد بداند ناظرهایش چه اشیایی هستند و فقط با آن‌ها از طریق یک رابط مشترک تعامل می‌کند. این الگو در موقعیت‌هایی مانند سیستم‌های اطلاع‌رسانی، رابط‌های کاربری reactive و رویدادهای real-time کاربرد گسترده‌ای دارد.

## 9.3.6.2 - مثال عملی

{{< play >}}
package main

import "fmt"

// Observer interface
type Observer interface {
Update(temperature float64)
}

// Subject interface
type Subject interface {
RegisterObserver(o Observer)
RemoveObserver(o Observer)
NotifyObservers()
}

// Concrete Subject - WeatherStation
type WeatherStation struct {
temperature float64
observers []Observer
}

func (w *WeatherStation) RegisterObserver(o Observer) {
w.observers = append(w.observers, o)
}

func (w *WeatherStation) RemoveObserver(o Observer) {
for i, observer := range w.observers {
if observer == o {
w.observers = append(w.observers[:i], w.observers[i+1:]...)
break
}
}
}

func (w *WeatherStation) NotifyObservers() {
for _, observer := range w.observers {
observer.Update(w.temperature)
}
}

func (w *WeatherStation) SetTemperature(temp float64) {
w.temperature = temp
w.NotifyObservers() // Notify all observers when temperature changes
}

// Concrete Observer - PhoneDisplay
type PhoneDisplay struct {
id string
}

func (p *PhoneDisplay) Update(temperature float64) {
fmt.Printf("Phone Display %s: Temperature is %.1f°C\n", p.id, temperature)
}

// Concrete Observer - TVDisplay
type TVDisplay struct {
channel string
}

func (t *TVDisplay) Update(temperature float64) {
fmt.Printf("TV Display on %s: Current temperature: %.1f°C\n", t.channel, temperature)
}

func main() {
// Create the subject (weather station)
weatherStation := &WeatherStation{}

// Create observers
phone1 := &PhoneDisplay{id: "1"}
phone2 := &PhoneDisplay{id: "2"}
tv := &TVDisplay{channel: "Weather Channel"}

// Register observers
weatherStation.RegisterObserver(phone1)
weatherStation.RegisterObserver(phone2)
weatherStation.RegisterObserver(tv)

// Change temperature - all observers get notified
fmt.Println("Setting temperature to 25.5°C:")
weatherStation.SetTemperature(25.5)

fmt.Println("\nSetting temperature to 30.2°C:")
weatherStation.SetTemperature(30.2)

// Remove one observer
fmt.Println("\nRemoving Phone Display 2 and setting temperature to 28.0°C:")
weatherStation.RemoveObserver(phone2)
weatherStation.SetTemperature(28.0)
}
{{< /play >}}

در این پیاده‌سازی الگوی Observer، دو رابط اصلی Observer و Subject تعریف شده‌اند. رابط Observer تنها متد Update را دارد که برای دریافت به‌روزرسانی‌ها از سوژه استفاده می‌شود. رابط Subject نیز متدهای RegisterObserver، RemoveObserver و NotifyObservers را برای مدیریت ناظرها تعریف می‌کند. کلاس WeatherStation به عنوان سوژهٔ بتن، این رابط را پیاده‌سازی کرده و دارای یک لیست از ناظرها است. وقتی دمای ایستگاه هواشناسی با متد SetTemperature تغییر می‌کند، متد NotifyObservers فراخوانی شده و تمام ناظرهای ثبت‌شده را با مقدار جدید دما به روز می‌کند. دو ناظر بتن PhoneDisplay و TVDisplay نیز رابط Observer را پیاده‌سازی کرده و هر کدام رفتار خاص خود را در متد Update تعریف می‌کنند. در تابع main، با ایجاد یک ایستگاه هواشناسی و چندین ناظر، نحوهٔ ثبت، حذف و اطلاع‌رسانی به ناظرها نمایش داده شده است.
149 changes: 148 additions & 1 deletion content/chapter 9/behavioral patterns/9.3.8-strategy.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,151 @@ title: '9.3.8 الگو Strategy'
slug: go-strategy-pattern
weight: 176008
---
الگو Strategy...

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

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

## 9.3.8.2 - مثال عملی

{{< play >}}
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("user@example.com")
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))
}
{{< /play >}}

در این پیاده‌سازی الگوی Strategy، رابط PaymentStrategy به عنوان استراتژی پایه تعریف شده که متد Pay را شامل می‌شود. چهار استراتژی بتن CreditCardPayment، PayPalPayment، CryptoPayment و BankTransferPayment این رابط را پیاده‌سازی کرده و هر کدام منطق پرداخت خاص خود را ارائه می‌دهند. کلاس PaymentProcessor به عنوان Context عمل می‌کند که یک استراتژی پرداخت را نگهداری کرده و با متد SetStrategy امکان تغییر استراتژی در زمان اجرا را فراهم می‌کند. متد ProcessPayment در Context، کار را به استراتژی فعلی واگذار می‌کند. همچنین یک تابع ساده به نام ProcessPayment به عنوان رویکرد جایگزین پیاده‌سازی شده است. در تابع main، استفاده از هر دو رویکرد (Context و تابع مستقیم) نمایش داده شده و نحوه تغییر پویای استراتژی‌ها در زمان اجرا نشان داده شده است. همچنین سناریویی برای پرداخت‌های متعدد با استراتژی‌های مختلف و مدیریت خطا برای زمانی که استراتژی تنظیم نشده، پیاده‌سازی شده است.
120 changes: 119 additions & 1 deletion content/chapter 9/creational patterns/9.1.2-factory-method.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,122 @@ slug: go-factory-method-pattern
weight: 172002
---

الگو Factory Method...
## 9.1.2.1 - توضیح الگوی Factory Method

الگوی Factory یک الگوی طراحی خلاقانه است که یک رابط برای ایجاد اشیا در یک سوپرکلاس فراهم می‌کند، اما اجازه می‌دهد زیرکلاس‌ها نوع شیءی که ساخته می‌شود را تغییر دهند. این الگو با جدا کردن منطق ساخت اشیا از کلاینت‌هایی که از آن اشیا استفاده می‌کنند، وابستگی مستقیم بین کلاینت و کلاس‌های بتن را از بین می‌برد. الگوی Factory زمانی مفید است که یک کلاس نتواند پیش‌بینی کند چه نوع کلاسی باید ایجاد کند، یا زمانی که یک کلاس بخواهد مسئولیت ایجاد اشیا را به زیرکلاس‌ها محول کند. این الگو با کپسوله‌سازی منطق ایجاد، نگهداری کد را آسان‌تر کرده و اصل "Open/Closed" را رعایت می‌کند.

## 9.1.2.2 - مثال عملی

{{< play >}}
package main

import "fmt"

// Product Interface
type Vehicle interface {
Drive() string
GetType() string
}

// Concrete Products
type Car struct{}

func (c *Car) Drive() string {
return "Driving a car on the road"
}

func (c *Car) GetType() string {
return "Car"
}

type Motorcycle struct{}

func (m *Motorcycle) Drive() string {
return "Riding a motorcycle on the road"
}

func (m *Motorcycle) GetType() string {
return "Motorcycle"
}

type Truck struct{}

func (t *Truck) Drive() string {
return "Driving a truck carrying heavy load"
}

func (t *Truck) GetType() string {
return "Truck"
}

// Factory
type VehicleFactory struct{}

func (vf *VehicleFactory) CreateVehicle(vehicleType string) (Vehicle, error) {
switch vehicleType {
case "car":
return &Car{}, nil
case "motorcycle":
return &Motorcycle{}, nil
case "truck":
return &Truck{}, nil
default:
return nil, fmt.Errorf("unknown vehicle type: %s", vehicleType)
}
}

// Alternatively, we can use factory functions instead of a struct
func CreateVehicle(vehicleType string) (Vehicle, error) {
switch vehicleType {
case "car":
return &Car{}, nil
case "motorcycle":
return &Motorcycle{}, nil
case "truck":
return &Truck{}, nil
default:
return nil, fmt.Errorf("unknown vehicle type: %s", vehicleType)
}
}

func main() {
// Using the factory struct
factory := &VehicleFactory{}

fmt.Println("Using VehicleFactory struct:")
car, _ := factory.CreateVehicle("car")
fmt.Printf("%s: %s\n", car.GetType(), car.Drive())

motorcycle, _ := factory.CreateVehicle("motorcycle")
fmt.Printf("%s: %s\n", motorcycle.GetType(), motorcycle.Drive())

truck, _ := factory.CreateVehicle("truck")
fmt.Printf("%s: %s\n", truck.GetType(), truck.Drive())

// Using the factory function
fmt.Println("\nUsing factory function:")
vehicle1, _ := CreateVehicle("car")
fmt.Printf("%s: %s\n", vehicle1.GetType(), vehicle1.Drive())

vehicle2, _ := CreateVehicle("motorcycle")
fmt.Printf("%s: %s\n", vehicle2.GetType(), vehicle2.Drive())

// Error handling
fmt.Println("\nTesting error handling:")
unknown, err := CreateVehicle("bicycle")
if err != nil {
fmt.Printf("Error: %s\n", err)
} else {
fmt.Printf("%s: %s\n", unknown.GetType(), unknown.Drive())
}

// Demonstrating polymorphism
fmt.Println("\nDemonstrating polymorphism:")
vehicles := []Vehicle{car, motorcycle, truck}
for _, vehicle := range vehicles {
fmt.Printf("- %s\n", vehicle.Drive())
}
}
{{< /play >}}

در این پیاده‌سازی الگوی Factory، رابط Vehicle به عنوان محصول پایه تعریف شده که متدهای Drive و GetType را شامل می‌شود. سه کلاس بتن Car، Motorcycle و Truck این رابط را پیاده‌سازی کرده و هر کدام رفتار خاص خود را ارائه می‌دهند. کلاس VehicleFactory به عنوان فکتوری عمل می‌کند که با متد CreateVehicle بر اساس نوع وسیله نقلیه درخواستی، نمونه مناسب را ایجاد و بازمی‌گرداند. همچنین یک تابع فکتوری مستقل به نام CreateVehicle نیز پیاده‌سازی شده که همان عملکرد را بدون نیاز به ایجاد نمونه از فکتوری ارائه می‌دهد. در تابع main، استفاده از هر دو روش فکتوری (هم از طریق ساختار و هم تابع) نمایش داده شده و نحوه ایجاد انواع مختلف وسایل نقلیه بدون وابستگی مستقیم به کلاس‌های بتن نشان داده شده است. همچنین مدیریت خطا برای نوع‌های ناشناخته و نمایش چندشکلی (polymorphism) با استفاده از آرایه‌ای از رابط Vehicle نیز پیاده‌سازی شده است.
Loading