الواجهة كعقد
جزء من قسم البرمجة كائنية التوجه في رحلة GO على Coddy. الدرس 27 من 107.
يساعدك التفكير في الواجهات باعتبارها عقودًا على تصميم برامج Go أفضل. عندما تقبل دالة نوع واجهة، فهي تقول: "لا يهمني نوع البيانات الفعلي الذي تمنحني إياه، ما دام يفي بهذا العقد."
يتيح لك هذا التفكير القائم على العقود كتابة دوال تركّز على السلوك بدلًا من أنواع محددة. تأمّل دالة تحتاج إلى معالجة البيانات من مصادر متنوعة:
type DataSource interface {
FetchData() string
}
func ProcessData(src DataSource) {
data := src.FetchData()
fmt.Println("Processing:", data)
}لا تعرف الدالة ProcessData ولا تهتم بما إذا كانت تتلقى اتصالًا بقاعدة بيانات، أو عميل API، أو قارئًا للملفات. فهي تتطلب فقط أن يتمكن الإدخال من استدعاء FetchData(). ويعمل أي نوع يلتزم بهذا contract.
type Database struct{ ConnectionString string }
func (d Database) FetchData() string { return "data from DB" }
type APIClient struct{ Endpoint string }
func (a APIClient) FetchData() string { return "data from API" }
func main() {
db := Database{ConnectionString: "localhost"}
api := APIClient{Endpoint: "https://api.example.com"}
ProcessData(db) // يعمل!
ProcessData(api) // يعمل أيضاً!
}يجعل نهج contract شيفرتك أكثر قابلية للاختبار أيضًا. أثناء الاختبار، يمكنك إنشاء نوع وهمي يفي بمتطلبات interface من دون الحاجة إلى قواعد بيانات حقيقية أو اتصالات بالشبكة. لا تعرف function الخاضعة للاختبار الفرق، لأن contract قد استُوفي.
التحدي
سهللنَبْنِ نظام notification يوضّح كيف تعمل interfaces بوصفها contract. ستنشئ interface باسم Notifier يحدّد ما يعنيه إرسال notification، ثم تنفّذ قنوات notification متعددة تلتزم جميعها بهذا contract.
ستنظّم الكود عبر ملفين:
notifiers.go: Define interface باسمNotifierمع method واحدة هيNotify(message string) string. ثم أنشئ ثلاثة structs مختلفة تستوفي هذا contract:ConsoleNotifierمع field باسمPrefixFileNotifierمع field باسمFilenameWebhookNotifierمع field باسمURL
Notifyلإرجاع string يصف كيفية إرسال notification.main.go: أنشئ function باسمBroadcastAlertتقبل شريحة من قيمNotifierوmessage من نوع string. يجب أن تستدعي هذه functionNotifyعلى كل notifier وتطبع النتائج. اقرأ الإعدادات من الإدخال، وأنشئ أنواع notifier الثلاثة، ثم أرسل alert عبرها جميعًا.
سيتم توفير المدخلات التالية:
- السطر 1: بادئة Console
- السطر 2: Filename
- السطر 3: عنوان URL الخاص بـ Webhook
- السطر 4: message الخاص بـ Alert
يجب أن تُرجع methods المسماة Notify strings بالتنسيقات التالية:
- ConsoleNotifier:
[Prefix] [message] - FileNotifier:
Writing to [Filename]: [message] - WebhookNotifier:
POST [URL]: [message]
على سبيل المثال، عند إعطاء ALERT: وlog.txt وhttps://hooks.example.com وServer down!، يجب أن يكون الناتج:
ALERT: Server down!
Writing to log.txt: Server down!
POST https://hooks.example.com: Server down!تكمن قوة هذا التصميم في أن function المسماة BroadcastAlert لا تعرف أنواع notifier المحددة ولا تهتم بها. فهي تتطلب فقط أن يفي كل واحد منها بـ contract الخاص بـ Notifier. ويمكنك إضافة SlackNotifier أو EmailNotifier لاحقًا دون تغيير function الخاصة بالبث إطلاقًا.
جرّب بنفسك
package main
import (
"bufio"
"fmt"
"os"
)
// BroadcastAlert يرسل رسالة عبر جميع notifiers
// TODO: نفّذ هذه الدالة لـ:
// - اقبل slice من قيم Notifier ورسالة string
// - استدعِ Notify على كل notifier واطبع النتائج
func BroadcastAlert(notifiers []Notifier, message string) {
// TODO: مرّ عبر notifiers واطبع نتيجة كل إشعار
}
func main() {
scanner := bufio.NewScanner(os.Stdin)
// اقرأ بادئة وحدة التحكم
scanner.Scan()
prefix := scanner.Text()
// اقرأ اسم الملف
scanner.Scan()
filename := scanner.Text()
// اقرأ عنوان URL للـ webhook
scanner.Scan()
url := scanner.Text()
// اقرأ رسالة التنبيه
scanner.Scan()
message := scanner.Text()
// TODO: أنشئ مثيلات لجميع أنواع الـ notifier الثلاثة
// - ConsoleNotifier مع الـ prefix
// - FileNotifier مع اسم الملف
// - WebhookNotifier مع الـ URL
// TODO: أنشئ شريحة من Notifier تحتوي على جميع المُبلِّغات الثلاثة
// TODO: استدعِ BroadcastAlert مع المُبلِّغات والرسالة
_ = prefix
_ = filename
_ = url
_ = message
fmt.Println("TODO: Implement the solution")
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
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تدرّب بنفسك: مترجم Go عبر الإنترنت