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

نمط التركيب

جزء من قسم البرمجة كائنية التوجه في رحلة GO على Coddy. الدرس 96 من 107.

يسمح لك نمط Composite بمعاملة الكائنات individual ومجموعات الكائنات بطريقة موحّدة. بينما يغيّر State السلوك بناءً على الحالة الداخلية، ينشئ Composite هياكل شجرية حيث تشترك كلٌّ من الأوراق والحاويات في الواجهة نفسها.

هذا النمط مثالي للهياكل الهرمية مثل أنظمة الملفات أو المخططات التنظيمية أو مكوّنات واجهة المستخدم. في Go، نعرّف interface شائعة ينفّذها كلٌّ من العناصر individual والحاويات:

type Component interface {
    GetSize() int
}

type File struct {
    Name string
    Size int
}

func (f File) GetSize() int {
    return f.Size
}

يحتوي composite (الحاوية) على Children ويطبّق interface نفسها من خلال تجميع نتائجها:

type Folder struct {
    Name     string
    Children []Component
}

func (f *Folder) Add(c Component) {
    f.Children = append(f.Children, c)
}

func (f Folder) GetSize() int {
    total := 0
    for _, child := range f.Children {
        total += child.GetSize()
    }
    return total
}

يمكنك الآن تداخل المجلدات داخل مجلدات أخرى، ويعمل استدعاء GetSize() بالطريقة نفسها سواء كان لملف واحد أو لشجرة أدلة كاملة:

docs := &Folder{Name: "docs"}
docs.Add(File{Name: "readme.txt", Size: 100})
docs.Add(File{Name: "notes.txt", Size: 50})

root := &Folder{Name: "root"}
root.Add(docs)
root.Add(File{Name: "config.json", Size: 25})

fmt.Println(root.GetSize())  // 175

يُعدّ Composite مثاليًا عندما تحتاج إلى تمثيل تسلسلات هرمية للجزء والكل، وتريد أن يتعامل العملاء مع الكائنات individual والتركيبات بطريقة متطابقة.

challenge icon

التحدي

سهل

لنَبْنِ نظامًا لمخطط تنظيمي باستخدام نمط Composite! ستنشئ بنية يمكن فيها التعامل مع كلٍّ من employees الفرديين وdepartments (التي contain employees أو departments أخرى) بطريقة موحّدة، وهو أمر مثالي لحساب إجمالي الرواتب عبر أي جزء من المؤسسة.

ستنظّم شيفرتك عبر ثلاثة ملفات:

  • component.go: Define الـ interface المشترك الذي سيقوم كلٌّ من individual وgroups بتنفيذه.

    أنشئ OrgComponent interface مع طريقتين:

    • GetSalary() int: تعيد إجمالي الراتب لهذا المكوّن
    • GetName() string: تعيد اسم الـ employee أو الـ department
  • organization.go: Implement نوعَي الورقة (الـ employee الفردي) والمركّب (الـ department).

    أنشئ بنية Employee مع حقلي Name (من النوع string) وSalary (من النوع int). تعيد GetSalary() راتبه الخاص، بينما تعيد GetName() اسمه.

    أنشئ بنية Department مع حقل Name (من النوع string) وشريحة Members التي تحتوي على عناصر OrgComponent. أضف طريقة Add(c OrgComponent) لإضافة الأعضاء. يجب أن تعيد GetSalary() مجموع رواتب جميع الأعضاء، وأن تعيد GetName() اسم الـ department.

  • main.go: أنشئ بنية تنظيمية واحسب الرواتب.

    اقرأ عدد الـ employees. ولكل employee، اقرأ اسمه وراتبه، وأنشئ مثيلات من Employee. ثم اقرأ عدد الـ departments. ولكل department، اقرأ اسمه وعدد فهارس الأعضاء، متبوعًا بتلك الفهارس (تبدأ من 0 وتشير إلى employees أو departments التي أُنشئت سابقًا وفق ترتيب الإنشاء).

    بعد إنشاء البنية، اقرأ فهرسًا نهائيًا واطبع اسم ذلك المكوّن وإجمالي راتبه بالتنسيق: [name]: [salary]

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

  • عدد الـ employees، ثم اسم وراتب كل employee في سطرين منفصلين
  • عدد الـ departments، ثم اسم كل department وعدد أعضائه وفهارس الأعضاء
  • الفهرس النهائي المطلوب الاستعلام عنه

على سبيل المثال، عند إعطاء:

3
Alice
50000
Bob
60000
Carol
55000
1
Engineering
3
0
1
2
3

يجب أن يكون الناتج:

Engineering: 165000

وعند إعطاء:

4
Alice
50000
Bob
60000
Carol
55000
Dave
70000
2
Dev
2
0
1
QA
2
2
3
4

يجب أن يكون الناتج:

QA: 125000

وعند إعطاء:

2
Alice
50000
Bob
60000
0
1

يجب أن يكون الناتج:

Bob: 60000

لاحظ كيف تعمل GetSalary() بالطريقة نفسها سواء استدعيتها على employee واحد أو على department كامل: يتيح لك نمط Composite التعامل مع الأفراد والمجموعات بطريقة موحّدة عبر الـ interface نفسه!

جرّب بنفسك

package main

import "fmt"

func main() {
	// اقرأ عدد الموظفين
	var numEmployees int
	fmt.Scanln(&numEmployees)

	// خزّن جميع المكونات (الموظفين والأقسام) بترتيب الإنشاء
	var components []OrgComponent

	// TODO: اقرأ اسم وراتب كل موظف
	// أنشئ مثيلات Employee وأضفها إلى شريحة components
	for i := 0; i < numEmployees; i++ {
		var name string
		var salary int
		fmt.Scanln(&name)
		fmt.Scanln(&salary)
		// TODO: أنشئ موظفاً وأضفه إلى components
	}

	// اقرأ عدد الأقسام
	var numDepartments int
	fmt.Scanln(&numDepartments)

	// TODO: اقرأ اسم كل قسم وعدد الأعضاء ومؤشرات الأعضاء
	// أنشئ مثيلات Department، أضف الأعضاء حسب الفهرس، وأضفها إلى components
	for i := 0; i < numDepartments; i++ {
		var deptName string
		var memberCount int
		fmt.Scanln(&deptName)
		fmt.Scanln(&memberCount)
		// TODO: أنشئ قسماً، اقرأ مؤشرات الأعضاء، أضف الأعضاء، أضف إلى components
	}

	// اقرأ الفهرس النهائي للاستعلام
	var queryIndex int
	fmt.Scanln(&queryIndex)

	// TODO: اطبع اسم المكون والراتب الإجمالي بالتنسيق: [name]: [salary]
	// مثال: fmt.Printf("%s: %d\n", name, salary)
}
quiz iconاختبر نفسك

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

جميع دروس البرمجة كائنية التوجه

تدرّب بنفسك: مترجم Go عبر الإنترنت