Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

التنفيذ الضمني

جزء من قسم Object Oriented Programming في رحلة GO على Coddy — الدرس 26 من 107.

في العديد من اللغات كائنية التوجه مثل Java أو C#، يجب عليك التصريح صراحةً بأن النوع ينفذ واجهة (interface) باستخدام كلمات مفتاحية مثل implements. تتبع Go نهجاً مختلفاً: تنفيذ الواجهة يكون ضمنياً.

يستوفي النوع الواجهة ببساطة عن طريق تنفيذ جميع دوالها. لا توجد حاجة للتصريح عن العلاقة في أي مكان. يكتشف المترجم ذلك تلقائيًا.

type Writer interface {
    Write(data string) int
}

type FileWriter struct {
    Filename string
}

// FileWriter يطبق الواجهة Writer بشكل ضمني
func (f FileWriter) Write(data string) int {
    fmt.Println("Writing to", f.Filename)
    return len(data)
}

لاحظ أنه لا يوجد تصريح implements Writer في FileWriter. وبما أن FileWriter يحتوي على دالة Write(data string) int تطابق توقيع الواجهة، فإنه يستوفي تلقائياً واجهة Writer.

لهذا التصميم نتيجة قوية: يمكنك تعريف الواجهات (interfaces) بعد أن تكون الأنواع (types) موجودة بالفعل. إذا كانت هناك مكتبة من جهة خارجية تحتوي على نوع يمتلك الدوال (methods) التي تحتاجها، فيمكنك إنشاء واجهة يحققها ذلك النوع بالفعل دون تعديل الكود الأصلي.

func Save(w Writer, content string) {
    w.Write(content)
}

func main() {
    fw := FileWriter{Filename: "data.txt"}
    Save(fw, "Hello!")  // يعمل لأن FileWriter يستوفي شروط Writer
}

يحافظ التنفيذ الضمني على كود Go مفككاً ومرناً. لا تحتاج الأنواع إلى معرفة الواجهات مسبقاً، مما يسهل بناء الأنظمة من أجزاء مستقلة.

challenge icon

التحدي

سهل

لنقم ببناء نظام لتوصيل الرسائل يوضح كيف تحقق الأنواع الواجهات (interfaces) بشكل ضمني في لغة Go. ستقوم بإنشاء أنواع مرسلين مختلفة تحقق جميعها نفس الواجهة — دون التصريح بتلك العلاقة بشكل صريح أبداً.

ستقوم بتنظيم الكود الخاص بك عبر ملفين:

  • messengers.go: قم بتعريف واجهة Messenger مع طريقة واحدة Send(message string) string. ثم أنشئ هيكلين (structs) سيحققان هذه الواجهة ضمنياً:
    • EmailMessenger مع حقل Address (string)
    • SMSMessenger مع حقل PhoneNumber (string)
    يجب أن يحتوي كل هيكل على طريقة Send تعيد سلسلة نصية تصف عملية التوصيل. لا يجب على أي من الهيكلين التصريح صراحةً بأنه ينفذ Messenger — ستكتشف Go ذلك تلقائياً بناءً على توقيعات الطرق (method signatures).
  • main.go: أنشئ دالة تسمى DeliverMessage تقبل أي Messenger وسلسلة رسالة نصية، ثم تعيد نتيجة استدعاء Send. اقرأ معلومات الاتصال من المدخلات، وأنشئ كلا نوعي المرسلين، واستخدم دالتك لتوصيل الرسائل من خلال كل منهما.

سيتم توفير المدخلات التالية:

  • السطر 1: عنوان البريد الإلكتروني
  • السطر 2: رقم الهاتف
  • السطر 3: الرسالة المراد إرسالها

يجب أن تعيد طرق Send الخاصة بك سلاسل نصية بهذه التنسيقات:

  • EmailMessenger: Email to [Address]: [message]
  • SMSMessenger: SMS to [PhoneNumber]: [message]

على سبيل المثال، إذا تم إعطاء alice@example.com، و 555-1234، و Hello!، يجب أن يكون مخرجك:

Email to alice@example.com: Hello!
SMS to 555-1234: Hello!

الفكرة الجوهرية هنا هي أن دالة DeliverMessage الخاصة بك تقبل أي Messenger، وكل من EmailMessenger و SMSMessenger يحققان تلك الواجهة ببساطة من خلال امتلاك طريقة Send مطابقة. لا توجد كلمة مفتاحية implements في أي مكان — فالتنفيذ الضمني في Go يتولى كل شيء.

ورقة مرجعية

في لغة Go، يكون تنفيذ الواجهة (interface) ضمنيًا. يحقق النوع الواجهة ببساطة عن طريق تنفيذ جميع أساليبها (methods) - لا يلزم وجود إعلان صريح.

type Writer interface {
    Write(data string) int
}

type FileWriter struct {
    Filename string
}

// FileWriter ينفذ Writer بشكل ضمني
func (f FileWriter) Write(data string) int {
    fmt.Println("Writing to", f.Filename)
    return len(data)
}

لا يوجد إعلان implements Writer. نظرًا لأن FileWriter لديه أسلوب Write(data string) int يطابق توقيع الواجهة، فإنه يحقق واجهة Writer تلقائيًا.

يمكنك استخدام أي نوع يحقق الواجهة دون أن يعرف النوع عن الواجهة:

func Save(w Writer, content string) {
    w.Write(content)
}

func main() {
    fw := FileWriter{Filename: "data.txt"}
    Save(fw, "Hello!")  // يعمل لأن FileWriter يحقق Writer
}

يسمح لك هذا التصميم بتعريف الواجهات بعد وجود الأنواع بالفعل، مما يجعل الكود مفككًا ومرنًا.

جرّب بنفسك

package main

import (
	"bufio"
	"fmt"
	"os"
)

// TODO: قم بإنشاء دالة DeliverMessage تقبل أي Messenger
// وسلسلة نصية للرسالة، ثم تُرجع نتيجة استدعاء Send

func main() {
	reader := bufio.NewReader(os.Stdin)
	
	// قراءة عنوان البريد الإلكتروني
	email, _ := reader.ReadString('\n')
	email = email[:len(email)-1]
	
	// قراءة رقم الهاتف
	phone, _ := reader.ReadString('\n')
	phone = phone[:len(phone)-1]
	
	// قراءة الرسالة
	message, _ := reader.ReadString('\n')
	if len(message) > 0 && message[len(message)-1] == '\n' {
		message = message[:len(message)-1]
	}
	
	// TODO: قم بإنشاء EmailMessenger باستخدام عنوان البريد الإلكتروني
	
	// TODO: قم بإنشاء SMSMessenger باستخدام رقم الهاتف
	
	// TODO: استخدم DeliverMessage لإرسال الرسالة عبر كل messenger
	// وطباعة النتائج
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming