نمط التركيب
جزء من قسم Object Oriented Programming في رحلة GO على Coddy — الدرس 96 من 107.
يتيح لك نمط التركيب (Composite pattern) التعامل مع الكائنات الفردية ومجموعات الكائنات بشكل موحد. فبينما يغير نمط الحالة (State) السلوك بناءً على الحالة الداخلية، يقوم نمط التركيب (Composite) ببناء هياكل شجرية تشترك فيها كل من العناصر الفردية (leaves) والحاويات (containers) في نفس الواجهة (interface).
هذا النمط مثالي للهياكل الهرمية مثل أنظمة الملفات، أو المخططات التنظيمية، أو مكونات واجهة المستخدم. في لغة Go، نقوم بتعريف واجهة (interface) مشتركة تنفذها كل من العناصر الفردية والحاويات:
type Component interface {
GetSize() int
}
type File struct {
Name string
Size int
}
func (f File) GetSize() int {
return f.Size
}يحتوي المركب (الحاوية) على العناصر الأبناء ويقوم بتنفيذ نفس الواجهة من خلال تجميع نتائجهم:
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) مثالياً عندما تحتاج إلى تمثيل التسلسلات الهرمية للجزء والكل، وتريد من العملاء معاملة الكائنات الفردية والتركيبات بشكل متماثل.
التحدي
سهللنقم ببناء نظام للهيكل التنظيمي باستخدام نمط Composite! ستقوم بإنشاء هيكل حيث يمكن معاملة كل من الموظفين الأفراد والأقسام (التي تحتوي على موظفين أو أقسام أخرى) بشكل موحد — وهو أمر مثالي لحساب إجمالي الرواتب في أي جزء من المنظمة.
ستقوم بتنظيم الكود الخاص بك عبر ثلاثة ملفات:
component.go: تحديد الواجهة (interface) المشتركة التي سيقوم كل من الأفراد والمجموعات بتنفيذها.قم بإنشاء واجهة
OrgComponentبطريقتين (methods):GetSalary() int— تعيد إجمالي الراتب لهذا المكونGetName() string— تعيد اسم الموظف أو القسم
organization.go: تنفيذ كل من أنواع الأوراق (الموظف الفردي) والمركبات (القسم).قم بإنشاء هيكل
Employeeمع حقولName(string) وSalary(int). تعيدGetSalary()الخاصة به راتبه الخاص، وتعيدGetName()اسمه.قم بإنشاء هيكل
Departmentمع حقلName(string) وشريحة (slice)Membersالتي تحمل عناصرOrgComponent. أضف طريقةAdd(c OrgComponent)لإضافة الأعضاء. يجب أن تعيدGetSalary()الخاصة به مجموع رواتب جميع الأعضاء، وتعيدGetName()اسم القسم.main.go: بناء هيكل المنظمة وحساب الرواتب.اقرأ عدد الموظفين. لكل موظف، اقرأ اسمه وراتبه، وقم بإنشاء مثيلات
Employee. ثم اقرأ عدد الأقسام. لكل قسم، اقرأ اسمه وعدد فهارس الأعضاء، متبوعاً بتلك الفهارس (تبدأ من 0، وتشير إلى الموظفين أو الأقسام التي تم إنشاؤها مسبقاً بترتيب إنشائها).بعد بناء الهيكل، اقرأ الفهرس النهائي واطبع اسم ذلك المكون وإجمالي راتبه بالتنسيق التالي:
[name]: [salary]
سيتم توفير المدخلات التالية:
- عدد الموظفين، ثم اسم كل موظف وراتبه في أسطر منفصلة
- عدد الأقسام، ثم اسم كل قسم، وعدد الأعضاء، وفهارس الأعضاء
- الفهرس النهائي للاستعلام عنه
على سبيل المثال، إذا كان المعطى:
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() بشكل متطابق سواء قمت باستدعائها لموظف واحد أو لقسم بأكمله — يتيح لك نمط Composite معاملة الأفراد والمجموعات بشكل موحد من خلال نفس الواجهة!
ورقة مرجعية
يعامل نمط التركيب (Composite pattern) الكائنات الفردية ومجموعات الكائنات بشكل موحد من خلال واجهة مشتركة. إنه مثالي للهياكل الهرمية مثل أنظمة الملفات، أو المخططات التنظيمية، أو مكونات واجهة المستخدم.
قم بتعريف واجهة مشتركة تنفذها كل من العناصر الفردية والحاويات:
type Component interface {
GetSize() int
}تنفيذ الورقة (العنصر الفردي):
type File struct {
Name string
Size int
}
func (f File) GetSize() int {
return f.Size
}تنفيذ التركيب (الحاوية) التي تحتوي على أبناء وتجمع نتائجهم:
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
}مثال على الاستخدام يوضح المعاملة الموحدة للعناصر الفردية والتكوينات:
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جرّب بنفسك
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)
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس 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