مقدمة في أنماط التصميم
جزء من قسم Object Oriented Programming في رحلة GO على Coddy — الدرس 85 من 107.
أنماط التصميم هي حلول قابلة لإعادة الاستخدام لمشكلات شائعة تظهر في تصميم البرمجيات. وهي ليست أكواداً برمجية تنسخها مباشرة، بل هي قوالب توجهك في كيفية هيكلة الأنواع الخاصة بك وتفاعلاتها لحل تحديات محددة.
عادةً ما يتم تصنيف أنماط التصميم إلى ثلاث فئات:
| الفئة | الغرض | الأمثلة |
|---|---|---|
| الإنشائية (Creational) | آليات إنشاء الكائنات | Singleton, Factory, Builder |
| الهيكلية (Structural) | تكوين الأنواع في هياكل أكبر | Adapter, Decorator, Composite |
| السلوكية (Behavioral) | التواصل بين الكائنات | Observer, Strategy, Command |
في لغة Go، تبدو أنماط التصميم مختلفة عما هي عليه في لغات البرمجة كائنية التوجه (OOP) التقليدية.
بدون الأصناف (classes) أو الوراثة (inheritance)، تقوم لغة Go بتنفيذ الأنماط باستخدام الواجهات (interfaces)، وتضمين الهياكل (struct embedding)، والتركيب (composition). على سبيل المثال، حيث قد تستخدم Java صنفاً مجرداً (abstract class)، تستخدم Go واجهة (interface). وحيث قد تستخدم C++ الوراثة (inheritance)، تستخدم Go التضمين (embedding).
يساعدك تعلم الأنماط على التعرف على المشكلات الشائعة وتوصيل الحلول بفعالية مع المطورين الآخرين. فعندما يذكر شخص ما "استخدم نمط Strategy هنا"، يفهم الفريق بأكمله النهج المتبع دون الحاجة إلى تفسيرات مطولة.
خلال الدروس القادمة، ستقوم بتنفيذ العديد من الأنماط الأساسية في Go، بدءاً بالأنماط الإنشائية مثل Singleton و Factory، ثم الانتقال إلى الأنماط السلوكية مثل Observer و Strategy.
التحدي
سهللنقم ببناء نظام إشعارات بسيط يوضح كيف تساعد أنماط التصميم في تنظيم الكود! ستقوم بإنشاء هيكل يمكن توسيعه بسهولة باستخدام أنماط مثل Observer أو Strategy في الدروس المستقبلية — ولكن في الوقت الحالي، ركز على إعداد واجهات (interfaces) وأنواع نظيفة تتبع مبادئ البرمجة كائنية التوجه (OOP) الجيدة.
ستقوم بتنظيم الكود الخاص بك عبر ثلاثة ملفات:
notifier.go: حدد الواجهة الأساسية والأنواع لنظام الإشعارات الخاص بك.قم بإنشاء واجهة
Notifierبطريقة واحدةSend(message string) stringتعيد رسالة تأكيد.قم بتنفيذ نوعين ملموسين من الإشعارات:
EmailNotifierمع حقلAddress— تعيد طريقةSendالخاصة بهEmail to [address]: [message]SMSNotifierمع حقلPhoneNumber— تعيد طريقةSendالخاصة بهSMS to [phone]: [message]
dispatcher.go: قم بإنشاء مرسل (dispatcher) يعمل مع أي إشعار من خلال الواجهة.قم ببناء هيكل
NotificationDispatcherيحتوي على شريحة (slice) من واجهاتNotifier. أضف هذه الطرق:AddNotifier(n Notifier)لتسجيل إشعارBroadcast(message string) []stringالتي ترسل الرسالة عبر جميع الإشعارات المسجلة وتعيد شريحة من جميع سلاسل التأكيد
قم أيضًا بإنشاء منشئ
NewNotificationDispatcher()يعيد مرسلاً مهيأً.main.go: قم بإعداد الإشعارات وبث الرسائل.اقرأ عدد الإشعارات المراد إنشاؤها. لكل إشعار، اقرأ نوعه (
emailأوsms) ووجهته (العنوان أو رقم الهاتف). أضف كل منها إلى المرسل.ثم اقرأ رسالة لبثها. أرسلها عبر جميع الإشعارات واطبع كل تأكيد في سطر خاص به.
سيتم توفير المدخلات التالية:
- السطر 1: عدد الإشعارات
- لكل إشعار: النوع (
emailأوsms)، ثم الوجهة - السطر الأخير: الرسالة المراد بثها
على سبيل المثال، إذا كان المعطى:
3
email
alice@example.com
sms
555-1234
email
bob@example.com
Server maintenance at midnightيجب أن يكون مخرجك:
Email to alice@example.com: Server maintenance at midnight
SMS to 555-1234: Server maintenance at midnight
Email to bob@example.com: Server maintenance at midnightوإذا كان المعطى:
2
sms
555-9999
sms
555-0000
Meeting canceledيجب أن يكون مخرجك:
SMS to 555-9999: Meeting canceled
SMS to 555-0000: Meeting canceledيوضح هذا الهيكل كيف تمكن الواجهات من إنشاء تصميمات مرنة وقابلة للتوسع. لا يعرف المرسل (dispatcher) أنواع الإشعارات المحددة — هو يعرف فقط أنها تستطيع تنفيذ Send. هذا هو الأساس الذي تبنى عليه أنماط مثل Observer و Strategy!
ورقة مرجعية
أنماط التصميم (Design patterns) هي حلول قابلة لإعادة الاستخدام لمشاكل شائعة في تصميم البرمجيات. وهي عبارة عن قوالب توجه كيفية هيكلة الأنواع وتفاعلاتها.
تُصنف أنماط التصميم إلى ثلاث فئات:
| الفئة | الغرض | أمثلة |
|---|---|---|
| إنشائية (Creational) | آليات إنشاء الكائنات | Singleton, Factory, Builder |
| هيكلية (Structural) | تركيب الأنواع في هياكل أكبر | Adapter, Decorator, Composite |
| سلوكية (Behavioral) | التواصل بين الكائنات | Observer, Strategy, Command |
في لغة Go، يتم تنفيذ أنماط التصميم باستخدام interfaces و struct embedding و composition بدلاً من classes و inheritance. وحيث تستخدم لغات OOP التقليدية abstract classes، تستخدم Go الـ interfaces. وحيث تستخدم الوراثة، تستخدم Go الـ embedding.
جرّب بنفسك
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// قراءة عدد الـ notifiers
countStr, _ := reader.ReadString('\n')
count, _ := strconv.Atoi(strings.TrimSpace(countStr))
// إنشاء dispatcher جديد
dispatcher := NewNotificationDispatcher()
// قراءة كل notifier وإضافته إلى الـ dispatcher
for i := 0; i < count; i++ {
notifierType, _ := reader.ReadString('\n')
notifierType = strings.TrimSpace(notifierType)
destination, _ := reader.ReadString('\n')
destination = strings.TrimSpace(destination)
// TODO: إنشاء الـ notifier المناسب بناءً على النوع
// وإضافته إلى الـ dispatcher
// Hint: تحقق مما إذا كان notifierType هو "email" أو "sms"
}
// قراءة الرسالة المراد بثها
message, _ := reader.ReadString('\n')
message = strings.TrimSpace(message)
// TODO: بث الرسالة وطباعة كل تأكيد
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات الـ OOP في Go
الملفات الخارجيةمساحة العمل والـ Modules في Goالـ Packages والـ Importsالأسماء المصدرة مقابل غير المصدرةمقدمة إلى الـ OOP في Goالـ Structs كـ Classesتعريف الـ Methods في الـ Structsالـ Pointer Receivers مقابل الـ Value Receiversتهيئة الـ Structدوال الـ Constructorمراجعة - آلة حاسبة بسيطة4الواجهات
مقدمة في الواجهاتالتنفيذ الضمنيالواجهة كعقدالواجهة الفارغة (any)تأكيد النوع (Type Assertion)تبديل النوع (Type Switch)تركيب الواجهاتواجهات Stringer و Errorمراجعة - حاسبة الأشكال7التغليف
الحقول المصدرة وغير المصدرةالتغليف على مستوى الحزمةدوال الـ Getter والـ Setterإخفاء المعلومات في Goملخص - سجلات الطلاب10الأنواع العامة (Go 1.18+)
مقدمة في الأنواع العامةمعاملات الأنواعقيود الأنواعالـ Structs العامةحل بديل للـ Methods العامةملخص - المجموعات العامة13أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factoryنمط Abstract Factoryنمط Observerنمط Strategy2تعمق في الأنواع و Structs
الأنواع الأساسية والمركبةتعريفات الأنواع المخصصةStruct TagsStructs مجهولةStructs متداخلةالقيم الصفرية والافتراضيةمراجعة - دفتر العناوين5التركيب بدلاً من الوراثة
لماذا لا تدعم Go الوراثةأساسيات تضمين الـ Structترقية الـ Methodتضمين عدة Structsالتضمين مقابل التجميعحجب الـ Methods المضمنةملخص - الهيكل الهرمي للموظفين8معالجة الأخطاء و OOP
واجهة errorأنواع الأخطاء المخصصةتغليف الأخطاء (fmt.Errorf)أخطاء Sentinelerrors.Is() و errors.As()Panic و Defer و Recoverملخص - File Parser11المكتبة القياسية والبرمجة كائنية التوجه (OOP)
io.Reader و io.Writerواجهة sort.Interfaceواجهة fmt.Stringerencoding/json مع Structsواجهة http.Handlerمراجعة - نماذج REST API14أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط طريقة القالبنمط الحالةنمط التركيبMiddleware كنمط مزين3المؤشرات والذاكرة
أساسيات المؤشرات في Goالمؤشرات إلى الـ Structsالتمرير بالقيمة مقابل التمرير بالمرجعدالة ()newGarbage Collection في Goملخص - بناء Linked List6تعدد الأشكال في Go
تعدد الأشكال عبر InterfacesDuck Typing في Goقواعد استيفاء الـ Interfaceمجموعات متعددة الأشكالحقن التبعيةمراجعة - معالج الدفع9التزامن والبرمجة كائنية التوجه (OOP)
أساسيات الـ Goroutinesالقنوات (Channels) والاتصالالقنوات المخزنة (Buffered) مقابل غير المخزنةجملة Selectsync.Mutex و sync.RWMutexsync.WaitGroupتصميم الـ Structs الآمنة للخيوط (Thread-Safe)مراجعة - Worker Pool