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

تعدد الأشكال عبر Interfaces

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

تعدد الأشكال (Polymorphism) يسمح بمعاملة الأنواع المختلفة بشكل موحد من خلال واجهة مشتركة. في Go، يتم تحقيق ذلك بالكامل من خلال الواجهات (interfaces)، دون الحاجة إلى الوراثة أو التسلسلات الهرمية للأصناف (class hierarchies).

عندما تقبل دالة نوع واجهة (interface type) كمعامل (parameter)، يمكن تمرير أي نوع ملموس (concrete type) يقوم بتنفيذ تلك الواجهة. لا تحتاج الدالة إلى معرفة النوع المحدد—فهي تهتم فقط بالسلوك المحدد بواسطة الواجهة:

type Speaker interface {
    Speak() string
}

type Dog struct{ Name string }
func (d Dog) Speak() string { return "Woof!" }

type Cat struct{ Name string }
func (c Cat) Speak() string { return "Meow!" }

func MakeSound(s Speaker) {
    fmt.Println(s.Speak())
}

الآن MakeSound تعمل مع أي نوع يحتوي على دالة Speak():

func main() {
    dog := Dog{Name: "Rex"}
    cat := Cat{Name: "Whiskers"}
    
    MakeSound(dog)  // هوهو!
    MakeSound(cat)  // مياو!
}

ينتج عن استدعاء الدالة نفسه سلوك مختلف اعتماداً على النوع الفعلي الذي تم تمريره. هذا هو تعدد الأشكال (polymorphism) في الواقع العملي. تمت كتابة الدالة MakeSound مرة واحدة ولكنها تعمل مع عدد غير محدود من الأنواع، طالما أنها تحقق واجهة Speaker.

هذا النهج يحافظ على مرونة الكود الخاص بك وقابليته للتوسع. إضافة نوع جديد يتحدث لا يتطلب أي تغييرات في الدوال الموجودة - ما عليك سوى تنفيذ الواجهة (interface)، وسيعمل بشكل تلقائي.

challenge icon

التحدي

سهل

لنقم ببناء نظام لوصف المركبات يوضح تعدد الأشكال (polymorphism) بشكل عملي. ستقوم بإنشاء أنواع مختلفة من المركبات تشترك جميعها في سلوك عام من خلال واجهة (interface)، ثم تكتب دالة واحدة تعمل مع أي مركبة.

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

  • vehicles.go: قم بتعريف واجهة Describer تتطلب دالة Describe() string. ثم أنشئ ثلاثة أنواع من المركبات ينفذ كل منها هذه الواجهة بطريقته الخاصة:
    • Car مع حقول Brand و Model— تعيد دالة Describe() الخاصة به Car: [Brand] [Model]
    • Motorcycle مع حقول Brand و EngineCC (int)— تعيد دالة Describe() الخاصة به Motorcycle: [Brand] [EngineCC]cc
    • Bicycle مع حقل Type (مثل "Mountain" أو "Road")— تعيد دالة Describe() الخاصة به Bicycle: [Type]
  • main.go: أنشئ دالة تسمى PrintDescription تقبل أي Describer وتطبع نتيجة استدعاء Describe(). اقرأ تفاصيل المركبات من المدخلات، وأنشئ واحدة من كل نوع مركبة، ومرر كل واحدة منها إلى PrintDescription لإثبات أن نفس الدالة تعمل مع جميع الأنواع الثلاثة المختلفة.

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

  • السطر 1: ماركة السيارة (Car brand)
  • السطر 2: طراز السيارة (Car model)
  • السطر 3: ماركة الدراجة النارية (Motorcycle brand)
  • السطر 4: سعة محرك الدراجة النارية (Motorcycle engine CC - عدد صحيح)
  • السطر 5: نوع الدراجة الهوائية (Bicycle type)

على سبيل المثال، إذا كانت المدخلات Toyota، Camry، Honda، 600، و Mountain، يجب أن يكون مخرجك:

Car: Toyota Camry
Motorcycle: Honda 600cc
Bicycle: Mountain

لاحظ كيف أن PrintDescription لا تحتاج إلى معرفة ما إذا كانت تستقبل Car أو Motorcycle أو Bicycle— فهي ببساطة تستدعي Describe() وكل نوع يستجيب بمخرجاته الفريدة. هذا هو تعدد الأشكال (polymorphism): دالة واحدة، سلوكيات متعددة.

ورقة مرجعية

تسمح تعددية الأشكال (Polymorphism) بمعاملة الأنواع المختلفة بشكل موحد من خلال واجهة (interface) مشتركة. في لغة Go، يتم تحقيق ذلك من خلال الواجهات دون الحاجة إلى الوراثة أو التسلسلات الهرمية للأصناف.

عندما تقبل دالة نوع واجهة (interface type) كمعامل، يمكن تمرير أي نوع ملموس (concrete type) يقوم بتنفيذ تلك الواجهة:

type Speaker interface {
    Speak() string
}

type Dog struct{ Name string }
func (d Dog) Speak() string { return "Woof!" }

type Cat struct{ Name string }
func (c Cat) Speak() string { return "Meow!" }

func MakeSound(s Speaker) {
    fmt.Println(s.Speak())
}

تعمل نفس الدالة مع أنواع مختلفة:

func main() {
    dog := Dog{Name: "Rex"}
    cat := Cat{Name: "Whiskers"}
    
    MakeSound(dog)  // Woof!
    MakeSound(cat)  // Meow!
}

تُكتب الدالة مرة واحدة ولكنها تعمل مع أنواع غير محدودة، طالما أنها تستوفي شروط الواجهة. إضافة أنواع جديدة لا يتطلب أي تغييرات على الدوال الحالية - فقط قم بتنفيذ الواجهة.

جرّب بنفسك

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

// TODO: أنشئ دالة تسمى PrintDescription تقبل أي Describer
// وتطبع نتيجة استدعاء Describe()

func main() {
	reader := bufio.NewReader(os.Stdin)

	// قراءة تفاصيل السيارة
	carBrand, _ := reader.ReadString('\n')
	carBrand = strings.TrimSpace(carBrand)
	carModel, _ := reader.ReadString('\n')
	carModel = strings.TrimSpace(carModel)

	// قراءة تفاصيل الدراجة النارية
	motoBrand, _ := reader.ReadString('\n')
	motoBrand = strings.TrimSpace(motoBrand)
	motoEngineStr, _ := reader.ReadString('\n')
	motoEngineStr = strings.TrimSpace(motoEngineStr)
	motoEngine, _ := strconv.Atoi(motoEngineStr)

	// قراءة تفاصيل الدراجة الهوائية
	bicycleType, _ := reader.ReadString('\n')
	bicycleType = strings.TrimSpace(bicycleType)

	// TODO: أنشئ Car و Motorcycle و Bicycle باستخدام قيم المدخلات

	// TODO: استدعِ PrintDescription لكل مركبة لتوضيح تعدد الأشكال (polymorphism)
	fmt.Println("TODO: Print vehicle descriptions")
}
quiz iconاختبر نفسك

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

جميع دروس Object Oriented Programming