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

تضمين عدة Structs

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

تسمح لك لغة Go بتضمين structs متعددة داخل struct واحد، مما يجمع بين السلوكيات من مصادر مختلفة. يشبه هذا الوراثة المتعددة في اللغات الأخرى، ولكن باستخدام نهج التركيب (composition) الخاص بـ Go.

type Logger struct{}

func (l Logger) Log(msg string) string {
    return "LOG: " + msg
}

type Notifier struct{}

func (n Notifier) Notify(msg string) string {
    return "NOTIFY: " + msg
}

type Service struct {
    Name string
    Logger
    Notifier
}

أصبح لهيكل Service الآن إمكانية الوصول إلى الدوال (methods) من كلا النوعين المضمنين:

func main() {
    s := Service{Name: "OrderService"}
    fmt.Println(s.Log("started"))      // LOG: started
    fmt.Println(s.Notify("new order")) // NOTIFY: new order
}

عند تضمين هياكل (structs) متعددة، قد يحدث تعارض في التسمية إذا كان لنوعين مضمنين دوال (methods) بنفس الاسم. لا تقوم لغة Go بحل هذا الغموض تلقائيًا:

type A struct{}
func (A) Greet() string { return "Hello from A" }

type B struct{}
func (B) Greet() string { return "Hello from B" }

type Combined struct {
    A
    B
}

func main() {
    c := Combined{}
    // c.Greet() - compile error: ambiguous selector
    fmt.Println(c.A.Greet())  // Hello from A
    fmt.Println(c.B.Greet())  // Hello from B
}

عندما تنشأ تعارضات، يجب عليك تحديد طريقة النوع المضمن التي تريد استدعاءها بشكل صريح باستخدام اسم النوع كمؤهل.

challenge icon

التحدي

سهل

لنقم ببناء نظام أجهزة منزلية ذكية يجمع بين إمكانيات متعددة من خلال تضمين الهياكل (struct embedding). ستقوم بإنشاء جهاز يمكنه التحكم في الإضاءة وتشغيل الموسيقى عن طريق تضمين أنواع تحكم منفصلة.

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

  • controllers.go: قم بإنشاء هيكلين مستقلين للتحكم يوفران إمكانيات مختلفة:
    • LightController مع حقل Brightness (int) وطريقة SetLight(level int) string تعيد Light set to [level]%
    • AudioController مع حقل Volume (int) وطريقة SetVolume(level int) string تعيد Volume set to [level]%
    يجب أن يمتلك كلا المتحكمين أيضاً طريقة Status() string—وهذا سيؤدي إلى تعارض في التسمية ستحتاج إلى معالجته. يجب أن تعيد LightController.Status() القيمة Brightness: [Brightness]% ويجب أن تعيد AudioController.Status() القيمة Volume: [Volume]%.
  • device.go: قم بإنشاء هيكل SmartDevice مع حقل Name يضمن كلاً من LightController و AudioController. أضف طريقة تسمى FullStatus() string تعيد اسم الجهاز مع حالتي كلا المتحكمين، مع حل الغموض من خلال استدعاء طريقة Status() الخاصة بكل نوع مضمن بشكل صريح.
  • main.go: اقرأ إعدادات الجهاز من الإدخال، وأنشئ SmartDevice، واستخدم الطرق المرفعة (promoted methods) لضبط مستويات الإضاءة والصوت، ثم اعرض الحالة الكاملة.

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

  • السطر 1: اسم الجهاز
  • السطر 2: مستوى السطوع الأولي (عدد صحيح)
  • السطر 3: مستوى الصوت الأولي (عدد صحيح)
  • السطر 4: مستوى السطوع الجديد المراد ضبطه (عدد صحيح)
  • السطر 5: مستوى الصوت الجديد المراد ضبطه (عدد صحيح)

يجب أن تعيد طريقة FullStatus() الخاصة بك:

[Name] - [LightController.Status()], [AudioController.Status()]

اطبع نتائج استدعاء SetLight و SetVolume (المرفعة من الأنواع المضمنة)، ثم اطبع الحالة الكاملة.

على سبيل المثال، إذا كانت المدخلات Living Room Hub، 50، 30، 75، و 60، يجب أن يكون مخرجك:

Light set to 75%
Volume set to 60%
Living Room Hub - Brightness: 75%, Volume: 60%

لاحظ كيف يمكن الوصول إلى SetLight و SetVolume مباشرة من SmartDevice من خلال ترفيع الطرق (method promotion)، ولكن Status() تتطلب تحديداً صريحاً لأن كلا النوعين المضمنين يمتلكان تلك الطريقة.

ورقة مرجعية

تسمح لغة Go بتضمين هياكل (structs) متعددة داخل هيكل واحد، مما يجمع بين السلوكيات من مصادر مختلفة من خلال التركيب (composition):

type Logger struct{}

func (l Logger) Log(msg string) string {
    return "LOG: " + msg
}

type Notifier struct{}

func (n Notifier) Notify(msg string) string {
    return "NOTIFY: " + msg
}

type Service struct {
    Name string
    Logger
    Notifier
}

يمتلك الهيكل Service إمكانية الوصول إلى الدوال (methods) من كلا النوعين المضمنين:

s := Service{Name: "OrderService"}
fmt.Println(s.Log("started"))      // LOG: started
fmt.Println(s.Notify("new order")) // NOTIFY: new order

تحدث تضاربات التسمية عندما تحتوي الأنواع المضمنة على دوال بنفس الاسم. يجب عليك تحديد دالة النوع المضمن التي تريد استدعاءها بشكل صريح:

type A struct{}
func (A) Greet() string { return "Hello from A" }

type B struct{}
func (B) Greet() string { return "Hello from B" }

type Combined struct {
    A
    B
}

// c.Greet() - compile error: ambiguous selector
// c.Greet() - خطأ في التجميع: محدد غامض
c := Combined{}
fmt.Println(c.A.Greet())  // Hello from A
fmt.Println(c.B.Greet())  // Hello from B

جرّب بنفسك

package main

import (
	"fmt"
)

func main() {
	// قراءة المدخلات
	var name string
	fmt.Scanln(&name)

	var initialBrightness int
	fmt.Scanln(&initialBrightness)

	var initialVolume int
	fmt.Scanln(&initialVolume)

	var newBrightness int
	fmt.Scanln(&newBrightness)

	var newVolume int
	fmt.Scanln(&newVolume)

	// TODO: قم بإنشاء SmartDevice بالاسم المعطى والقيم الأولية

	// TODO: استخدم طريقة SetLight المرفوعة واطبع النتيجة

	// TODO: استخدم طريقة SetVolume المرفوعة واطبع النتيجة

	// TODO: اطبع الحالة الكاملة باستخدام طريقة FullStatus()
}
quiz iconاختبر نفسك

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

جميع دروس Object Oriented Programming