مجموعات متعددة الأشكال
جزء من قسم Object Oriented Programming في رحلة GO على Coddy — الدرس 44 من 107.
يعد تخزين أنواع مختلفة في نفس المجموعة أحد أقوى تطبيقات الواجهات (interfaces). يمكن لشريحة (slice) من نوع واجهة أن تحتوي على أي قيمة تحقق تلك الواجهة، مما يسمح لك بتجميع أنواع مختلفة ولكنها مرتبطة معاً.
فكر في سيناريو تحتاج فيه إلى إدارة أشكال مختلفة. بدلاً من استخدام شرائح (slices) منفصلة لكل نوع، يمكنك استخدام شريحة واحدة من نوع الواجهة (interface type):
type Shape interface {
Area() float64
}
type Circle struct{ Radius float64 }
func (c Circle) Area() float64 { return 3.14159 * c.Radius * c.Radius }
type Rectangle struct{ Width, Height float64 }
func (r Rectangle) Area() float64 { return r.Width * r.Height }
func main() {
shapes := []Shape{
Circle{Radius: 5},
Rectangle{Width: 4, Height: 3},
Circle{Radius: 2},
}
for _, s := range shapes {
fmt.Printf("Area: %.2f\n", s.Area())
}
}
تحتوي شريحة shapes على كل من الدوائر والمستطيلات. عند التكرار، يستجيب كل عنصر لـ Area() وفقاً لنوعه الفعلي. هذا هو تعدد الأشكال المطبق على المجموعات—حيث تتعامل نفس الحلقة مع جميع أنواع الأشكال بشكل موحد.
تعد المجموعات متعددة الأشكال (Polymorphic collections) أساسية عند بناء أنظمة تعالج عناصر متنوعة: مثل نظام إشعارات يرسل رسائل بريد إلكتروني ورسائل SMS، أو لعبة تقوم بتحديث أنواع مختلفة من الأعداء، أو معالج مستندات يتعامل مع تنسيقات ملفات متعددة. لا تهتم المجموعة بالأنواع المحددة، بل تهتم فقط بأن يوفر كل عنصر السلوك المطلوب.
التحدي
سهللنقم ببناء نظام لإدارة المهام يتعامل مع أنواع مختلفة من المهام من خلال واجهة موحدة. ستقوم بإنشاء أنواع مهام متنوعة ومعالجتها جميعاً معاً في مجموعة متعددة الأشكال (polymorphic collection).
ستقوم بتنظيم الكود الخاص بك عبر ثلاثة ملفات:
task.go: قم بتعريف واجهةTaskتتطلب تابعاً باسمSummary() string. ستكون هذه الواجهة هي العقد المشترك الذي يجب أن تلتزم به جميع أنواع المهام.types.go: قم بإنشاء ثلاثة أنواع مختلفة من المهام، كل منها ينفذ واجهةTask:BugFixمع حقولID(string) وSeverity(string)—وتابعSummary()الخاص به يعيد"Bug #[ID] ([Severity])"Featureمع حقولName(string) وPoints(int)—وتابعSummary()الخاص به يعيد"Feature: [Name] - [Points] pts"Documentationمع حقلTopic(string)—وتابعSummary()الخاص به يعيد"Docs: [Topic]"
main.go: قم بإنشاء دالة تسمىPrintBacklogتقبل شريحة (slice) منTaskوتطبع ملخص كل مهمة في سطر مستقل. اقرأ تفاصيل المهام من المدخلات، وأنشئ واحداً من كل نوع من أنواع المهام، واجمعها جميعاً في شريحة[]Taskواحدة، ومررها إلىPrintBacklog.
سيتم توفير المدخلات التالية:
- السطر 1: معرف الخطأ (Bug ID)
- السطر 2: خطورة الخطأ (Bug severity)
- السطر 3: اسم الميزة (Feature name)
- السطر 4: نقاط الميزة (Feature points - عدد صحيح)
- السطر 5: موضوع التوثيق (Documentation topic)
على سبيل المثال، إذا كانت المدخلات "1042"، "critical"، "Dark Mode"، "8"، و "API Reference"، يجب أن يكون مخرجك:
Bug #1042 (critical)
Feature: Dark Mode - 8 pts
Docs: API Referenceالقوة هنا تكمن في أن PrintBacklog لا تحتاج إلى منطق منفصل للأخطاء أو الميزات أو التوثيق—فهي ببساطة تمر عبر الشريحة وتستدعي Summary() لكل عنصر. يستجيب كل نوع مهمة بتنسيقه الفريد الخاص، مما يوضح كيف تسمح لك المجموعات متعددة الأشكال بمعالجة أنواع متنوعة بشكل موحد.
ورقة مرجعية
يمكن لشريحة (slice) من نوع واجهة (interface) أن تحمل أي قيمة تحقق تلك الواجهة، مما يتيح إنشاء مجموعات متعددة الأشكال (polymorphic collections):
type Shape interface {
Area() float64
}
type Circle struct{ Radius float64 }
func (c Circle) Area() float64 { return 3.14159 * c.Radius * c.Radius }
type Rectangle struct{ Width, Height float64 }
func (r Rectangle) Area() float64 { return r.Width * r.Height }
func main() {
shapes := []Shape{
Circle{Radius: 5},
Rectangle{Width: 4, Height: 3},
Circle{Radius: 2},
}
for _, s := range shapes {
fmt.Printf("Area: %.2f\n", s.Area())
}
}
تحتوي شريحة shapes على أنواع مختلفة (Circle و Rectangle) والتي تطبق جميعها واجهة Shape. عند التكرار، يستجيب كل عنصر لـ Area() وفقاً لنوعه الفعلي - وهذا هو تعدد الأشكال (polymorphism) المطبق على المجموعات.
هذا النمط مفيد لمعالجة عناصر متنوعة بشكل موحد: أنظمة التنبيه التي تتعامل مع أنواع رسائل مختلفة، أو الألعاب التي تقوم بتحديث أنواع مختلفة من الأعداء، أو معالجات المستندات التي تتعامل مع تنسيقات ملفات متعددة.
جرّب بنفسك
package main
import (
"fmt"
)
// TODO: قم بإنشاء دالة PrintBacklog تقبل slice من النوع Task
// وتطبع ملخص كل مهمة في سطر منفصل
func main() {
// قراءة المدخلات
var bugID string
var bugSeverity string
var featureName string
var featurePoints int
var docTopic string
fmt.Scanln(&bugID)
fmt.Scanln(&bugSeverity)
fmt.Scanln(&featureName)
fmt.Scanln(&featurePoints)
fmt.Scanln(&docTopic)
// TODO: قم بإنشاء واحدة من كل نوع من أنواع المهام (BugFix، Feature، Documentation)
// TODO: اجمع كل المهام في slice واحدة من النوع []Task
// TODO: قم باستدعاء PrintBacklog مع slice المهام
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس 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