الگوهای طراحی، راهحلهای تکرارشونده برای مشکلات رایج در طراحی نرمافزار هستند. گروه چهار نفره (GoF) این الگوها را به سه دسته اصلی تقسیم میکند: ساختاری (Creational)، ترکیبی (Structural) و رفتاری (Behavioral). در این مقاله، پیادهسازیهای دنیای واقعی با استفاده از Go را با تمرکز بر شفافیت، سادگی و کد بومی (idiomatic) بررسی میکنیم. چه در حال ساخت میکروسرویسها باشید و چه مونولیتهای پیچیده، درک این الگوها به شما کمک میکند تا کدی قابل نگهداری، مقیاسپذیر و قابل تست بنویسید.
الگوهای ساختاری (Creational): ایجاد اشیاء
الگوهای ساختاری با مکانیزمهای ایجاد اشیاء سروکار دارند و سعی میکنند اشیاء را به شکلی مناسب با شرایط ایجاد کنند. رایجترین آنها روش کارخانه (Factory Method) است.
روش کارخانه در Go
در Go، روشهای کارخانه مبتنی بر رابط (interface) بومی هستند. فرض کنید یک سیستم پرداخت دارید که پردازندههای متعددی را پشتیبانی میکند:
type PaymentProcessor interface {
Process(amount float64) error
}
type StripeProcessor struct{}
func (s StripeProcessor) Process(amount float64) error {
fmt.Printf("Processing $%.2f via Stripe\n", amount)
return nil
}
type PayPalProcessor struct{}
func (p PayPalProcessor) Process(amount float64) error {
fmt.Printf("Processing $%.2f via PayPal\n", amount)
return nil
}
func NewPaymentProcessor(provider string) PaymentProcessor {
switch provider {
case "stripe":
return StripeProcessor{}
case "paypal":
return PayPalProcessor{}
default:
return nil
}
}
این روش، کد کلاینت را از پیادهسازیهای عینی (concrete) جدا میکند. شما میتوانید به راحتی روشهای پرداخت جدید را بدون تغییر در کد موجود اضافه کنید، که این امر با اصل باز/بسته (Open/Closed Principle) همخوانی دارد.
الگوهای ترکیبی (Structural): ترکیب اشیاء
الگوهای ترکیبی با نحوه ترکیب کلاسها و اشیاء برای تشکیل ساختارهای بزرگتر سروکار دارند. الگوی تطبیقدهنده (Adapter Pattern) برای یکپارچهسازی رابطهای ناسازگار حیاتی است.
الگوی تطبیقدهنده در Go
فرض کنید یک رابط لاگنویسی قدیمی (legacy) دارید که با لاگر برنامه مدرن شما متفاوت است:
type LegacyLogger interface {
Log(msg string)
}
type AppLogger interface {
Info(msg string)
Error(msg string)
}
type LoggerAdapter struct {
legacy LegacyLogger
}
func (a LoggerAdapter) Info(msg string) {
a.legacy.Log("[INFO] " + msg)
}
func (a LoggerAdapter) Error(msg string) {
a.legacy.Log("[ERROR] " + msg)
}
func NewLoggerAdapter(legacy LegacyLogger) AppLogger {
return LoggerAdapter{legacy: legacy}
}
این تطبیقدهنده به برنامه مدرن شما اجازه میدهد تا از لاگر قدیمی بدون تغییر در رابط آن استفاده کند، که این امر تکرار کد و بازسازی تدریجی (refactoring) را ترویج میدهد.
الگوهای رفتاری (Behavioral): تعامل اشیاء
الگوهای رفتاری به الگوریتمها و تخصیص مسئولیتها بین اشیاء مربوط میشوند. الگوی ناظر (Observer Pattern) برای معماریهای رویدادمحور حیاتی است.
الگوی ناظر در Go
کانالهای Go پیادهسازی طبیعی الگوی ناظر را برای ارتباط غیرهمزمان فراهم میکنند:
type Event struct {
ID int
Data string
}
type EventHandler func(Event)
type EventBus struct {
handlers map[string][]EventHandler
}
func NewEventBus() *EventBus {
return &EventBus{handlers: make(map[string][]EventHandler)}
}
func (eb *EventBus) Subscribe(eventType string, handler EventHandler) {
eb.handlers[eventType] = append(eb.handlers[eventType], handler)
}
func (eb *EventBus) Publish(event Event) {
for _, handler := range eb.handlers["order"] {
go handler(event)
}
}
func main() {
bus := NewEventBus()
bus.Subscribe("order", func(e Event) {
fmt.Printf("Email sent for order %d\n", e.ID)
})
bus.Subscribe("order", func(e Event) {
fmt.Printf("Notification sent for order %d\n", e.ID)
})
bus.Publish(Event{ID: 1, Data: "New Order"})
}
این جفتشدگی ضعیف (loose coupling) به چندین مشترک اجازه میدهد تا بدون آگاهی از وجود یکدیگر به رویدادها واکنش نشان دهند، که این امر برای میکروسرویسها و سیستمهای رویدادمحور ایدهآل است.
نتیجهگیری
الگوهای طراحی راهحلهای یکسایز-برای-همه نیستند، بلکه قالبهای اثباتشدهای هستند که چالشهای معماری رایج را برطرف میکنند. الگوهای ساختاری ایجاد اشیاء را ساده میکنند، الگوهای ترکیبی روابط بین اشیاء را مدیریت میکنند و الگوهای رفتاری ارتباطات را مدیریت میکنند. در Go، از نقاط قوت زبان مانند رابطها و کانالها برای پیادهسازی بومی این الگوها استفاده کنید. به یاد داشته باشید: الگوهایی را انتخاب کنید که مشکل خاص شما را حل میکنند و آنها را در جاهایی که مناسب نیستند تحمیل نکنید. تسلط بر این الگوها کد شما را از حالت کاربردی به سطح مقاوم و قابل نگهداری ارتقا میدهد.