تعدد الأشكال عبر Interfaces
جزء من قسم البرمجة كائنية التوجه في رحلة GO على Coddy. الدرس 41 من 107.
يسمح polymorphism بالتعامل مع أنواع مختلفة بطريقة موحّدة من خلال واجهة مشتركة. في Go، يتحقق ذلك بالكامل من خلال الواجهات، من دون التوريث أو التسلسلات الهرمية للفئات.
عندما تقبل function نوع interface كمعامل، يمكن تمرير أي نوع ملموس يطبّق ذلك الـinterface. لا تحتاج function إلى معرفة النوع المحدد. فهي تهتم فقط بالسلوك الذي يحدده الـinterface:
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) // مياو!
}
ينتج استدعاء function نفسه سلوكًا مختلفًا اعتمادًا على النوع الفعلي المُمرَّر. هذا هو polymorphism أثناء العمل. تتم كتابة function MakeSound مرة واحدة، لكنها تعمل مع عدد غير محدود من الأنواع، ما دامت تستوفي الواجهة Speaker.
يحافظ هذا النهج على مرونة شفرتك وقابليتها للتوسّع. لا تتطلب إضافة نوع جديد يتحدث أي تغييرات في functions الحالية؛ ما عليك سوى تنفيذ interface، وسيعمل تلقائيًا.
التحدي
سهللنَبْنِ نظامًا لوصف المركبات يوضّح مفهوم polymorphism عمليًا. ستنشئ أنواعًا مختلفة من المركبات تشترك جميعها في سلوك common من خلال interface، ثم تكتب function واحدة تعمل مع any مركبة.
ستنظّم code الخاص بك عبر ملفين:
vehicles.go: عرّف interface باسمDescriberيتطلب method باسمDescribe() string. ثم أنشئ ثلاثة أنواع من المركبات، يطبّق كل منها هذا interface بطريقته الخاصة:Carمع fields باسمَيBrandوModel. تُعيدDescribe()القيمةCar: [Brand] [Model]Motorcycleمع fields باسمَيBrandوEngineCC(من النوعint). تُعيدDescribe()القيمةMotorcycle: [Brand] [EngineCC]ccBicycleمع field باسمType(مثل "Mountain" أو "Road"). تُعيدDescribe()القيمةBicycle: [Type]
main.go: أنشئ function باسمPrintDescriptionتقبل أيDescriberوتطبع نتيجة استدعاءDescribe(). اقرأ تفاصيل المركبات من input، وأنشئ واحدة من كل نوع من أنواع المركبات، ومرّر كل واحدة منها إلىPrintDescriptionلإظهار أن function نفسها تعمل مع الأنواع الثلاثة المختلفة.
سيتم توفير input التالي:
- السطر 1: Brand الخاص بـ Car
- السطر 2: Model الخاص بـ Car
- السطر 3: Brand الخاص بـ Motorcycle
- السطر 4: Engine CC الخاص بـ Motorcycle (عدد صحيح)
- السطر 5: Type الخاص بـ Bicycle
على سبيل المثال، عند إعطاء Toyota وCamry وHonda و600 وMountain، يجب أن يكون output الخاص بك:
Car: Toyota Camry
Motorcycle: Honda 600cc
Bicycle: Mountainلاحظ أن PrintDescription لا تحتاج إلى معرفة ما إذا كانت تتلقى Car أو Motorcycle أو Bicycle. فهي تستدعي ببساطة Describe()، ويستجيب كل نوع بإخراجه الفريد الخاص به. هذا هو polymorphism: function واحدة، وسلوكيات متعددة.
جرّب بنفسك
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 لكل مركبة لإظهار تعدد الأشكال
fmt.Println("TODO: Print vehicle descriptions")
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
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 عبر الإنترنت