نمط التركيب
جزء من قسم البرمجة كائنية التوجه في رحلة 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 والتركيبات بطريقة متطابقة.
التحدي
سهللنَبْنِ نظامًا لمخطط تنظيمي باستخدام نمط Composite! ستنشئ بنية يمكن فيها التعامل مع كلٍّ من employees الفرديين وdepartments (التي contain employees أو departments أخرى) بطريقة موحّدة، وهو أمر مثالي لحساب إجمالي الرواتب عبر أي جزء من المؤسسة.
ستنظّم شيفرتك عبر ثلاثة ملفات:
component.go: Define الـinterfaceالمشترك الذي سيقوم كلٌّ منindividualوgroupsبتنفيذه.أنشئ
OrgComponentinterfaceمع طريقتين: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)
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
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 عبر الإنترنت