تعدد الأشكال عبر 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)، وسيعمل بشكل تلقائي.
التحدي
سهللنقم ببناء نظام لوصف المركبات يوضح تعدد الأشكال (polymorphism) بشكل عملي. ستقوم بإنشاء أنواع مختلفة من المركبات تشترك جميعها في سلوك عام من خلال واجهة (interface)، ثم تكتب دالة واحدة تعمل مع أي مركبة.
ستقوم بتنظيم الكود الخاص بك عبر ملفين:
vehicles.go: قم بتعريف واجهةDescriberتتطلب دالةDescribe() string. ثم أنشئ ثلاثة أنواع من المركبات ينفذ كل منها هذه الواجهة بطريقته الخاصة:Carمع حقولBrandوModel— تعيد دالةDescribe()الخاصة بهCar: [Brand] [Model]Motorcycleمع حقولBrandوEngineCC(int)— تعيد دالةDescribe()الخاصة بهMotorcycle: [Brand] [EngineCC]ccBicycleمع حقل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")
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس 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