الفرق بين المراجعتين لصفحة: «Design Patterns/composite»
أسامه-دمراني (نقاش | مساهمات) 2.1 محتوى |
جميل-بيلوني (نقاش | مساهمات) طلا ملخص تعديل |
||
(10 مراجعات متوسطة بواسطة 4 مستخدمين غير معروضة) | |||
سطر 1: | سطر 1: | ||
<noinclude>{{DISPLAYTITLE:نمط | <noinclude>{{DISPLAYTITLE:نمط المركب Composite}}</noinclude> | ||
نمط | نمط المُركَّب (Composite) هو نمط تصميم هيكلي يسمح لك بتركيب كائنات في هياكل شجرية لتعاملها على أنها وحدة واحدة أو كائن واحد. | ||
== المشكلة == | == المشكلة == | ||
[[ملف:dpc.problem-en.png|تصغير|(ش.1) قد يتكون الطلب الواحد من عدة منتجات مختلفة مغلفة في صناديق وغير مغلفة، وتغلف كلها في صناديق أكبر منها تحتويها، وهكذا ليبدو الشكل الكلي كشجرة مقلوبة.]] | |||
يستخدم نمط المركَّب في حالة واحدة فقط، وهي حين يمكن تمثيل النموذج الذي يقوم عليه تطبيقك في هيئة شجرية، كأن يكون لديك نوعين من الكائنات هما المنتجات <code>Products</code> والصناديق <code>Boxes</code>. ويستطيع الأخير استيعاب منتجات بداخله وصناديق أخرى كذلك أصغر منه في الحجم، وتلك الصناديق الجديدة يمكنها احتواء صناديق أخرى بداخلها، وهكذا دواليك. ولنقل أنك تريد إنشاء نظام طلبات يستخدم تلك الفئات، تحتوي فيه الطلبات المختلفة على منتجات بسيطة بدون أي تغليف إضافة إلى صناديق ملأى بالمنتجات والصناديق الأخرى الأصغر منها، وتريد الآن أن تحسب الثمن الكلي لطلب به صناديق بداخلها صناديق أخرى ومنتجات بدون تغليف، كيف تفعل ذلك؟ | يستخدم نمط المركَّب في حالة واحدة فقط، وهي حين يمكن تمثيل النموذج الذي يقوم عليه تطبيقك في هيئة شجرية، كأن يكون لديك نوعين من الكائنات هما المنتجات <code>Products</code> والصناديق <code>Boxes</code>. ويستطيع الأخير استيعاب منتجات بداخله وصناديق أخرى كذلك أصغر منه في الحجم، وتلك الصناديق الجديدة يمكنها احتواء صناديق أخرى بداخلها، وهكذا دواليك. ولنقل أنك تريد إنشاء نظام طلبات يستخدم تلك الفئات، تحتوي فيه الطلبات المختلفة على منتجات بسيطة بدون أي تغليف إضافة إلى صناديق ملأى بالمنتجات والصناديق الأخرى الأصغر منها، وتريد الآن أن تحسب الثمن الكلي لطلب به صناديق بداخلها صناديق أخرى ومنتجات بدون تغليف، كيف تفعل ذلك؟ | ||
إن سلكنا الطريق المباشرة فهذا يعني فتح الصناديق جميعها وحساب ثمن كل منتج بسيط ثم جمع تلك الأثمان لنحصل على الثمن الكلي للطلب كاملًا، لكن هذا الأسلوب وإن كان يصلح أحيانًا في الأمثلة التي نراها في الحياة اليومية، لكنها لا تصلح في البرمجة، إذ -وفقًا لهذا المنظور- يجب أن تعرف فئات كل المنتجات <code>Products</code> والصناديق <code>Boxes</code> التي ستفحصها، ومستوى تداخل الصناديق وأمور أخرى غير ذلك، وذلك يجعل المنظور المباشر غير عملي وقريبًا من المستحيل لحل مشكلة كهذه. | إن سلكنا الطريق المباشرة فهذا يعني فتح الصناديق جميعها وحساب ثمن كل منتج بسيط ثم جمع تلك الأثمان لنحصل على الثمن الكلي للطلب كاملًا، لكن هذا الأسلوب وإن كان يصلح أحيانًا في الأمثلة التي نراها في الحياة اليومية، لكنها لا تصلح في البرمجة، إذ -وفقًا لهذا المنظور- يجب أن تعرف فئات كل المنتجات <code>Products</code> والصناديق <code>Boxes</code> التي ستفحصها، ومستوى تداخل الصناديق وأمور أخرى غير ذلك، وذلك يجعل المنظور المباشر غير عملي وقريبًا من المستحيل لحل مشكلة كهذه. | ||
== الحل == | == الحل == | ||
[[ملف:dpc.composite-comic-1-en.png|تصغير|(ش.2) يسمح لك نمط المركَّب بتشغيل أسلوب ما بشكل تكراري في عناصر شجرة من الكائنات.]] | |||
يقترح نمط المركَّب أن تعمل مع <code>Boxes</code> و <code>Products</code> من خلال واجهة مشتركة تصرح عن أسلوب لحساب الثمن الكلي للطلب، وسيفحص ذلك الأسلوب كل عنصر في الصندوق ويطلب ثمنه ثم يعيد إجمالي الصندوق، وإن كان أحد عناصر الصندوق صندوقًا أصغر منه فإن الأسلوب يكرر نفس الخطوات في فحص كل عنصر فيه وطلب ثمنه، وهكذا يعامل كل صندوق فرعي معاملةَ الصندوق الأول حتى يصل إلى آخر تلك السلسلة ويحسب أثمان كل المنتجات الفرعية، بل قد يحسب أثمان الصناديق أنفسها أيضًا في بعض الحالات التي تُحسب فيها قيمة الصناديق من تكلفة التغليف. | يقترح نمط المركَّب أن تعمل مع <code>Boxes</code> و <code>Products</code> من خلال واجهة مشتركة تصرح عن أسلوب لحساب الثمن الكلي للطلب، وسيفحص ذلك الأسلوب كل عنصر في الصندوق ويطلب ثمنه ثم يعيد إجمالي الصندوق، وإن كان أحد عناصر الصندوق صندوقًا أصغر منه فإن الأسلوب يكرر نفس الخطوات في فحص كل عنصر فيه وطلب ثمنه، وهكذا يعامل كل صندوق فرعي معاملةَ الصندوق الأول حتى يصل إلى آخر تلك السلسلة ويحسب أثمان كل المنتجات الفرعية، بل قد يحسب أثمان الصناديق أنفسها أيضًا في بعض الحالات التي تُحسب فيها قيمة الصناديق من تكلفة التغليف. | ||
وأعظم فائدة لهذا الأسلوب في الحل أنك لا تحتاج إلى النظر في الفئات الحقيقية للكائنات التي تتكون منها الشجرة، فلا تحتاج إلى معرفة ما إن كان الكائن منتجًا بسيطًا أو صندوقًا معقدًا إذ يمكنك معالجتها جميعًا بنفس الأسلوب من خلال الواجهة المشتركة، ذلك أن الكائنات أنفسها تمرر الطلب إلى ما تحتها في كامل الشجرة كلما استدعيت أسلوبًا ما. | وأعظم فائدة لهذا الأسلوب في الحل أنك لا تحتاج إلى النظر في الفئات الحقيقية للكائنات التي تتكون منها الشجرة، فلا تحتاج إلى معرفة ما إن كان الكائن منتجًا بسيطًا أو صندوقًا معقدًا إذ يمكنك معالجتها جميعًا بنفس الأسلوب من خلال الواجهة المشتركة، ذلك أن الكائنات أنفسها تمرر الطلب إلى ما تحتها في كامل الشجرة كلما استدعيت أسلوبًا ما. | ||
== مثال واقعي == | == مثال واقعي == | ||
تُشكَّل جيوش أغلب الدول بصورة هرمية يتكون فيها الجيش من أقسام رئيسية، في كل قسم مجموعة ألوية، في كل لواء منها مجموعة كتائب، ثم كل كتيبة بها مجموعة سرايا، وكل سرية فيها مجموعات من الجنود الذين تُمرر إليهم الأوامر من القيادات العليا للجيش لتعرف المهام التي عليها تنفيذها. | تُشكَّل جيوش أغلب الدول بصورة هرمية يتكون فيها الجيش من أقسام رئيسية، في كل قسم مجموعة ألوية، في كل لواء منها مجموعة كتائب، ثم كل كتيبة بها مجموعة سرايا، وكل سرية فيها مجموعات من الجنود الذين تُمرر إليهم الأوامر من القيادات العليا للجيش لتعرف المهام التي عليها تنفيذها. | ||
== البُنية == | == البُنية == | ||
[[ملف:composit-indexed.png|بديل=|تصغير|(ش.4) البُنية]] | |||
# تصف واجهةُ | # تصف واجهةُ العنصر (Component) العمليات المشتركةَ في كل من العناصر البسيطة والمعقدة في الشجرة. | ||
# الورقة (Leaf) هي عنصر بسيط في الشجرة ليس له عناصر فرعية، وعادة ما تنفذ الورقة أغلب المهام الحقيقية بما أنها ليس لها عناصر تحتها تفوض إليه تلك المهام. | # الورقة (Leaf) هي عنصر بسيط في الشجرة ليس له عناصر فرعية، وعادة ما تنفذ الورقة أغلب المهام الحقيقية بما أنها ليس لها عناصر تحتها تفوض إليه تلك المهام. | ||
# الحاوية (المركَّب أيضًا) هي عنصر في الشجرة تحته عناصر فرعية سواء كانت حاويات أخرى أو أوراقًا، ولا تعرف الحاوية الفئات الحقيقية لما تحتها من فروع، وهي تعمل مع كل العناصر الفرعية من خلال واجهة | # الحاوية (المركَّب أيضًا) هي عنصر في الشجرة تحته عناصر فرعية سواء كانت حاويات أخرى أو أوراقًا، ولا تعرف الحاوية الفئات الحقيقية لما تحتها من فروع، وهي تعمل مع كل العناصر الفرعية من خلال واجهة العنصر فقط، وتفوض العمل إلى عناصرها الفرعية ثم تعالج النتائج الأولية وتعيد النتيجة النهائية للعميل. | ||
# يعمل العميل مع كل العناصر من خلال واجهة | # يعمل العميل مع كل العناصر من خلال واجهة العنصر، ويستطيع العمل نتيجة لهذا بنفس الطريقة مع العناصر البسيطة والمعقدة من الشجرة. | ||
== مثال توضيحي == | == مثال توضيحي == | ||
[[ملف:composit example .png|بديل=|بدون|تصغير|(ش.5) مثال محرر الأشكال الهندسية.]] | |||
يسمح لك نمط المركَّب في هذا المثال بتنفيذ تكديسات لأشكال هندسية (Geometric) داخل محرِّر مرئي. | يسمح لك نمط المركَّب في هذا المثال بتنفيذ تكديسات لأشكال هندسية (Geometric) داخل محرِّر مرئي. | ||
فئة <code>CompoundGraphic</code> هي الحاوية التي تشمل أي عدد من الأشكال الفرعية بما في ذلك الأشكال المجمَّعة الأخرى، والشكل المجمَّع له نفس الأساليب كالشكل البسيط، لكن بدلًا من فعل أي شيء بنفسه فإنه -أي المجمَّع- يمرر الطلب بشكل تكراري إلى كل فروعه ويجمع النتيجة في النهاية. | فئة <code>CompoundGraphic</code> هي الحاوية التي تشمل أي عدد من الأشكال الفرعية بما في ذلك الأشكال المجمَّعة الأخرى، والشكل المجمَّع له نفس الأساليب كالشكل البسيط، لكن بدلًا من فعل أي شيء بنفسه فإنه -أي المجمَّع- يمرر الطلب بشكل تكراري إلى كل فروعه ويجمع النتيجة في النهاية. | ||
وتعمل شيفرة العميل مع الأشكال كلها من خلال واجهة واحدة مشتركة بين كل فئات الأشكال، لهذا لا يعرف العميل ما إن كان يعمل مع شكل بسيط أو مجمع، ويعمل العميل لهذا أيضًا مع هياكل لأشكال معقدة جدًا دون التقيد بالفئات الحقيقية التي تكوِّن تلك الهياكل.<syntaxhighlight lang="java"> | وتعمل شيفرة العميل مع الأشكال كلها من خلال واجهة واحدة مشتركة بين كل فئات الأشكال، لهذا لا يعرف العميل ما إن كان يعمل مع شكل بسيط أو مجمع، ويعمل العميل لهذا أيضًا مع هياكل لأشكال معقدة جدًا دون التقيد بالفئات الحقيقية التي تكوِّن تلك الهياكل.<syntaxhighlight lang="java"> | ||
// تصرح واجهة | |||
// تصرح واجهة العنصر عن عمليات مشتركة للأشكال المجمعة والبسيطة لمركب ما. | |||
interface Graphic is | interface Graphic is | ||
method move(x, y) | method move(x, y) | ||
سطر 54: | سطر 51: | ||
// Y و X ارسم نقطة عند. | // Y و X ارسم نقطة عند. | ||
// جميع فئات | // جميع فئات العنصر تستطيع توسعة العناصر العناصر الأخرى. | ||
class Circle extends Dot is | class Circle extends Dot is | ||
field radius | field radius | ||
سطر 63: | سطر 60: | ||
// R وبنصف قطر Y و X ارسم دائرة عند. | // R وبنصف قطر Y و X ارسم دائرة عند. | ||
// تمثل فئة المركب | // تمثل فئة المركب عناصرً معقدة قد تكون لها كائنات فرعية، وتفوض كائناتُ المركبُ المهامَ عادة لما دونها | ||
// ثم تجمع النتيجة. | // ثم تجمع النتيجة. | ||
class CompoundGraphic implements Graphic is | class CompoundGraphic implements Graphic is | ||
field children: array of Graphic | field children: array of Graphic | ||
// يمكن أن يضيف كائن المركب أو يحذف | // يمكن أن يضيف كائن المركب أو يحذف عناصر أخرى -بسيطة أومعقدة- إلى أو من قائمته الفرعية. | ||
method add(child: Graphic) is | method add(child: Graphic) is | ||
// إضافة | // إضافة عنصر فرعي إلى مصفوفة الفروع. | ||
method remove(child: Graphic) is | method remove(child: Graphic) is | ||
// حذف | // حذف عنصر فرعي إلى مصفوفة الفروع. | ||
method move(x, y) is | method move(x, y) is | ||
سطر 82: | سطر 79: | ||
// المركب تمرر استدعاءتها إلى فروعها فإن المركب يمر على الشجرة كلها. | // المركب تمرر استدعاءتها إلى فروعها فإن المركب يمر على الشجرة كلها. | ||
method draw() is | method draw() is | ||
// 1. لكل | // 1. لكل عنصر فرعي | ||
// - ارسم | // - ارسم العنصر | ||
// - (Bounding Rectangle) حدِّث المستطيل المحدِّد | // - (Bounding Rectangle) حدِّث المستطيل المحدِّد | ||
// 2. (Bounding Coordinates) باستخدام إحداثيات الحدود (Dashes) ارسم مستطيلًا خطه مكون من شُرط مقطعة . | // 2. (Bounding Coordinates) باستخدام إحداثيات الحدود (Dashes) ارسم مستطيلًا خطه مكون من شُرط مقطعة . | ||
// تعمل شيفرة العميل مع كل | // تعمل شيفرة العميل مع كل العناصر من خلال واجهتها الأساسية، وهكذا تستطيع شيفرة العميل أن تدعم عناصر الورقة | ||
// البسيطة والمركبات المعقدة على حد سواء. | // البسيطة والمركبات المعقدة على حد سواء. | ||
class ImageEditor is | class ImageEditor is | ||
سطر 97: | سطر 94: | ||
// ... | // ... | ||
// اجمع | // اجمع العناصر المختارة في عنصرٍ مركب معقد واحد. | ||
method groupSelected(components: array of Graphic) is | method groupSelected(components: array of Graphic) is | ||
group = new CompoundGraphic() | group = new CompoundGraphic() | ||
سطر 103: | سطر 100: | ||
all.remove(components) | all.remove(components) | ||
all.add(group) | all.add(group) | ||
// ستُرسم كل | // ستُرسم كل العناصر. | ||
all.draw() | all.draw() | ||
</syntaxhighlight> | </syntaxhighlight> | ||
سطر 115: | سطر 114: | ||
== كيفية الاستخدام == | == كيفية الاستخدام == | ||
# تأكد أن النموذج الأساسي لتطبيقك يمكن تمثيله بهيكل شجري، ثم جزّء ذلك إلى عناصر بسيطة وحاويات. تذكر أن الحاويات يجب أن تكون قادرة على احتواء العناصر البسيطة إضافة إلى الحاويات الأخرى كذلك. | # تأكد أن النموذج الأساسي لتطبيقك يمكن تمثيله بهيكل شجري، ثم جزّء ذلك إلى عناصر بسيطة وحاويات. تذكر أن الحاويات يجب أن تكون قادرة على احتواء العناصر البسيطة إضافة إلى الحاويات الأخرى كذلك. | ||
# صرِّح عن واجهة | # صرِّح عن واجهة العنصر مع قائمة أساليب تصلح للعناصر البسيطة والحاويات المعقدة على حد سواء. | ||
# أنشئ فئة ورقة (Leaf) لتمثل العناصر البسيطة، قد يحتوي البرنامج فئات مختلفة للأوراق. | # أنشئ فئة ورقة (Leaf) لتمثل العناصر البسيطة، قد يحتوي البرنامج فئات مختلفة للأوراق. | ||
# أنشئ فئة حاوية (Container) لتمثل العناصر المعقدة في تلك الفئة، وأضف حقل مصفوفة لتخزين المراجع إلى العناصر الفرعية، يجب أن تكون المصفوفة قادرة على تخزين الأوراق والحاويات، لذا تأكد أنها مصرح عنها مع نوع واجهة | # أنشئ فئة حاوية (Container) لتمثل العناصر المعقدة في تلك الفئة، وأضف حقل مصفوفة لتخزين المراجع إلى العناصر الفرعية، يجب أن تكون المصفوفة قادرة على تخزين الأوراق والحاويات، لذا تأكد أنها مصرح عنها مع نوع واجهة العنصر. تذكر أثناء تنفيذ أساليب واجهة العنصر أن الحاوية يفترض بها تفويض أغلب مهامها إلى العناصر الفرعية لها. | ||
# أخيرًا، عرّف أساليب إضافة وحذف العناصر الفرعية في الحاوية. | # أخيرًا، عرّف أساليب إضافة وحذف العناصر الفرعية في الحاوية. | ||
لاحظ أن هذه العمليات يمكن التصريح عنها في واجهة | لاحظ أن هذه العمليات يمكن التصريح عنها في واجهة العنصر (Component Interface)، وذاك قد يخرق مبدأ عزل الواجهة (Interface Segregation Principle) لأن الأساليب ستكون فارغة في فئة الورقة، لكن العميل سيتمكن من معاملة كل العناصر بالتساوي حتى عند تركيب (Composing) الشجرة. | ||
== المزايا والعيوب == | == المزايا والعيوب == | ||
سطر 129: | سطر 128: | ||
=== العيوب === | === العيوب === | ||
* قد يكون من الصعب توفير واجهة مشتركة للفئات التي تختلف وظائفها عن بعضها بشكل كبير، فستحتاج في حالات معينة إلى المبالغة في تعميم واجهة | * قد يكون من الصعب توفير واجهة مشتركة للفئات التي تختلف وظائفها عن بعضها بشكل كبير، فستحتاج في حالات معينة إلى المبالغة في تعميم واجهة العنصر إلى الحد الذي سيجعلها تستعصي على الفهم. | ||
== العلاقات مع الأنماط الأخرى == | == العلاقات مع الأنماط الأخرى == | ||
* يمكنك استخدام نمط [https://refactoring.guru/design-patterns/builder الباني] عند إنشاء أشجار مركبات معقدة لأنك تستطيع برمجة خطوات هيكلتها لتعمل بتكرارية. | * يمكنك استخدام نمط [https://refactoring.guru/design-patterns/builder الباني] عند إنشاء أشجار مركبات معقدة لأنك تستطيع برمجة خطوات هيكلتها لتعمل بتكرارية. | ||
* تُستخدم [[Design Patterns/chain of responsibility|سلسلة المسؤوليات]] عادة بالتزامن مع المركَّب، وفي تلك الحالة تمرر الورقة الطلب الذي تحصل عليه خلال | * تُستخدم [[Design Patterns/chain of responsibility|سلسلة المسؤوليات]] عادة بالتزامن مع المركَّب، وفي تلك الحالة تمرر الورقة الطلب الذي تحصل عليه خلال العناصر التي تعلوها في السلسلة الهرمية حتى تصل إلى جذر شجرة الكائنات. | ||
* يمكنك استخدام [[Design Patterns/iterator|المكررات]] لاجتياز أشجار المركبات. | * يمكنك استخدام [[Design Patterns/iterator|المكررات]] لاجتياز أشجار المركبات. | ||
* يمكنك استخدام نمط [https://refactoring.guru/design-patterns/visitor الزائر] لتنفيذ عملية على شجرة مركب بكاملها. | * يمكنك استخدام نمط [https://refactoring.guru/design-patterns/visitor الزائر] لتنفيذ عملية على شجرة مركب بكاملها. | ||
* يمكنك تنفيذ عُقد ورقية مشتركة (Shared Leaf Nodes) لشجرة المركب لتكوّن أنماطًا من نوع [[Design Patterns/flyweight|وزن الذبابة]]، من أجل توفير الذاكرة العشوائية RAM. | * يمكنك تنفيذ عُقد ورقية مشتركة (Shared Leaf Nodes) لشجرة المركب لتكوّن أنماطًا من نوع [[Design Patterns/flyweight|وزن الذبابة]]، من أجل توفير الذاكرة العشوائية RAM. | ||
* لنمطيْ المركب [[Design Patterns/decorator|والمزخرِف]] هياكل متشابهة بما أنهما يعتمدان على التركيب التكراري لتنظيم عدد مفتوح النهاية (open-ended) من الكائنات. | * لنمطيْ المركب [[Design Patterns/decorator|والمزخرِف]] هياكل متشابهة بما أنهما يعتمدان على التركيب التكراري لتنظيم عدد مفتوح النهاية (open-ended) من الكائنات. | ||
* [[Design Patterns/decorator|المزخرف]] مثل المركب لكنه لا يملك إلا | * [[Design Patterns/decorator|المزخرف]] مثل المركب لكنه لا يملك إلا عنصرًا فرعيًا واحدًا، ويضيف المزخرف مسؤوليات إضافية إلى الكائن المُغلَّف، بينما ما يقوم به المركَّب هو جمع نتائج الفروع فقط. لكن بأي حال فإن هذه الأنماط تتعاون فيما بينها، فتستطيع استخدام المزخرِف لتوسيع سلوك كائن معين في شجرة مركَّب ما. | ||
* التصميمات التي تستخدم المركب والمزخرف بكثرة يمكنها الاستفادة من نمط [[Design Patterns/prototype|النموذج الأولي]]، إذ استخدامه يسمح باستنساخ هياكل معقدة بدلًا من إعادة بنائهم من الصفر. | * التصميمات التي تستخدم المركب والمزخرف بكثرة يمكنها الاستفادة من نمط [[Design Patterns/prototype|النموذج الأولي]]، إذ استخدامه يسمح باستنساخ هياكل معقدة بدلًا من إعادة بنائهم من الصفر. | ||
سطر 146: | سطر 145: | ||
'''الانتشار''': ★ ★ ★ | '''الانتشار''': ★ ★ ★ | ||
'''أمثلة الاستخدام''': يكثر استخدام نمط المركَّب في لغة بايثون، إذ يُستخدم لتمثيل هرميات | '''أمثلة الاستخدام''': يكثر استخدام نمط المركَّب في لغة بايثون، إذ يُستخدم لتمثيل هرميات لعناصر من واجهة المستخدم أو شيفرة تعمل مع الرسوم التوضيحية (Graphs). إليك بعض الأمثلة على نمط المركب من مكتبات جافا قياسية: | ||
* [http://docs.oracle.com/javase/8/docs/api/java/awt/Container.html#add-java.awt.Component- (java.awt.Container#add(Component] تقريبًا في كل | * [http://docs.oracle.com/javase/8/docs/api/java/awt/Container.html#add-java.awt.Component- (java.awt.Container#add(Component] تقريبًا في كل عناصر واجهة Swing. | ||
* [http://docs.oracle.com/javaee/7/api/javax/faces/component/UIComponent.html#getChildren-- ()javax.faces.component.UIComponent#getChildren] تقريبًا في كل | * [http://docs.oracle.com/javaee/7/api/javax/faces/component/UIComponent.html#getChildren-- ()javax.faces.component.UIComponent#getChildren] تقريبًا في كل عناصر واجهات JSF. | ||
يمكن ملاحظة نمط المركب من خلال الأساليب السلوكية التي تأخذ نسخة من نفس النوع التجريدي/التنفيذي إلى هيكل شجري. | يمكن ملاحظة نمط المركب من خلال الأساليب السلوكية التي تأخذ نسخة من نفس النوع التجريدي/التنفيذي إلى هيكل شجري. | ||
سطر 632: | سطر 631: | ||
==== OutputDemo.png: نتائج التنفيذ ==== | ==== OutputDemo.png: نتائج التنفيذ ==== | ||
[[ملف:dpcj.OutputDemo.png]] | |||
== الاستخدام في لغة #C == | == الاستخدام في لغة #C == | ||
سطر 639: | سطر 638: | ||
'''الانتشار''': ★ ★ ★ | '''الانتشار''': ★ ★ ★ | ||
'''أمثلة الاستخدام''': يكثر استخدام نمط المركَّب في لغة #C، إذ يُستخدم لتمثيل هرميات | '''أمثلة الاستخدام''': يكثر استخدام نمط المركَّب في لغة #C، إذ يُستخدم لتمثيل هرميات لعناصر من واجهة المستخدم أو شيفرة تعمل مع الرسوم التوضيحية (Graphs). | ||
يمكن ملاحظة نمط المركب من خلال الأساليب السلوكية التي تأخذ نسخة من نفس النوع التجريدي/التنفيذي إلى هيكل شجري. | يمكن ملاحظة نمط المركب من خلال الأساليب السلوكية التي تأخذ نسخة من نفس النوع التجريدي/التنفيذي إلى هيكل شجري. | ||
سطر 656: | سطر 655: | ||
namespace RefactoringGuru.DesignPatterns.Composite.Conceptual | namespace RefactoringGuru.DesignPatterns.Composite.Conceptual | ||
{ | { | ||
// تصرح فئة | // تصرح فئة العنصر الأساسي عن العمليات المشتركة للكائنات البسيطة والمعقدة لمركب ما. | ||
abstract class Component | abstract class Component | ||
{ | { | ||
public Component() { } | public Component() { } | ||
// قد ينفذ | // قد ينفذ العنصر الأساسي بعض السلوك الافتراضي أو لا ينفذه للفئات الحقيقية من خلال التصريح عن الأسلوب | ||
// الحاوي للسلوك على أنه تجريدي. | // الحاوي للسلوك على أنه تجريدي. | ||
public abstract string Operation(); | public abstract string Operation(); | ||
// قد يفيد في بعض الحالات أن تعرّف عمليات إدارة الفروع في فئة | // قد يفيد في بعض الحالات أن تعرّف عمليات إدارة الفروع في فئة العنصر الأساسي مباشرة، وهكذا لن تحتاج إلى | ||
// كشف الفئات الحقيقية | // كشف الفئات الحقيقية للعناصر إلى شيفرة العميل حتى أثناء تجميع الشجرة. لكن المشكلة هنا هي أن هذه | ||
// الأساليب ستكون فارغة | // الأساليب ستكون فارغة للعناصر التي في مستوى الورقة. | ||
public virtual void Add(Component component) | public virtual void Add(Component component) | ||
{ | { | ||
سطر 678: | سطر 677: | ||
} | } | ||
// تستطيع إضافة أسلوب يسمح لشيفرة العميل أن تعرف إن كان | // تستطيع إضافة أسلوب يسمح لشيفرة العميل أن تعرف إن كان عنصر ما يستطيع استيعاب فروع له. | ||
public virtual bool IsComposite() | public virtual bool IsComposite() | ||
{ | { | ||
سطر 704: | سطر 703: | ||
} | } | ||
// تمثل فئة المركب | // تمثل فئة المركب العناصر المعقدة التي قد تحتوي على فروع، وعادة ما تفوض | ||
// كائنات المركب الأعمال الحقيقية لما دونها ثم تجمع النتيجة النهائية. | // كائنات المركب الأعمال الحقيقية لما دونها ثم تجمع النتيجة النهائية. | ||
{ | { | ||
سطر 742: | سطر 741: | ||
class Client | class Client | ||
{ | { | ||
// تعمل شيفرة العميل مع كل | // تعمل شيفرة العميل مع كل العناصر من خلال الواجهة الأساسية. | ||
public void ClientCode(Component leaf) | public void ClientCode(Component leaf) | ||
{ | { | ||
سطر 748: | سطر 747: | ||
} | } | ||
// تستطيع شيفرة العميل أن تعمل مع أي | // تستطيع شيفرة العميل أن تعمل مع أي عنصر بسيط أو معقد دون الاعتماد على الفئات الحقيقية للعناصر | ||
// وذلك بفضل كون التصريح عن عمليات إدارة الفروع في فئة | // وذلك بفضل كون التصريح عن عمليات إدارة الفروع في فئة العنصر الأساسي. | ||
public void ClientCode2(Component component1, Component component2) | public void ClientCode2(Component component1, Component component2) | ||
{ | { | ||
سطر 767: | سطر 766: | ||
Client client = new Client(); | Client client = new Client(); | ||
// تستطيع شيفرة العميل بهذه الطريقة أن تدعم | // تستطيع شيفرة العميل بهذه الطريقة أن تدعم عناصر الورقة البسيطة. | ||
Leaf leaf = new Leaf(); | Leaf leaf = new Leaf(); | ||
Console.WriteLine("Client: I get a simple component:"); | Console.WriteLine("Client: I get a simple component:"); | ||
سطر 789: | سطر 788: | ||
} | } | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
==== Output.txt: المخرجات ==== | ==== Output.txt: المخرجات ==== | ||
<syntaxhighlight> | <syntaxhighlight lang="text"> | ||
Client: I get a simple component: | Client: I get a simple component: | ||
RESULT: Leaf | RESULT: Leaf | ||
سطر 808: | سطر 810: | ||
'''الانتشار''': ★ ★ ★ | '''الانتشار''': ★ ★ ★ | ||
'''أمثلة الاستخدام''': يكثر استخدام نمط المركَّب في لغة PHP عند التعامل مع أشجار الكائنات، وأبسط مثال له قد يكون تطبيقه على عناصر شجرة DOM لتعمل مع نمط المركب والعناصر البسيطة للشجرة بنفس الأسلوب. | '''أمثلة الاستخدام''': يكثر استخدام نمط المركَّب في لغة [[PHP]] عند التعامل مع أشجار الكائنات، وأبسط مثال له قد يكون تطبيقه على عناصر شجرة DOM لتعمل مع نمط المركب والعناصر البسيطة للشجرة بنفس الأسلوب. | ||
=== مثال: مثال تصوري === | === مثال: مثال تصوري === | ||
سطر 823: | سطر 825: | ||
/** | /** | ||
* | * تصرح فئة العنصر الأساسي عن العمليات المشتركة للكائنات البسيطة والمعقدة لمركب ما. | ||
*/ | */ | ||
abstract class Component | abstract class Component | ||
سطر 834: | سطر 835: | ||
/** | /** | ||
* | * لفئة العنصر الأساسي أن تصرح عن واجهة لإعداد أحد الأصول لعنصر ما والوصول إليه في هيكل شجري | ||
* | * كما تضيف بعض الاستخدامات الافتراضية لهذه الأساليب. | ||
*/ | */ | ||
public function setParent(Component $parent) | public function setParent(Component $parent) | ||
سطر 849: | سطر 849: | ||
/** | /** | ||
* | * قد يفيد في بعض الحالات أن تعرّف عمليات إدارة الفروع في فئة العنصر الأساسي مباشرة، وهكذا لن تحتاج | ||
* | * إلى كشف الفئات الحقيقية للعناصر إلى شيفرة العميل حتى أثناء تجميع الشجرة. لكن المشكلة هنا هي أن هذه | ||
* | * الأساليب ستكون فارغة للعناصر التي في مستوى الورقة. | ||
*/ | */ | ||
public function add(Component $component): void { } | public function add(Component $component): void { } | ||
سطر 860: | سطر 858: | ||
/** | /** | ||
* | * تستطيع إضافة أسلوب يسمح لشيفرة العميل أن تعرف إن كان عنصر ما يستطيع استيعاب فروع له | ||
*/ | */ | ||
public function isComposite(): bool | public function isComposite(): bool | ||
سطر 869: | سطر 866: | ||
/** | /** | ||
* | * قد ينفذ العنصر الأساسي بعض السلوك الافتراضي أو لا ينفذه للفئات الحقيقية من خلال التصريح عن الأسلوب | ||
* | * الحاوي للسلوك على أنه تجريدي. | ||
*/ | */ | ||
abstract public function operation(): string; | abstract public function operation(): string; | ||
سطر 877: | سطر 873: | ||
/** | /** | ||
* | * تمثل فئة الورقةُ الكائناتِ النهائيةَ لمركب ما، فلا يمكن لكائن الورقة | ||
* | * أن يحتوي على كائنات فرعية | ||
* | * | ||
* | * عادة ما تنفذ كائنات الأوراق المهام التي توكل إليها بينما تفوض | ||
* | * الكائنات المركبة تلك المهام إلى ما دونها. | ||
*/ | */ | ||
class Leaf extends Component | class Leaf extends Component | ||
سطر 892: | سطر 888: | ||
/** | /** | ||
* | * تمثل فئة المركب العناصر المعقدة التي قد تحتوي على فروع، وعادة ما تفوض | ||
* | * كائنات المركب الأعمال الحقيقية لما دونها ثم تجمع النتيجة النهائية. | ||
*/ | */ | ||
class Composite extends Component | class Composite extends Component | ||
سطر 909: | سطر 904: | ||
/** | /** | ||
* | * يستطيع الكائن المركب أن يضيف أو يحذف عناصرً أخرى -بسيطة أو معقدة- من قائمة الفروع. | ||
*/ | */ | ||
public function add(Component $component): void | public function add(Component $component): void | ||
سطر 930: | سطر 924: | ||
/** | /** | ||
* | * ينفذ المركب منطقه الأساسي بأن يمر على فروعه كلها بشكل تكراري جامعًا نتائجها، وبما أن فروع | ||
* | * المركب تمرر استدعاءاتها لما دونها فإن الشجرة بكاملها تُفحص نتيجة لهذا. | ||
*/ | |||
public function operation(): string | public function operation(): string | ||
{ | { | ||
سطر 947: | سطر 939: | ||
/** | /** | ||
* | * تعمل شيفرة العميل مع كل العناصر من خلال الواجهة الأساسية. | ||
*/ | */ | ||
function clientCode(Component $component) | function clientCode(Component $component) | ||
سطر 959: | سطر 951: | ||
/** | /** | ||
* | * تستطيع شيفرة العميل بهذه الطريقة أن تدعم عناصر الورقة البسيطة. | ||
*/ | */ | ||
$simple = new Leaf; | $simple = new Leaf; | ||
سطر 967: | سطر 959: | ||
/** | /** | ||
* ... | * ... إضافة إلى المركبات المعقدة. | ||
*/ | */ | ||
$tree = new Composite; | $tree = new Composite; | ||
سطر 982: | سطر 974: | ||
/** | /** | ||
* | * تستطيع شيفرة العميل أن تعمل مع أي عنصر بسيط أو معقد دون الاعتماد على الفئات الحقيقية | ||
* | * للعناصر، وذلك بفضل كون التصريح عن عمليات إدارة الفروع في فئة العنصر الأساسي. | ||
*/ | */ | ||
function clientCode2(Component $component1, Component $component2) | function clientCode2(Component $component1, Component $component2) | ||
سطر 1٬000: | سطر 991: | ||
echo "Client: I don't need to check the components classes even when managing the tree:\n"; | echo "Client: I don't need to check the components classes even when managing the tree:\n"; | ||
clientCode2($tree, $simple); | clientCode2($tree, $simple); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
==== Output.txt: المخرجات ==== | ==== Output.txt: المخرجات ==== | ||
<syntaxhighlight> | <syntaxhighlight lang="text"> | ||
Client: I get a simple component: | Client: I get a simple component: | ||
RESULT: Leaf | RESULT: Leaf | ||
سطر 1٬028: | سطر 1٬021: | ||
/** | /** | ||
* | * تصرح فئة العنصر الأساسي عن واجهة لكل العناصر الحقيقية سواء البسيطة أو المعقدة. | ||
* DOM لعناصر (Rendering Behaviour) وسنركز في مثالنا على السلوك الإخراجي | |||
* | |||
*/ | */ | ||
abstract class FormElement | abstract class FormElement | ||
{ | { | ||
/** | /** | ||
* | * تتطلب هذه الحقول الثلاثة DOM نستطيع توقع أن كل عناصر | ||
*/ | */ | ||
protected $name; | protected $name; | ||
سطر 1٬064: | سطر 1٬055: | ||
/** | /** | ||
* | * حقيقي بتنفيذه الإخراجي، لكن يمكننا الافتراض DOM يجب أن يزودنا كل عنصر | ||
* | * (Strings) أنها جميعًا ستعيد مقاطع نصية | ||
*/ | */ | ||
abstract public function render(): string; | abstract public function render(): string; | ||
سطر 1٬071: | سطر 1٬062: | ||
/** | /** | ||
* | * هذا عنصر من نوع الورقة، وككل عناصر الأوراق فإنه لا يملك أي فروع له. | ||
*/ | */ | ||
class Input extends FormElement | class Input extends FormElement | ||
سطر 1٬084: | سطر 1٬075: | ||
/** | /** | ||
* | * بما أن الأوراق ليس لها فروع تفوض إليها المهام فإنها تنفذ المهام التي توكل إليها بأنفسها | ||
* | * في نمط المركب. | ||
*/ | */ | ||
public function render(): string | public function render(): string | ||
سطر 1٬096: | سطر 1٬086: | ||
/** | /** | ||
* | * تنفذ الفئةُ الأساسيةُ للمركب البنيةَ التحتيةَ لإدارة الكائنات الفرعية التي يعاد استخدامها | ||
* | * بواسطة كل المركبات الحقيقية. | ||
* | |||
*/ | */ | ||
abstract class FieldComposite extends FormElement | abstract class FieldComposite extends FormElement | ||
سطر 1٬107: | سطر 1٬098: | ||
/** | /** | ||
* | * أساليب إضافة أو حذف الكائنات الفرعية. | ||
*/ | */ | ||
public function add(FormElement $field): void | public function add(FormElement $field): void | ||
سطر 1٬123: | سطر 1٬114: | ||
/** | /** | ||
* | * يجب أن يضع أسلوب المركب كائناته الفرعية في حساباته في حين أن أسلوب الورقة عادة ما ينفذ | ||
* | * المهام بنفسه، ويستطيع المركب في تلك الحالة أن يقبل البيانات المهيكلة. | ||
* @param array $data | * @param array $data | ||
*/ | */ | ||
سطر 1٬140: | سطر 1٬128: | ||
/** | /** | ||
* | * الذي يعيد البيانات المهيكلة من المركب نفسه إن وجد (Getter) ينطبق نفس المنطق على المستخلِص | ||
* | * وكل بيانات الفروع. | ||
*/ | */ | ||
public function getData(): array | public function getData(): array | ||
سطر 1٬155: | سطر 1٬143: | ||
/** | /** | ||
* | * التنفيذ الأساسي لإخراج المركب يجمع نتائج كل فروعه، وستكون المركبات الحقيقية قادرة على إعادة | ||
* | * استخدام هذا التنفيذ في استخدامات حقيقية. | ||
*/ | */ | ||
public function render(): string | public function render(): string | ||
سطر 1٬172: | سطر 1٬159: | ||
/** | /** | ||
* | * هو مركب حقيقي fieldset عنصر. | ||
*/ | */ | ||
class Fieldset extends FieldComposite | class Fieldset extends FieldComposite | ||
سطر 1٬178: | سطر 1٬165: | ||
public function render(): string | public function render(): string | ||
{ | { | ||
// | // fieldset لاحظ كيف تُدمج نتيجة الإخراج المُجمع للفروع في وسم | ||
$output = parent::render(); | $output = parent::render(); | ||
سطر 1٬187: | سطر 1٬173: | ||
/** | /** | ||
* | * form وكذا عنصر . | ||
*/ | */ | ||
class Form extends FieldComposite | class Form extends FieldComposite | ||
سطر 1٬207: | سطر 1٬193: | ||
/** | /** | ||
* | * تحصل شيفرة العميل على واجهة مريحة لبناء هياكل شجرية معقدة. | ||
*/ | */ | ||
function getProductForm(): FormElement | function getProductForm(): FormElement | ||
سطر 1٬225: | سطر 1٬210: | ||
/** | /** | ||
* | * يمكن ملء هياكل الاستمارات ببيانات من مصادر مختلفة، وليس على العميل أن يفحص كل حقول الاستمارات | ||
* | * لتعيين بيانات إلى الحقول المختلفة بما أن الاستمارة نفسها يمكنها معالجة ذلك. | ||
*/ | */ | ||
function loadProductData(FormElement $form) | function loadProductData(FormElement $form) | ||
سطر 1٬244: | سطر 1٬228: | ||
/** | /** | ||
* | * تستطيع شيفرة العميل أن تعمل مع عناصر الاستمارات باستخدام الواجهة المجردة، وهكذا لا يهم | ||
* | * إن كان العميل يعمل مع عناصر بسيطة أو شجرة مركب معقدة. | ||
*/ | */ | ||
function renderProduct(FormElement $form) | function renderProduct(FormElement $form) | ||
سطر 1٬260: | سطر 1٬243: | ||
loadProductData($form); | loadProductData($form); | ||
renderProduct($form); | renderProduct($form); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
==== Output.txt: المخرجات ==== | ==== Output.txt: المخرجات ==== | ||
<syntaxhighlight> | <syntaxhighlight lang="text"> | ||
<form action="/product/add"> | <form action="/product/add"> | ||
<h3>Add product</h3> | <h3>Add product</h3> | ||
سطر 1٬284: | سطر 1٬269: | ||
'''الانتشار''': ★ ★ ★ | '''الانتشار''': ★ ★ ★ | ||
'''أمثلة الاستخدام''': يكثر استخدام نمط المركَّب في لغة بايثون، إذ يُستخدم لتمثيل هرميات | '''أمثلة الاستخدام''': يكثر استخدام نمط المركَّب في لغة بايثون، إذ يُستخدم لتمثيل هرميات لعناصر من واجهة المستخدم أو شيفرة تعمل مع الرسوم التوضيحية (Graphs). | ||
يمكن ملاحظة نمط المركب من خلال الأساليب السلوكية التي تأخذ نسخة من نفس النوع التجريدي/التنفيذي إلى هيكل شجري. | يمكن ملاحظة نمط المركب من خلال الأساليب السلوكية التي تأخذ نسخة من نفس النوع التجريدي/التنفيذي إلى هيكل شجري. | ||
سطر 1٬303: | سطر 1٬288: | ||
class Component(ABC): | class Component(ABC): | ||
""" | """ | ||
تصرح فئة العنصر الأساسي عن العمليات المشتركة للكائنات البسيطة والمعقدة لمركب ما. | |||
""" | """ | ||
سطر 1٬314: | سطر 1٬298: | ||
def parent(self, parent: Component): | def parent(self, parent: Component): | ||
""" | """ | ||
لفئة العنصر الأساسي أن تصرح عن واجهة لإعداد أحد الأصول لعنصر ما والوصول إليه في هيكل شجري | |||
كما تضيف بعض الاستخدامات الافتراضية لهذه الأساليب. | |||
""" | """ | ||
سطر 1٬322: | سطر 1٬305: | ||
""" | """ | ||
قد يفيد في بعض الحالات أن تعرّف عمليات إدارة الفروع في فئة العنصر الأساسي مباشرة، وهكذا لن تحتاج | |||
إلى كشف الفئات الحقيقية للعناصر إلى شيفرة العميل حتى أثناء تجميع الشجرة. لكن المشكلة هنا هي أن | |||
هذه الأساليب ستكون فارغة للعناصر التي في مستوى الورقة. | |||
""" | """ | ||
سطر 1٬337: | سطر 1٬318: | ||
def is_composite(self) -> bool: | def is_composite(self) -> bool: | ||
""" | """ | ||
تستطيع إضافة أسلوب يسمح لشيفرة العميل أن تعرف إن كان عنصر ما يستطيع استيعاب فروع له | |||
""" | """ | ||
سطر 1٬346: | سطر 1٬326: | ||
def operation(self) -> str: | def operation(self) -> str: | ||
""" | """ | ||
قد ينفذ العنصر الأساسي بعض السلوك الافتراضي أو لا ينفذه للفئات الحقيقية من خلال التصريح عن | |||
الأسلوب الحاوي له على أنه تجريدي. | |||
""" | """ | ||
سطر 1٬356: | سطر 1٬335: | ||
class Leaf(Component): | class Leaf(Component): | ||
""" | """ | ||
تمثل فئة الورقةُ الكائناتِ النهائيةَ لمركب ما، فلا يمكن لكائن الورقة أن يحتوي على كائنات فرعية | |||
عادة ما تنفذ كائنات الأوراق المهام التي توكل إليها بينما تفوض الكائنات المركبة تلك المهام إلى | |||
ما دونها. | |||
""" | """ | ||
سطر 1٬369: | سطر 1٬346: | ||
class Composite(Component): | class Composite(Component): | ||
""" | """ | ||
تمثل فئة المركب العناصر المعقدة التي قد تحتوي على فروع، وعادة ما تفوض | |||
كائنات المركب الأعمال الحقيقية لما دونها ثم تجمع النتيجة النهائية. | |||
""" | """ | ||
سطر 1٬378: | سطر 1٬354: | ||
""" | """ | ||
يستطيع الكائن المركب أن يضيف أو يحذف عناصرً أخرى -بسيطة أو معقدة- من قائمة الفروع. | |||
""" | """ | ||
سطر 1٬395: | سطر 1٬370: | ||
def operation(self) -> str: | def operation(self) -> str: | ||
""" | """ | ||
ينفذ المركب منطقه الأساسي بأن يمر على فروعه كلها بشكل تكراري جامعًا نتائجها، وبما أن فروع | |||
المركب تمرر استدعاءاتها لما دونها فإن الشجرة بكاملها تُفحص نتيجة لهذا. | |||
""" | """ | ||
سطر 1٬410: | سطر 1٬382: | ||
def client_code(component: Component) -> None: | def client_code(component: Component) -> None: | ||
""" | """ | ||
تعمل شيفرة العميل مع كل العناصر من خلال الواجهة الأساسية | |||
""" | """ | ||
سطر 1٬418: | سطر 1٬390: | ||
def client_code2(component1: Component, component2: Component) -> None: | def client_code2(component1: Component, component2: Component) -> None: | ||
""" | """ | ||
تستطيع شيفرة العميل أن تعمل مع أي عنصر بسيط أو معقد دون الاعتماد على الفئات الحقيقية | |||
للعناصر، وذلك بفضل كون التصريح عن عمليات إدارة الفروع في فئة العنصر الأساسي. | |||
""" | """ | ||
سطر 1٬430: | سطر 1٬401: | ||
if __name__ == "__main__": | if __name__ == "__main__": | ||
# | # تستطيع شيفرة العميل بهذه الطريقة أن تدعم عناصر الورقة البسيطة. | ||
simple = Leaf() | simple = Leaf() | ||
print("Client: I've got a simple component:") | print("Client: I've got a simple component:") | ||
سطر 1٬436: | سطر 1٬407: | ||
print("\n") | print("\n") | ||
# ... | # ... إضافة إلى المركبات المعقدة. | ||
tree = Composite() | tree = Composite() | ||
سطر 1٬455: | سطر 1٬426: | ||
print("Client: I don't need to check the components classes even when managing the tree:") | print("Client: I don't need to check the components classes even when managing the tree:") | ||
client_code2(tree, simple) | client_code2(tree, simple) | ||
</syntaxhighlight> | </syntaxhighlight> | ||
==== Output.txt: المخرجات ==== | ==== Output.txt: المخرجات ==== | ||
<syntaxhighlight> | <syntaxhighlight lang="text"> | ||
Client: I've got a simple component: | Client: I've got a simple component: | ||
RESULT: Leaf | RESULT: Leaf | ||
سطر 1٬468: | سطر 1٬441: | ||
RESULT: Branch(Branch(Leaf+Leaf)+Branch(Leaf)+Leaf) | RESULT: Branch(Branch(Leaf+Leaf)+Branch(Leaf)+Leaf) | ||
</syntaxhighlight> | |||
== الاستخدام في لغة روبي == | |||
'''المستوى''': ★ ★ ☆ | |||
'''الانتشار''': ★ ★ ☆ | |||
'''أمثلة الاستخدام''': يكثر استخدام نمط المركَّب في لغة بايثون، إذ يُستخدم لتمثيل هرميات لعناصر من واجهة المستخدم أو شيفرة تعمل مع الرسوم التوضيحية (Graphs). | |||
يمكن ملاحظة نمط المركب من خلال الأساليب السلوكية التي تأخذ نسخة من نفس النوع التجريدي/التنفيذي إلى هيكل شجري. | |||
=== مثال: مثال تصوري === | |||
يوضح هذا المثال بنية نمط الجسر، ويركز على إجابة الأسئلة التالية: | |||
* ما الفئات التي يتكون منها؟ | |||
* ما الأدوار التي تلعبها هذه الفئات؟ | |||
* كيف ترتبط عناصر النمط ببعضها؟ | |||
<syntaxhighlight lang="ruby"> | |||
# تصرح فئة العنصر الأساسي عن العمليات المشتركة للكائنات | |||
# البسيطة والمعقدة لمركب ما. | |||
class Component | |||
# @return [Component] | |||
def parent | |||
@parent | |||
end | |||
# لفئة العنصر الأساسي أن تصرح عن واجهة لإعداد أحد الأصول لعنصر ما | |||
# والوصول إليه في هيكل شجري كما تضيف بعض الاستخدامات | |||
# الافتراضية لهذه الأساليب. | |||
def parent=(parent) | |||
@parent = parent | |||
end | |||
# قد يفيد في بعض الحالات أن تعرّف عمليات إدارة الفروع في فئة العنصر الأساسي | |||
# مباشرة، وهكذا لن تحتاج إلى كشف الفئات الحقيقية للعناصر إلى شيفرة العميل | |||
# حتى أثناء تجميع الشجرة. لكن المشكلة هنا هي أن هذه الأساليب ستكون فارغة | |||
# للعناصر التي في مستوى الورقة. | |||
def add(component) | |||
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" | |||
end | |||
# @abstract | |||
# | |||
# @param [Component] component | |||
def remove(component) | |||
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" | |||
end | |||
# تستطيع إضافة أسلوب يسمح لشيفرة العميل أن تعرف إن كان | |||
# عنصر ما يستطيع استيعاب فروع له | |||
def composite? | |||
false | |||
end | |||
# قد يطبق العنصر الأساسي بعض السلوك الافتراضي أو لا يطبقه للفئات | |||
# الحقيقية من خلال التصريح عن الأسلوب الحاوي له على أنه تجريدي. | |||
def operation | |||
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'" | |||
end | |||
end | |||
# تمثل فئة الورقةُ الكائناتِ النهائيةَ لمركب ما، فلا يمكن | |||
# لكائن الورقة أن يحتوي على كائنات فرعية | |||
# | |||
# عادة ما تنفذ كائنات الأوراق المهام التي توكل إليها | |||
# بينما تفوض الكائنات المركبة تلك المهام إلى ما دونها. | |||
class Leaf < Component | |||
# return [String] | |||
def operation | |||
'Leaf' | |||
end | |||
end | |||
# تمثل فئة المركب العناصر المعقدة التي قد تحتوي على فروع، وعادة ما تفوض | |||
# كائنات المركب الأعمال الحقيقية لما دونها ثم تجمع النتيجة النهائية. | |||
class Composite < Component | |||
def initialize | |||
@children = [] | |||
end | |||
# يستطيع الكائن المركب أن يضيف أو يحذف عناصرً | |||
# أخرى -بسيطة أو معقدة- من قائمة الفروع. | |||
# @param [Component] component | |||
def add(component) | |||
@children.append(component) | |||
component.parent = self | |||
end | |||
# @param [Component] component | |||
def remove(component) | |||
@children.remove(component) | |||
component.parent = nil | |||
end | |||
# @return [Boolean] | |||
def composite? | |||
true | |||
end | |||
# ينفذ المركب منطقه الأساسي بأن يمر على فروعه كلها بشكل تكراري جامعًا | |||
# نتائجها، وبما أن فروع المركب تمرر استدعاءاتها لما دونها | |||
# فإن الشجرة بكاملها تُفحص نتيجة لهذا. | |||
def operation | |||
results = [] | |||
@children.each { |child| results.append(child.operation) } | |||
"Branch(#{results.join('+')})" | |||
end | |||
end | |||
# تعمل شيفرة العميل مع كل العناصر من خلال الواجهة الأساسية. | |||
def client_code(component) | |||
puts "RESULT: #{component.operation}" | |||
end | |||
# تستطيع شيفرة العميل أن تعمل مع أي عنصر بسيط أو معقد | |||
# دون الاعتماد على الفئات الحقيقية للعناصر، وذلك بفضل وجود التصريح عن | |||
# عمليات إدارة الفروع في فئة العنصر الأساسي | |||
def client_code2(component1, component2) | |||
component1.add(component2) if component1.composite? | |||
print "RESULT: #{component1.operation}" | |||
end | |||
# تستطيع شيفرة العميل بهذه الطريقة أن تدعم عناصر الورقة البسيطة.... | |||
simple = Leaf.new | |||
puts 'Client: I\'ve got a simple component:' | |||
client_code(simple) | |||
puts "\n" | |||
# ...إضافة إلى المركبات المعقدة. | |||
tree = Composite.new | |||
branch1 = Composite.new | |||
branch1.add(Leaf.new) | |||
branch1.add(Leaf.new) | |||
branch2 = Composite.new | |||
branch2.add(Leaf.new) | |||
tree.add(branch1) | |||
tree.add(branch2) | |||
puts 'Client: Now I\'ve got a composite tree:' | |||
client_code(tree) | |||
puts "\n" | |||
puts 'Client: I don\'t need to check the components classes even when managing the tree:' | |||
client_code2(tree, simple) | |||
</syntaxhighlight> | |||
==== output.txt: نتائج التنفيذ ==== | |||
<syntaxhighlight lang="text"> | |||
Client: I've got a simple component: | |||
RESULT: Leaf | |||
Client: Now I've got a composite tree: | |||
RESULT: Branch(Branch(Leaf+Leaf)+Branch(Leaf)) | |||
Client: I don't need to check the components classes even when managing the tree: | |||
RESULT: Branch(Branch(Leaf+Leaf)+Branch(Leaf)+Leaf) | |||
</syntaxhighlight> | |||
== الاستخدام في لغة Swift == | |||
'''المستوى''': ★ ★ ☆ | |||
'''الانتشار''': ★ ★ ★ | |||
'''أمثلة الاستخدام''': يكثر استخدام نمط المركَّب في لغة Swift، إذ يُستخدم لتمثيل هرميات لعناصر من واجهة المستخدم أو شيفرة تعمل مع الرسوم التوضيحية (Graphs). | |||
يمكن ملاحظة نمط المركب من خلال الأساليب السلوكية التي تأخذ نسخة من نفس النوع التجريدي/التنفيذي إلى هيكل شجري. | |||
=== مثال: مثال تصوري === | |||
يوضح هذا المثال بنية نمط الجسر، ويركز على إجابة الأسئلة التالية: | |||
* ما الفئات التي يتكون منها؟ | |||
* ما الأدوار التي تلعبها هذه الفئات؟ | |||
* كيف ترتبط عناصر النمط ببعضها؟ | |||
بعد تعلم بنية النمط سيكون من السهل عليك استيعاب المثال التالي المبني على حالة واقعية في لغة Swift. | |||
==== Example.swift: مثال تصوري ==== | |||
<syntaxhighlight lang="swift"> | |||
import XCTest | |||
/// تصرح فئة العنصر الأساسي عن العمليات المشتركة للكائنات | |||
/// البسيطة والمعقدة لمركب ما. | |||
protocol Component { | |||
/// لفئة العنصر الأساسي أن تصرح عن واجهة لإعداد أحد الأصول لعنصر ما والوصول | |||
/// إليه في هيكل شجري كما تضيف بعض الاستخدامات الافتراضية لهذه الأساليب. | |||
var parent: Component? { get set } | |||
/// قد يفيد في بعض الحالات أن تعرّف عمليات إدارة الفروع في فئة | |||
/// العنصر الأساسي مباشرة، وهكذا لن تحتاج إلى كشف الفئات الحقيقية | |||
/// للعناصر إلى شيفرة العميل حتى أثناء تجميع الشجرة. لكن المشكلة هنا | |||
/// هي أن هذه الأساليب ستكون فارغة للعناصر التي في مستوى الورقة. | |||
func add(component: Component) | |||
func remove(component: Component) | |||
/// تستطيع إضافة أسلوب يسمح لشيفرة العميل أن تعرف إن كان عنصر | |||
/// ما يستطيع استيعاب فروع له | |||
func isComposite() -> Bool | |||
/// قد يطبق العنصر الأساسي بعض السلوك الافتراضي أو يتركه للفئات الحقيقية. | |||
func operation() -> String | |||
} | |||
extension Component { | |||
func add(component: Component) {} | |||
func remove(component: Component) {} | |||
func isComposite() -> Bool { | |||
return false | |||
} | |||
} | |||
/// تمثل فئة الورقةُ الكائناتِ النهائيةَ لمركب ما، فلا | |||
/// يمكن لكائن الورقة أن يحتوي على كائنات فرعية. | |||
/// | |||
/// عادة ما تنفذ كائنات الأوراق المهام التي توكل إليها | |||
/// بينما تفوض الكائنات المركبة تلك المهام إلى ما دونها. | |||
class Leaf: Component { | |||
var parent: Component? | |||
func operation() -> String { | |||
return "Leaf" | |||
} | |||
} | |||
/// تمثل فئة المركب العناصر المعقدة التي قد تحتوي على فروع، | |||
/// وعادة ما تفوض كائنات المركب الأعمال الحقيقية لما دونها | |||
/// ثم تجمع النتيجة النهائية. | |||
class Composite: Component { | |||
var parent: Component? | |||
/// يحتوي هذا الحقل على الشجرة الفرعية للعناصر. | |||
private var children = [Component]() | |||
/// يستطيع الكائن المركب أن يضيف أو يحذف عناصرً أخرى -بسيطة أو معقدة- | |||
/// من قائمة الفروع. | |||
func add(component: Component) { | |||
var item = component | |||
item.parent = self | |||
children.append(item) | |||
} | |||
func remove(component: Component) { | |||
// ... | |||
} | |||
func isComposite() -> Bool { | |||
return true | |||
} | |||
/// ينفذ المركب منطقه الأساسي بأن يمر على فروعه كلها بشكل | |||
/// تكراري جامعًا نتائجها، وبما أن فروع المركب تمرر استدعاءاتها لما | |||
/// دونها فإن الشجرة بكاملها تُفحص نتيجة لهذا. | |||
func operation() -> String { | |||
let result = children.map({ $0.operation() }) | |||
return "Branch(" + result.joined(separator: " ") + ")" | |||
} | |||
} | |||
class Client { | |||
/// تعمل شيفرة العميل مع كل العناصر من خلال الواجهة الأساسية. | |||
static func someClientCode(component: Component) { | |||
print("Result: " + component.operation()) | |||
} | |||
/// تستطيع شيفرة العميل أن تعمل مع أي عنصر بسيط أو معقد | |||
/// دون الاعتماد على الفئات الحقيقية، وذلك بفضل وجود التصريح عن | |||
/// عمليات إدارة الفروع في فئة العنصر الأساسي. | |||
static func moreComplexClientCode(leftComponent: Component, rightComponent: Component) { | |||
if leftComponent.isComposite() { | |||
leftComponent.add(component: rightComponent) | |||
} | |||
print("Result: " + leftComponent.operation()) | |||
} | |||
} | |||
/// لنرى كيف سيعمل كل ذلك... | |||
class CompositeConceptual: XCTestCase { | |||
func testCompositeConceptual() { | |||
/// تستطيع شيفرة العميل بهذه الطريقة أن تدعم عناصر الورقة البسيطة.... | |||
print("Client: I've got a simple component:") | |||
Client.someClientCode(component: Leaf()) | |||
/// ...إضافة إلى المركبات المعقدة. | |||
let tree = Composite() | |||
let branch1 = Composite() | |||
branch1.add(component: Leaf()) | |||
branch1.add(component: Leaf()) | |||
let branch2 = Composite() | |||
branch2.add(component: Leaf()) | |||
branch2.add(component: Leaf()) | |||
tree.add(component: branch1) | |||
tree.add(component: branch2) | |||
print("\nClient: Now I've got a composite tree:") | |||
Client.someClientCode(component: tree) | |||
print("\nClient: I don't need to check the components classes even when managing the tree:") | |||
Client.moreComplexClientCode(leftComponent: tree, rightComponent: Leaf()) | |||
} | |||
} | |||
</syntaxhighlight> | |||
==== Output.txt: نتائج التنفيذ ==== | |||
<syntaxhighlight lang="text"> | |||
Client: I've got a simple component: | |||
Result: Leaf | |||
Client: Now I've got a composite tree: | |||
Result: Branch(Branch(Leaf Leaf) Branch(Leaf Leaf)) | |||
Client: I don't need to check the components classes even when managing the tree: | |||
Result: Branch(Branch(Leaf Leaf) Branch(Leaf Leaf) Leaf) | |||
</syntaxhighlight> | |||
=== مثال واقعي === | |||
==== Example.swift: مثال واقعي ==== | |||
<syntaxhighlight lang="swift"> | |||
import UIKit | |||
import XCTest | |||
protocol Component { | |||
func accept<T: Theme>(theme: T) | |||
} | |||
extension Component where Self: UIViewController { | |||
func accept<T: Theme>(theme: T) { | |||
view.accept(theme: theme) | |||
view.subviews.forEach({ $0.accept(theme: theme) }) | |||
} | |||
} | |||
extension UIView: Component {} | |||
extension UIViewController: Component {} | |||
extension Component where Self: UIView { | |||
func accept<T: Theme>(theme: T) { | |||
print("\t\(description): has applied \(theme.description)") | |||
backgroundColor = theme.backgroundColor | |||
} | |||
} | |||
extension Component where Self: UILabel { | |||
func accept<T: LabelTheme>(theme: T) { | |||
print("\t\(description): has applied \(theme.description)") | |||
backgroundColor = theme.backgroundColor | |||
textColor = theme.textColor | |||
} | |||
} | |||
extension Component where Self: UIButton { | |||
func accept<T: ButtonTheme>(theme: T) { | |||
print("\t\(description): has applied \(theme.description)") | |||
backgroundColor = theme.backgroundColor | |||
setTitleColor(theme.textColor, for: .normal) | |||
setTitleColor(theme.highlightedColor, for: .highlighted) | |||
} | |||
} | |||
protocol Theme: CustomStringConvertible { | |||
var backgroundColor: UIColor { get } | |||
} | |||
protocol ButtonTheme: Theme { | |||
var textColor: UIColor { get } | |||
var highlightedColor: UIColor { get } | |||
/// خصائص أخرى. | |||
} | |||
protocol LabelTheme: Theme { | |||
var textColor: UIColor { get } | |||
/// خصائص أخرى. | |||
} | |||
/// سمات الأزرار. | |||
struct DefaultButtonTheme: ButtonTheme { | |||
var textColor = UIColor.red | |||
var highlightedColor = UIColor.white | |||
var backgroundColor = UIColor.orange | |||
var description: String { return "Default Buttom Theme" } | |||
} | |||
struct NightButtonTheme: ButtonTheme { | |||
var textColor = UIColor.white | |||
var highlightedColor = UIColor.red | |||
var backgroundColor = UIColor.black | |||
var description: String { return "Night Buttom Theme" } | |||
} | |||
/// Label themes سمات العناوين. | |||
struct DefaultLabelTheme: LabelTheme { | |||
var textColor = UIColor.red | |||
var backgroundColor = UIColor.black | |||
var description: String { return "Default Label Theme" } | |||
} | |||
struct NightLabelTheme: LabelTheme { | |||
var textColor = UIColor.white | |||
var backgroundColor = UIColor.black | |||
var description: String { return "Night Label Theme" } | |||
} | |||
class CompositeRealWorld: XCTestCase { | |||
func testCompositeRealWorld() { | |||
print("\nClient: Applying 'default' theme for 'UIButton'") | |||
apply(theme: DefaultButtonTheme(), for: UIButton()) | |||
print("\nClient: Applying 'night' theme for 'UIButton'") | |||
apply(theme: NightButtonTheme(), for: UIButton()) | |||
print("\nClient: Let's use View Controller as a composite!") | |||
/// السمة الليلية. | |||
print("\nClient: Applying 'night button' theme for 'WelcomeViewController'...") | |||
apply(theme: NightButtonTheme(), for: WelcomeViewController()) | |||
print() | |||
print("\nClient: Applying 'night label' theme for 'WelcomeViewController'...") | |||
apply(theme: NightLabelTheme(), for: WelcomeViewController()) | |||
print() | |||
/// السمة الافتراضية. | |||
print("\nClient: Applying 'default button' theme for 'WelcomeViewController'...") | |||
apply(theme: DefaultButtonTheme(), for: WelcomeViewController()) | |||
print() | |||
print("\nClient: Applying 'default label' theme for 'WelcomeViewController'...") | |||
apply(theme: DefaultLabelTheme(), for: WelcomeViewController()) | |||
print() | |||
} | |||
func apply<T: Theme>(theme: T, for component: Component) { | |||
component.accept(theme: theme) | |||
} | |||
} | |||
class WelcomeViewController: UIViewController { | |||
class ContentView: UIView { | |||
var titleLabel = UILabel() | |||
var actionButton = UIButton() | |||
override init(frame: CGRect) { | |||
super.init(frame: frame) | |||
setup() | |||
} | |||
required init?(coder decoder: NSCoder) { | |||
super.init(coder: decoder) | |||
setup() | |||
} | |||
func setup() { | |||
addSubview(titleLabel) | |||
addSubview(actionButton) | |||
} | |||
} | |||
override func loadView() { | |||
view = ContentView() | |||
} | |||
} | |||
/// من أجل مخرجات أفضل description property غير خصائص الوصف . | |||
extension WelcomeViewController { | |||
open override var description: String { return "WelcomeViewController" } | |||
} | |||
extension WelcomeViewController.ContentView { | |||
override var description: String { return "ContentView" } | |||
} | |||
extension UIButton { | |||
open override var description: String { return "UIButton" } | |||
} | |||
extension UILabel { | |||
open override var description: String { return "UILabel" } | |||
} | |||
</syntaxhighlight> | |||
==== Output.txt: نتائج التنفيذ ==== | |||
<syntaxhighlight lang="text"> | |||
Client: Applying 'default' theme for 'UIButton' | |||
UIButton: has applied Default Buttom Theme | |||
Client: Applying 'night' theme for 'UIButton' | |||
UIButton: has applied Night Buttom Theme | |||
Client: Let's use View Controller as a composite! | |||
Client: Applying 'night button' theme for 'WelcomeViewController'... | |||
ContentView: has applied Night Buttom Theme | |||
UILabel: has applied Night Buttom Theme | |||
UIButton: has applied Night Buttom Theme | |||
</syntaxhighlight> | |||
== الاستخدام في لغة TypeScript == | |||
'''المستوى''': ★ ★ ☆ | |||
'''الانتشار''': ★ ★ ★ | |||
'''أمثلة الاستخدام''': يكثر استخدام نمط المركَّب في لغة TypeScript، إذ يُستخدم لتمثيل هرميات لعناصر من واجهة المستخدم أو شيفرة تعمل مع الرسوم التوضيحية (Graphs). | |||
يمكن ملاحظة نمط المركب من خلال الأساليب السلوكية التي تأخذ نسخة من نفس النوع التجريدي/التنفيذي إلى هيكل شجري. | |||
=== مثال: مثال تصوري === | |||
يوضح هذا المثال بنية نمط الجسر، ويركز على إجابة الأسئلة التالية: | |||
* ما الفئات التي يتكون منها؟ | |||
* ما الأدوار التي تلعبها هذه الفئات؟ | |||
* كيف ترتبط عناصر النمط ببعضها؟ | |||
==== index.ts: مثال تصوري ==== | |||
<syntaxhighlight lang="typescript"> | |||
/** | |||
* تصرح فئة العنصر الأساسي عن العمليات المشتركة للكائنات | |||
* البسيطة والمعقدة لمركب ما. | |||
*/ | |||
abstract class Component { | |||
protected parent: Component; | |||
/** | |||
* لفئة العنصر الأساسي أن تصرح عن واجهة لإعداد أحد الأصول لعنصر ما والوصول | |||
* إليه في هيكل شجري كما تضيف بعض الاستخدامات الافتراضية لهذه الأساليب. | |||
*/ | |||
public setParent(parent: Component) { | |||
this.parent = parent; | |||
} | |||
public getParent(): Component { | |||
return this.parent; | |||
} | |||
/** | |||
* قد يفيد في بعض الحالات أن تعرّف عمليات إدارة الفروع في فئة | |||
* العنصر الأساسي مباشرة، وهكذا لن تحتاج إلى كشف الفئات الحقيقية | |||
* للعناصر إلى شيفرة العميل حتى أثناء تجميع الشجرة. لكن المشكلة هنا | |||
* هي أن هذه الأساليب ستكون فارغة للعناصر التي في مستوى الورقة. | |||
*/ | |||
public add(component: Component): void { } | |||
public remove(component: Component): void { } | |||
/** | |||
* تستطيع إضافة أسلوب يسمح لشيفرة العميل أن تعرف إن كان عنصر | |||
* ما يستطيع استيعاب فروع له | |||
*/ | |||
public isComposite(): boolean { | |||
return false; | |||
} | |||
/** | |||
* قد يطبق العنصر الأساسي بعض السلوك الافتراضي أو يتركه للفئات الحقيقية. | |||
* عبر التصريح عن الأسلوب الحاوي للسلوك على أنه مجرد. | |||
*/ | |||
public abstract operation(): string; | |||
} | |||
/** | |||
* تمثل فئة الورقةُ الكائناتِ النهائيةَ لمركب ما، فلا | |||
* يمكن لكائن الورقة أن يحتوي على كائنات فرعية. | |||
* | |||
* عادة ما تنفذ كائنات الأوراق المهام التي توكل إليها | |||
* بينما تفوض الكائنات المركبة تلك المهام إلى ما دونها. | |||
*/ | |||
class Leaf extends Component { | |||
public operation(): string { | |||
return 'Leaf'; | |||
} | |||
} | |||
/** | |||
* تمثل فئة المركب العناصر المعقدة التي قد تحتوي على فروع، | |||
* وعادة ما تفوض كائنات المركب الأعمال الحقيقية لما دونها | |||
* ثم تجمع النتيجة النهائية. | |||
*/ | |||
class Composite extends Component { | |||
protected children: Component[] = []; | |||
/** | |||
* يستطيع الكائن المركب أن يضيف أو يحذف عناصرً أخرى -بسيطة أو معقدة- | |||
* من قائمة الفروع. | |||
*/ | |||
public add(component: Component): void { | |||
this.children.push(component); | |||
component.setParent(this); | |||
} | |||
public remove(component: Component): void { | |||
const componentIndex = this.children.indexOf(component); | |||
this.children.splice(componentIndex, 1); | |||
component.setParent(null); | |||
} | |||
public isComposite(): boolean { | |||
return true; | |||
} | |||
/** | |||
* ينفذ المركب منطقه الأساسي بأن يمر على فروعه كلها بشكل | |||
* تكراري جامعًا نتائجها، وبما أن فروع المركب تمرر استدعاءاتها لما | |||
* دونها فإن الشجرة بكاملها تُفحص نتيجة لهذا. | |||
*/ | |||
public operation(): string { | |||
const results = []; | |||
for (const child of this.children) { | |||
results.push(child.operation()); | |||
} | |||
return `Branch(${results.join('+')})`; | |||
} | |||
} | |||
/** | |||
* تعمل شيفرة العميل مع كل العناصر من خلال الواجهة الأساسية. | |||
*/ | |||
function clientCode(component: Component) { | |||
// ... | |||
console.log(`RESULT: ${component.operation()}`); | |||
// ... | |||
} | |||
/** | |||
* تستطيع شيفرة العميل بهذه الطريقة أن تدعم عناصر الورقة البسيطة.... | |||
*/ | |||
const simple = new Leaf(); | |||
console.log('Client: I\'ve got a simple component:'); | |||
clientCode(simple); | |||
console.log(''); | |||
/** | |||
* ...إضافة إلى المركبات المعقدة. | |||
*/ | |||
const tree = new Composite(); | |||
const branch1 = new Composite(); | |||
branch1.add(new Leaf()); | |||
branch1.add(new Leaf()); | |||
const branch2 = new Composite(); | |||
branch2.add(new Leaf()); | |||
tree.add(branch1); | |||
tree.add(branch2); | |||
console.log('Client: Now I\'ve got a composite tree:'); | |||
clientCode(tree); | |||
console.log(''); | |||
/** | |||
* تستطيع شيفرة العميل أن تعمل مع أي عنصر بسيط أو معقد. | |||
* دون الاعتماد على الفئات الحقيقية، وذلك بفضل وجود التصريح عن | |||
* عمليات إدارة الفروع في فئة العنصر الأساسي. | |||
*/ | |||
function clientCode2(component1: Component, component2: Component) { | |||
// ... | |||
if (component1.isComposite()) { | |||
component1.add(component2); | |||
} | |||
console.log(`RESULT: ${component1.operation()}`); | |||
// ... | |||
} | |||
console.log('Client: I don\'t need to check the components classes even when managing the tree:'); | |||
clientCode2(tree, simple); | |||
</syntaxhighlight> | |||
==== Output.txt: نتائج التنفيذ ==== | |||
<syntaxhighlight lang="text"> | |||
Client: I've got a simple component: | |||
RESULT: Leaf | |||
Client: Now I've got a composite tree: | |||
RESULT: Branch(Branch(Leaf+Leaf)+Branch(Leaf)) | |||
Client: I don't need to check the components classes even when managing the tree: | |||
RESULT: Branch(Branch(Leaf+Leaf)+Branch(Leaf)+Leaf) | |||
</syntaxhighlight> | </syntaxhighlight> | ||
== انظر أيضًا == | == انظر أيضًا == | ||
* [[Design Patterns/factory method|نمط أسلوب المصنع.]] | * [[Design Patterns/factory method|نمط أسلوب المصنع Factory Method.]] | ||
* [[Design Patterns/abstract factory|نمط المصنع المجرد]]. | * [[Design Patterns/abstract factory|نمط المصنع المجرد Abstract Factory]]. | ||
* [[Design Patterns/singleton|نمط المفردة]]. | * [[Design Patterns/singleton|نمط المفردة Singleton]]. | ||
== مصادر == | == مصادر == | ||
* [https://refactoring.guru/design-patterns/composite توثيق نمط المركَّب في موقع refactoring.guru.] | * [https://refactoring.guru/design-patterns/composite توثيق نمط المركَّب في موقع refactoring.guru.] | ||
[[تصنيف:Design Patterns]] | |||
[[تصنيف:Composite Design Pattern]] | |||
[[تصنيف:Structural Design Patterns]] |
المراجعة الحالية بتاريخ 10:47، 7 أكتوبر 2022
نمط المُركَّب (Composite) هو نمط تصميم هيكلي يسمح لك بتركيب كائنات في هياكل شجرية لتعاملها على أنها وحدة واحدة أو كائن واحد.
المشكلة
يستخدم نمط المركَّب في حالة واحدة فقط، وهي حين يمكن تمثيل النموذج الذي يقوم عليه تطبيقك في هيئة شجرية، كأن يكون لديك نوعين من الكائنات هما المنتجات Products
والصناديق Boxes
. ويستطيع الأخير استيعاب منتجات بداخله وصناديق أخرى كذلك أصغر منه في الحجم، وتلك الصناديق الجديدة يمكنها احتواء صناديق أخرى بداخلها، وهكذا دواليك. ولنقل أنك تريد إنشاء نظام طلبات يستخدم تلك الفئات، تحتوي فيه الطلبات المختلفة على منتجات بسيطة بدون أي تغليف إضافة إلى صناديق ملأى بالمنتجات والصناديق الأخرى الأصغر منها، وتريد الآن أن تحسب الثمن الكلي لطلب به صناديق بداخلها صناديق أخرى ومنتجات بدون تغليف، كيف تفعل ذلك؟
إن سلكنا الطريق المباشرة فهذا يعني فتح الصناديق جميعها وحساب ثمن كل منتج بسيط ثم جمع تلك الأثمان لنحصل على الثمن الكلي للطلب كاملًا، لكن هذا الأسلوب وإن كان يصلح أحيانًا في الأمثلة التي نراها في الحياة اليومية، لكنها لا تصلح في البرمجة، إذ -وفقًا لهذا المنظور- يجب أن تعرف فئات كل المنتجات Products
والصناديق Boxes
التي ستفحصها، ومستوى تداخل الصناديق وأمور أخرى غير ذلك، وذلك يجعل المنظور المباشر غير عملي وقريبًا من المستحيل لحل مشكلة كهذه.
الحل
يقترح نمط المركَّب أن تعمل مع Boxes
و Products
من خلال واجهة مشتركة تصرح عن أسلوب لحساب الثمن الكلي للطلب، وسيفحص ذلك الأسلوب كل عنصر في الصندوق ويطلب ثمنه ثم يعيد إجمالي الصندوق، وإن كان أحد عناصر الصندوق صندوقًا أصغر منه فإن الأسلوب يكرر نفس الخطوات في فحص كل عنصر فيه وطلب ثمنه، وهكذا يعامل كل صندوق فرعي معاملةَ الصندوق الأول حتى يصل إلى آخر تلك السلسلة ويحسب أثمان كل المنتجات الفرعية، بل قد يحسب أثمان الصناديق أنفسها أيضًا في بعض الحالات التي تُحسب فيها قيمة الصناديق من تكلفة التغليف.
وأعظم فائدة لهذا الأسلوب في الحل أنك لا تحتاج إلى النظر في الفئات الحقيقية للكائنات التي تتكون منها الشجرة، فلا تحتاج إلى معرفة ما إن كان الكائن منتجًا بسيطًا أو صندوقًا معقدًا إذ يمكنك معالجتها جميعًا بنفس الأسلوب من خلال الواجهة المشتركة، ذلك أن الكائنات أنفسها تمرر الطلب إلى ما تحتها في كامل الشجرة كلما استدعيت أسلوبًا ما.
مثال واقعي
تُشكَّل جيوش أغلب الدول بصورة هرمية يتكون فيها الجيش من أقسام رئيسية، في كل قسم مجموعة ألوية، في كل لواء منها مجموعة كتائب، ثم كل كتيبة بها مجموعة سرايا، وكل سرية فيها مجموعات من الجنود الذين تُمرر إليهم الأوامر من القيادات العليا للجيش لتعرف المهام التي عليها تنفيذها.
البُنية
- تصف واجهةُ العنصر (Component) العمليات المشتركةَ في كل من العناصر البسيطة والمعقدة في الشجرة.
- الورقة (Leaf) هي عنصر بسيط في الشجرة ليس له عناصر فرعية، وعادة ما تنفذ الورقة أغلب المهام الحقيقية بما أنها ليس لها عناصر تحتها تفوض إليه تلك المهام.
- الحاوية (المركَّب أيضًا) هي عنصر في الشجرة تحته عناصر فرعية سواء كانت حاويات أخرى أو أوراقًا، ولا تعرف الحاوية الفئات الحقيقية لما تحتها من فروع، وهي تعمل مع كل العناصر الفرعية من خلال واجهة العنصر فقط، وتفوض العمل إلى عناصرها الفرعية ثم تعالج النتائج الأولية وتعيد النتيجة النهائية للعميل.
- يعمل العميل مع كل العناصر من خلال واجهة العنصر، ويستطيع العمل نتيجة لهذا بنفس الطريقة مع العناصر البسيطة والمعقدة من الشجرة.
مثال توضيحي
يسمح لك نمط المركَّب في هذا المثال بتنفيذ تكديسات لأشكال هندسية (Geometric) داخل محرِّر مرئي.
فئة CompoundGraphic
هي الحاوية التي تشمل أي عدد من الأشكال الفرعية بما في ذلك الأشكال المجمَّعة الأخرى، والشكل المجمَّع له نفس الأساليب كالشكل البسيط، لكن بدلًا من فعل أي شيء بنفسه فإنه -أي المجمَّع- يمرر الطلب بشكل تكراري إلى كل فروعه ويجمع النتيجة في النهاية.
وتعمل شيفرة العميل مع الأشكال كلها من خلال واجهة واحدة مشتركة بين كل فئات الأشكال، لهذا لا يعرف العميل ما إن كان يعمل مع شكل بسيط أو مجمع، ويعمل العميل لهذا أيضًا مع هياكل لأشكال معقدة جدًا دون التقيد بالفئات الحقيقية التي تكوِّن تلك الهياكل.
// تصرح واجهة العنصر عن عمليات مشتركة للأشكال المجمعة والبسيطة لمركب ما.
interface Graphic is
method move(x, y)
method draw()
// تمثل فئة الورقةُ الكائناتِ النهائيةَ لمركب ما، فلا يمكن لكائن الورقة أن يحتوي على كائنات فرعية
// وعادة ما ينفذ المهام الحقيقية لذلك، بينما تفوض الكائنات المجمعة المهام إلى ما دونها.
class Dot implements Graphic is
field x, y
constructor Dot(x, y) { ... }
method move(x, y) is
this.x += x, this.y += y
method draw() is
// Y و X ارسم نقطة عند.
// جميع فئات العنصر تستطيع توسعة العناصر العناصر الأخرى.
class Circle extends Dot is
field radius
constructor Circle(x, y, radius) { ... }
method draw() is
// R وبنصف قطر Y و X ارسم دائرة عند.
// تمثل فئة المركب عناصرً معقدة قد تكون لها كائنات فرعية، وتفوض كائناتُ المركبُ المهامَ عادة لما دونها
// ثم تجمع النتيجة.
class CompoundGraphic implements Graphic is
field children: array of Graphic
// يمكن أن يضيف كائن المركب أو يحذف عناصر أخرى -بسيطة أومعقدة- إلى أو من قائمته الفرعية.
method add(child: Graphic) is
// إضافة عنصر فرعي إلى مصفوفة الفروع.
method remove(child: Graphic) is
// حذف عنصر فرعي إلى مصفوفة الفروع.
method move(x, y) is
foreach (child in children) do
child.move(x, y)
// ينفذ المركب منطقه الأساسي بشكل معين، إذ يمر مرة بعد مرة على فروعه كلها ويجمع نتائجها، وبما أن فروع
// المركب تمرر استدعاءتها إلى فروعها فإن المركب يمر على الشجرة كلها.
method draw() is
// 1. لكل عنصر فرعي
// - ارسم العنصر
// - (Bounding Rectangle) حدِّث المستطيل المحدِّد
// 2. (Bounding Coordinates) باستخدام إحداثيات الحدود (Dashes) ارسم مستطيلًا خطه مكون من شُرط مقطعة .
// تعمل شيفرة العميل مع كل العناصر من خلال واجهتها الأساسية، وهكذا تستطيع شيفرة العميل أن تدعم عناصر الورقة
// البسيطة والمركبات المعقدة على حد سواء.
class ImageEditor is
method load() is
all = new CompoundGraphic()
all.add(new Dot(1, 2))
all.add(new Circle(5, 3, 10))
// ...
// اجمع العناصر المختارة في عنصرٍ مركب معقد واحد.
method groupSelected(components: array of Graphic) is
group = new CompoundGraphic()
group.add(components)
all.remove(components)
all.add(group)
// ستُرسم كل العناصر.
all.draw()
قابلية التنفيذ
- استخدم نمط المركَّب عند تنفيذ هيكل شجري لمجموعة كائنات.
يزودك نمط المركَّب بنوعين أساسيين من العناصر التي تشترك في واجهة واحدة، وهي الأوراق (Leaves) البسيطة، والحاويات (Containers) المعقدة، وقد تتكون الحاوية من الأوراق والحاويات الأخرى كذلك، ويسمح لك هذا بإنشاء هيكلة تكرارية متداخلة تشبه الشجرة.
- استخدم نمط المركَّب حين تريد لشيفرة العميل أن تعامل العناصر البسيطة والمعقدة بنفس الأسلوب.
تشترك كل العناصر المعرَّفة من قبل نمط المركَّب في واجهة واحدة، ولا يحتاج العميل أن يعرف الفئة الحقيقية للكائن الذي يتعامل معه.
كيفية الاستخدام
- تأكد أن النموذج الأساسي لتطبيقك يمكن تمثيله بهيكل شجري، ثم جزّء ذلك إلى عناصر بسيطة وحاويات. تذكر أن الحاويات يجب أن تكون قادرة على احتواء العناصر البسيطة إضافة إلى الحاويات الأخرى كذلك.
- صرِّح عن واجهة العنصر مع قائمة أساليب تصلح للعناصر البسيطة والحاويات المعقدة على حد سواء.
- أنشئ فئة ورقة (Leaf) لتمثل العناصر البسيطة، قد يحتوي البرنامج فئات مختلفة للأوراق.
- أنشئ فئة حاوية (Container) لتمثل العناصر المعقدة في تلك الفئة، وأضف حقل مصفوفة لتخزين المراجع إلى العناصر الفرعية، يجب أن تكون المصفوفة قادرة على تخزين الأوراق والحاويات، لذا تأكد أنها مصرح عنها مع نوع واجهة العنصر. تذكر أثناء تنفيذ أساليب واجهة العنصر أن الحاوية يفترض بها تفويض أغلب مهامها إلى العناصر الفرعية لها.
- أخيرًا، عرّف أساليب إضافة وحذف العناصر الفرعية في الحاوية.
لاحظ أن هذه العمليات يمكن التصريح عنها في واجهة العنصر (Component Interface)، وذاك قد يخرق مبدأ عزل الواجهة (Interface Segregation Principle) لأن الأساليب ستكون فارغة في فئة الورقة، لكن العميل سيتمكن من معاملة كل العناصر بالتساوي حتى عند تركيب (Composing) الشجرة.
المزايا والعيوب
المزايا
- تستطيع العمل مع هياكل شجرية معقدة بشكل أسهل: استخدم تعدد الأشكال (polymorphism) والتكرار المستمر (recursion) لصالحك.
- مبدأ المفتوح/المغلق. تستطيع إدخال أنواعًا جديدة من العناصر في التطبيق دون تعطيل الشيفرة الحالية، والتي تعمل الآن مع شجرة كائنات.
العيوب
- قد يكون من الصعب توفير واجهة مشتركة للفئات التي تختلف وظائفها عن بعضها بشكل كبير، فستحتاج في حالات معينة إلى المبالغة في تعميم واجهة العنصر إلى الحد الذي سيجعلها تستعصي على الفهم.
العلاقات مع الأنماط الأخرى
- يمكنك استخدام نمط الباني عند إنشاء أشجار مركبات معقدة لأنك تستطيع برمجة خطوات هيكلتها لتعمل بتكرارية.
- تُستخدم سلسلة المسؤوليات عادة بالتزامن مع المركَّب، وفي تلك الحالة تمرر الورقة الطلب الذي تحصل عليه خلال العناصر التي تعلوها في السلسلة الهرمية حتى تصل إلى جذر شجرة الكائنات.
- يمكنك استخدام المكررات لاجتياز أشجار المركبات.
- يمكنك استخدام نمط الزائر لتنفيذ عملية على شجرة مركب بكاملها.
- يمكنك تنفيذ عُقد ورقية مشتركة (Shared Leaf Nodes) لشجرة المركب لتكوّن أنماطًا من نوع وزن الذبابة، من أجل توفير الذاكرة العشوائية RAM.
- لنمطيْ المركب والمزخرِف هياكل متشابهة بما أنهما يعتمدان على التركيب التكراري لتنظيم عدد مفتوح النهاية (open-ended) من الكائنات.
- المزخرف مثل المركب لكنه لا يملك إلا عنصرًا فرعيًا واحدًا، ويضيف المزخرف مسؤوليات إضافية إلى الكائن المُغلَّف، بينما ما يقوم به المركَّب هو جمع نتائج الفروع فقط. لكن بأي حال فإن هذه الأنماط تتعاون فيما بينها، فتستطيع استخدام المزخرِف لتوسيع سلوك كائن معين في شجرة مركَّب ما.
- التصميمات التي تستخدم المركب والمزخرف بكثرة يمكنها الاستفادة من نمط النموذج الأولي، إذ استخدامه يسمح باستنساخ هياكل معقدة بدلًا من إعادة بنائهم من الصفر.
الاستخدام في لغة جافا
المستوى: ★ ★ ☆
الانتشار: ★ ★ ★
أمثلة الاستخدام: يكثر استخدام نمط المركَّب في لغة بايثون، إذ يُستخدم لتمثيل هرميات لعناصر من واجهة المستخدم أو شيفرة تعمل مع الرسوم التوضيحية (Graphs). إليك بعض الأمثلة على نمط المركب من مكتبات جافا قياسية:
- (java.awt.Container#add(Component تقريبًا في كل عناصر واجهة Swing.
- ()javax.faces.component.UIComponent#getChildren تقريبًا في كل عناصر واجهات JSF.
يمكن ملاحظة نمط المركب من خلال الأساليب السلوكية التي تأخذ نسخة من نفس النوع التجريدي/التنفيذي إلى هيكل شجري.
مثال: أشكال مرئية بسيطة ومجمَّعة
أشكال
shapes/Shape.java: واجهة مشتركة للشكل
package refactoring_guru.composite.example.shapes;
import java.awt.*;
public interface Shape {
int getX();
int getY();
int getWidth();
int getHeight();
void move(int x, int y);
boolean isInsideBounds(int x, int y);
void select();
void unSelect();
boolean isSelected();
void paint(Graphics graphics);
}
shapes/BaseShape.java: شكل تجريدي بوظائف بسيطة
package refactoring_guru.composite.example.shapes;
import java.awt.*;
abstract class BaseShape implements Shape {
public int x;
public int y;
public Color color;
private boolean selected = false;
BaseShape(int x, int y, Color color) {
this.x = x;
this.y = y;
this.color = color;
}
@Override
public int getX() {
return x;
}
@Override
public int getY() {
return y;
}
@Override
public int getWidth() {
return 0;
}
@Override
public int getHeight() {
return 0;
}
@Override
public void move(int x, int y) {
this.x += x;
this.y += y;
}
@Override
public boolean isInsideBounds(int x, int y) {
return x > getX() && x < (getX() + getWidth()) &&
y > getY() && y < (getY() + getHeight());
}
@Override
public void select() {
selected = true;
}
@Override
public void unSelect() {
selected = false;
}
@Override
public boolean isSelected() {
return selected;
}
void enableSelectionStyle(Graphics graphics) {
graphics.setColor(Color.LIGHT_GRAY);
Graphics2D g2 = (Graphics2D) graphics;
float dash1[] = {2.0f};
g2.setStroke(new BasicStroke(1.0f,
BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER,
2.0f, dash1, 0.0f));
}
void disableSelectionStyle(Graphics graphics) {
graphics.setColor(color);
Graphics2D g2 = (Graphics2D) graphics;
g2.setStroke(new BasicStroke());
}
@Override
public void paint(Graphics graphics) {
if (isSelected()) {
enableSelectionStyle(graphics);
}
else {
disableSelectionStyle(graphics);
}
// ...
}
}
shapes/Dot.java: نقطة
package refactoring_guru.composite.example.shapes;
import java.awt.*;
public class Dot extends BaseShape {
private final int DOT_SIZE = 3;
public Dot(int x, int y, Color color) {
super(x, y, color);
}
@Override
public int getWidth() {
return DOT_SIZE;
}
@Override
public int getHeight() {
return DOT_SIZE;
}
@Override
public void paint(Graphics graphics) {
super.paint(graphics);
graphics.fillRect(x - 1, y - 1, getWidth(), getHeight());
}
}
shapes/Circle.java: دائرة
package refactoring_guru.composite.example.shapes;
import java.awt.*;
public class Circle extends BaseShape {
public int radius;
public Circle(int x, int y, int radius, Color color) {
super(x, y, color);
this.radius = radius;
}
@Override
public int getWidth() {
return radius * 2;
}
@Override
public int getHeight() {
return radius * 2;
}
public void paint(Graphics graphics) {
super.paint(graphics);
graphics.drawOval(x, y, getWidth() - 1, getHeight() - 1);
}
}
shapes/Rectangle.java: مستطيل
package refactoring_guru.composite.example.shapes;
import java.awt.*;
public class Rectangle extends BaseShape {
public int width;
public int height;
public Rectangle(int x, int y, int width, int height, Color color) {
super(x, y, color);
this.width = width;
this.height = height;
}
@Override
public int getWidth() {
return width;
}
@Override
public int getHeight() {
return height;
}
@Override
public void paint(Graphics graphics) {
super.paint(graphics);
graphics.drawRect(x, y, getWidth() - 1, getHeight() - 1);
}
}
shapes/CompoundShape.java: شكل مجمع يتكون من كائنات أشكال أخرى
package refactoring_guru.composite.example.shapes;
import java.awt.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class CompoundShape extends BaseShape {
protected List<Shape> children = new ArrayList<>();
public CompoundShape(Shape... components) {
super(0, 0, Color.BLACK);
add(components);
}
public void add(Shape component) {
children.add(component);
}
public void add(Shape... components) {
children.addAll(Arrays.asList(components));
}
public void remove(Shape child) {
children.remove(child);
}
public void remove(Shape... components) {
children.removeAll(Arrays.asList(components));
}
public void clear() {
children.clear();
}
@Override
public int getX() {
if (children.size() == 0) {
return 0;
}
int x = children.get(0).getX();
for (Shape child : children) {
if (child.getX() < x) {
x = child.getX();
}
}
return x;
}
@Override
public int getY() {
if (children.size() == 0) {
return 0;
}
int y = children.get(0).getY();
for (Shape child : children) {
if (child.getY() < y) {
y = child.getY();
}
}
return y;
}
@Override
public int getWidth() {
int maxWidth = 0;
int x = getX();
for (Shape child : children) {
int childsRelativeX = child.getX() - x;
int childWidth = childsRelativeX + child.getWidth();
if (childWidth > maxWidth) {
maxWidth = childWidth;
}
}
return maxWidth;
}
@Override
public int getHeight() {
int maxHeight = 0;
int y = getY();
for (Shape child : children) {
int childsRelativeY = child.getY() - y;
int childHeight = childsRelativeY + child.getHeight();
if (childHeight > maxHeight) {
maxHeight = childHeight;
}
}
return maxHeight;
}
@Override
public void move(int x, int y) {
for (Shape child : children) {
child.move(x, y);
}
}
@Override
public boolean isInsideBounds(int x, int y) {
for (Shape child : children) {
if (child.isInsideBounds(x, y)) {
return true;
}
}
return false;
}
@Override
public void unSelect() {
super.unSelect();
for (Shape child : children) {
child.unSelect();
}
}
public boolean selectChildAt(int x, int y) {
for (Shape child : children) {
if (child.isInsideBounds(x, y)) {
child.select();
return true;
}
}
return false;
}
@Override
public void paint(Graphics graphics) {
if (isSelected()) {
enableSelectionStyle(graphics);
graphics.drawRect(getX() - 1, getY() - 1, getWidth() + 1, getHeight() + 1);
disableSelectionStyle(graphics);
}
for (refactoring_guru.composite.example.shapes.Shape child : children) {
child.paint(graphics);
}
}
}
المحرر
editor/ImageEditor.java: محرر الشكل
package refactoring_guru.composite.example.editor;
import refactoring_guru.composite.example.shapes.CompoundShape;
import refactoring_guru.composite.example.shapes.Shape;
import javax.swing.*;
import javax.swing.border.Border;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
public class ImageEditor {
private EditorCanvas canvas;
private CompoundShape allShapes = new CompoundShape();
public ImageEditor() {
canvas = new EditorCanvas();
}
public void loadShapes(Shape... shapes) {
allShapes.clear();
allShapes.add(shapes);
canvas.refresh();
}
private class EditorCanvas extends Canvas {
JFrame frame;
private static final int PADDING = 10;
EditorCanvas() {
createFrame();
refresh();
addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
allShapes.unSelect();
allShapes.selectChildAt(e.getX(), e.getY());
e.getComponent().repaint();
}
});
}
void createFrame() {
frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
JPanel contentPanel = new JPanel();
Border padding = BorderFactory.createEmptyBorder(PADDING, PADDING, PADDING, PADDING);
contentPanel.setBorder(padding);
frame.setContentPane(contentPanel);
frame.add(this);
frame.setVisible(true);
frame.getContentPane().setBackground(Color.LIGHT_GRAY);
}
public int getWidth() {
return allShapes.getX() + allShapes.getWidth() + PADDING;
}
public int getHeight() {
return allShapes.getY() + allShapes.getHeight() + PADDING;
}
void refresh() {
this.setSize(getWidth(), getHeight());
frame.pack();
}
public void paint(Graphics graphics) {
allShapes.paint(graphics);
}
}
}
Demo.java: شيفرة العميل
package refactoring_guru.composite.example;
import refactoring_guru.composite.example.editor.ImageEditor;
import refactoring_guru.composite.example.shapes.Circle;
import refactoring_guru.composite.example.shapes.CompoundShape;
import refactoring_guru.composite.example.shapes.Dot;
import refactoring_guru.composite.example.shapes.Rectangle;
import java.awt.*;
public class Demo {
public static void main(String[] args) {
ImageEditor editor = new ImageEditor();
editor.loadShapes(
new Circle(10, 10, 10, Color.BLUE),
new CompoundShape(
new Circle(110, 110, 50, Color.RED),
new Dot(160, 160, Color.RED)
),
new CompoundShape(
new Rectangle(250, 250, 100, 100, Color.GREEN),
new Dot(240, 240, Color.GREEN),
new Dot(240, 360, Color.GREEN),
new Dot(360, 360, Color.GREEN),
new Dot(360, 240, Color.GREEN)
)
);
}
}
OutputDemo.png: نتائج التنفيذ
الاستخدام في لغة #C
المستوى: ★ ★ ☆
الانتشار: ★ ★ ★
أمثلة الاستخدام: يكثر استخدام نمط المركَّب في لغة #C، إذ يُستخدم لتمثيل هرميات لعناصر من واجهة المستخدم أو شيفرة تعمل مع الرسوم التوضيحية (Graphs).
يمكن ملاحظة نمط المركب من خلال الأساليب السلوكية التي تأخذ نسخة من نفس النوع التجريدي/التنفيذي إلى هيكل شجري.
مثال: مثال تصوري
يوضح هذا المثال بنية نمط المركَّب، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
Program.cs: مثال تصوري
using System;
using System.Collections.Generic;
namespace RefactoringGuru.DesignPatterns.Composite.Conceptual
{
// تصرح فئة العنصر الأساسي عن العمليات المشتركة للكائنات البسيطة والمعقدة لمركب ما.
abstract class Component
{
public Component() { }
// قد ينفذ العنصر الأساسي بعض السلوك الافتراضي أو لا ينفذه للفئات الحقيقية من خلال التصريح عن الأسلوب
// الحاوي للسلوك على أنه تجريدي.
public abstract string Operation();
// قد يفيد في بعض الحالات أن تعرّف عمليات إدارة الفروع في فئة العنصر الأساسي مباشرة، وهكذا لن تحتاج إلى
// كشف الفئات الحقيقية للعناصر إلى شيفرة العميل حتى أثناء تجميع الشجرة. لكن المشكلة هنا هي أن هذه
// الأساليب ستكون فارغة للعناصر التي في مستوى الورقة.
public virtual void Add(Component component)
{
throw new NotImplementedException();
}
public virtual void Remove(Component component)
{
throw new NotImplementedException();
}
// تستطيع إضافة أسلوب يسمح لشيفرة العميل أن تعرف إن كان عنصر ما يستطيع استيعاب فروع له.
public virtual bool IsComposite()
{
return true;
}
}
// تمثل فئة الورقةُ الكائناتِ النهائيةَ لمركب ما، فلا يمكن لكائن الورقة
// أن يحتوي على كائنات فرعية
//
// عادة ما تنفذ كائنات الأوراق المهام التي توكل إليها بينما تفوض
// الكائنات المركبة تلك المهام إلى ما دونها.
class Leaf : Component
{
public override string Operation()
{
return "Leaf";
}
public override bool IsComposite()
{
return false;
}
}
// تمثل فئة المركب العناصر المعقدة التي قد تحتوي على فروع، وعادة ما تفوض
// كائنات المركب الأعمال الحقيقية لما دونها ثم تجمع النتيجة النهائية.
{
protected List<Component> _children = new List<Component>();
public override void Add(Component component)
{
this._children.Add(component);
}
public override void Remove(Component component)
{
this._children.Remove(component);
}
// ينفذ المركب منطقه الأساسي بأن يمر على فروعه كلها بشكل تكراري جامعًا نتائجها، وبما أن فروع المركب
// تمرر استدعاءاتها لما دونها فإن الشجرة بكاملها تُفحص نتيجة لهذا.
public override string Operation()
{
int i = 0;
string result = "Branch(";
foreach (Component component in this._children)
{
result += component.Operation();
if (i != this._children.Count - 1)
{
result += "+";
}
i++;
}
return result + ")";
}
}
class Client
{
// تعمل شيفرة العميل مع كل العناصر من خلال الواجهة الأساسية.
public void ClientCode(Component leaf)
{
Console.WriteLine($"RESULT: {leaf.Operation()}\n");
}
// تستطيع شيفرة العميل أن تعمل مع أي عنصر بسيط أو معقد دون الاعتماد على الفئات الحقيقية للعناصر
// وذلك بفضل كون التصريح عن عمليات إدارة الفروع في فئة العنصر الأساسي.
public void ClientCode2(Component component1, Component component2)
{
if (component1.IsComposite())
{
component1.Add(component2);
}
Console.WriteLine($"RESULT: {component1.Operation()}");
}
}
class Program
{
static void Main(string[] args)
{
Client client = new Client();
// تستطيع شيفرة العميل بهذه الطريقة أن تدعم عناصر الورقة البسيطة.
Leaf leaf = new Leaf();
Console.WriteLine("Client: I get a simple component:");
client.ClientCode(leaf);
// ... إضافة إلى المركبات المعقدة.
Composite tree = new Composite();
Composite branch1 = new Composite();
branch1.Add(new Leaf());
branch1.Add(new Leaf());
Composite branch2 = new Composite();
branch2.Add(new Leaf());
tree.Add(branch1);
tree.Add(branch2);
Console.WriteLine("Client: Now I've got a composite tree:");
client.ClientCode(tree);
Console.Write("Client: I don't need to check the components classes even when managing the tree:\n");
client.ClientCode2(tree, leaf);
}
}
}
Output.txt: المخرجات
Client: I get a simple component:
RESULT: Leaf
Client: Now I've got a composite tree:
RESULT: Branch(Branch(Leaf+Leaf)+Branch(Leaf))
Client: I don't need to check the components classes even when managing the tree:
RESULT: Branch(Branch(Leaf+Leaf)+Branch(Leaf)+Leaf)
الاستخدام في لغة PHP
المستوى: ★ ★ ☆
الانتشار: ★ ★ ★
أمثلة الاستخدام: يكثر استخدام نمط المركَّب في لغة PHP عند التعامل مع أشجار الكائنات، وأبسط مثال له قد يكون تطبيقه على عناصر شجرة DOM لتعمل مع نمط المركب والعناصر البسيطة للشجرة بنفس الأسلوب.
مثال: مثال تصوري
يوضح هذا المثال بنية نمط المركَّب، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
index.php: مثال تصوري
<?php
namespace RefactoringGuru\Composite\Conceptual;
/**
* تصرح فئة العنصر الأساسي عن العمليات المشتركة للكائنات البسيطة والمعقدة لمركب ما.
*/
abstract class Component
{
/**
* @var Component
*/
protected $parent;
/**
* لفئة العنصر الأساسي أن تصرح عن واجهة لإعداد أحد الأصول لعنصر ما والوصول إليه في هيكل شجري
* كما تضيف بعض الاستخدامات الافتراضية لهذه الأساليب.
*/
public function setParent(Component $parent)
{
$this->parent = $parent;
}
public function getParent(): Component
{
return $this->parent;
}
/**
* قد يفيد في بعض الحالات أن تعرّف عمليات إدارة الفروع في فئة العنصر الأساسي مباشرة، وهكذا لن تحتاج
* إلى كشف الفئات الحقيقية للعناصر إلى شيفرة العميل حتى أثناء تجميع الشجرة. لكن المشكلة هنا هي أن هذه
* الأساليب ستكون فارغة للعناصر التي في مستوى الورقة.
*/
public function add(Component $component): void { }
public function remove(Component $component): void { }
/**
* تستطيع إضافة أسلوب يسمح لشيفرة العميل أن تعرف إن كان عنصر ما يستطيع استيعاب فروع له
*/
public function isComposite(): bool
{
return false;
}
/**
* قد ينفذ العنصر الأساسي بعض السلوك الافتراضي أو لا ينفذه للفئات الحقيقية من خلال التصريح عن الأسلوب
* الحاوي للسلوك على أنه تجريدي.
*/
abstract public function operation(): string;
}
/**
* تمثل فئة الورقةُ الكائناتِ النهائيةَ لمركب ما، فلا يمكن لكائن الورقة
* أن يحتوي على كائنات فرعية
*
* عادة ما تنفذ كائنات الأوراق المهام التي توكل إليها بينما تفوض
* الكائنات المركبة تلك المهام إلى ما دونها.
*/
class Leaf extends Component
{
public function operation(): string
{
return "Leaf";
}
}
/**
* تمثل فئة المركب العناصر المعقدة التي قد تحتوي على فروع، وعادة ما تفوض
* كائنات المركب الأعمال الحقيقية لما دونها ثم تجمع النتيجة النهائية.
*/
class Composite extends Component
{
/**
* @var \SplObjectStorage
*/
protected $children;
public function __construct()
{
$this->children = new \SplObjectStorage;
}
/**
* يستطيع الكائن المركب أن يضيف أو يحذف عناصرً أخرى -بسيطة أو معقدة- من قائمة الفروع.
*/
public function add(Component $component): void
{
$this->children->attach($component);
$component->setParent($this);
}
public function remove(Component $component): void
{
$this->children->detach($component);
$component->setParent(null);
}
public function isComposite(): bool
{
return true;
}
/**
* ينفذ المركب منطقه الأساسي بأن يمر على فروعه كلها بشكل تكراري جامعًا نتائجها، وبما أن فروع
* المركب تمرر استدعاءاتها لما دونها فإن الشجرة بكاملها تُفحص نتيجة لهذا.
*/
public function operation(): string
{
$results = [];
foreach ($this->children as $child) {
$results[] = $child->operation();
}
return "Branch(" . implode("+", $results) . ")";
}
}
/**
* تعمل شيفرة العميل مع كل العناصر من خلال الواجهة الأساسية.
*/
function clientCode(Component $component)
{
// ...
echo "RESULT: " . $component->operation();
// ...
}
/**
* تستطيع شيفرة العميل بهذه الطريقة أن تدعم عناصر الورقة البسيطة.
*/
$simple = new Leaf;
echo "Client: I've got a simple component:\n";
clientCode($simple);
echo "\n\n";
/**
* ... إضافة إلى المركبات المعقدة.
*/
$tree = new Composite;
$branch1 = new Composite;
$branch1->add(new Leaf);
$branch1->add(new Leaf);
$branch2 = new Composite;
$branch2->add(new Leaf);
$tree->add($branch1);
$tree->add($branch2);
echo "Client: Now I've got a composite tree:\n";
clientCode($tree);
echo "\n\n";
/**
* تستطيع شيفرة العميل أن تعمل مع أي عنصر بسيط أو معقد دون الاعتماد على الفئات الحقيقية
* للعناصر، وذلك بفضل كون التصريح عن عمليات إدارة الفروع في فئة العنصر الأساسي.
*/
function clientCode2(Component $component1, Component $component2)
{
// ...
if ($component1->isComposite()) {
$component1->add($component2);
}
echo "RESULT: " . $component1->operation();
// ...
}
echo "Client: I don't need to check the components classes even when managing the tree:\n";
clientCode2($tree, $simple);
Output.txt: المخرجات
Client: I get a simple component:
RESULT: Leaf
Client: Now I get a composite tree:
RESULT: Branch(Branch(Leaf+Leaf)+Branch(Leaf))
Client: I don't need to check the components classes even when managing the tree::
RESULT: Branch(Branch(Leaf+Leaf)+Branch(Leaf)+Leaf)
مثال: حالة واقعية
يستطيع نمط المركب أن ينظم العمل مع أي هيكل تكراري شجري، كما في مثال شجرة HTML DOM، ففي حين تتصرف عناصر الإدخال كالأوراق، تلعب العناصر المعقدة مثل الاستمارات ووسوم fieldsets دور المركَّبات.
ويمكنك استخدام نمط المركب لتطبيق سلوكيات مختلفة إلى كامل شجرة HTML بنفس أسلوب تطبيقها على عناصرها الداخلية دون ربط شيفرتك بالفئات الحقيقية لشجرة DOM ولا تصديرها إلى صيغ مختلفة أو التأكد من صحة أجزائها، إلخ.
ولا تحتاج كذلك مع نمط المركب إلى تفقد ما إن كان العنصر من النوع البسيط أو المعقد قبل تنفيذ السلوك، إذ يُنفَّذ مباشرة أو يُمرر إلى العناصر الفرعية للعنصر، اعتمادًا على نوع العنصر نفسه.
index.php: حالة واقعية
<?php
namespace RefactoringGuru\Composite\RealWorld;
/**
* تصرح فئة العنصر الأساسي عن واجهة لكل العناصر الحقيقية سواء البسيطة أو المعقدة.
* DOM لعناصر (Rendering Behaviour) وسنركز في مثالنا على السلوك الإخراجي
*/
abstract class FormElement
{
/**
* تتطلب هذه الحقول الثلاثة DOM نستطيع توقع أن كل عناصر
*/
protected $name;
protected $title;
protected $data;
public function __construct(string $name, string $title)
{
$this->name = $name;
$this->title = $title;
}
public function getName(): string
{
return $this->name;
}
public function setData($data): void
{
$this->data = $data;
}
public function getData(): array
{
return $this->data;
}
/**
* حقيقي بتنفيذه الإخراجي، لكن يمكننا الافتراض DOM يجب أن يزودنا كل عنصر
* (Strings) أنها جميعًا ستعيد مقاطع نصية
*/
abstract public function render(): string;
}
/**
* هذا عنصر من نوع الورقة، وككل عناصر الأوراق فإنه لا يملك أي فروع له.
*/
class Input extends FormElement
{
private $type;
public function __construct(string $name, string $title, string $type)
{
parent::__construct($name, $title);
$this->type = $type;
}
/**
* بما أن الأوراق ليس لها فروع تفوض إليها المهام فإنها تنفذ المهام التي توكل إليها بأنفسها
* في نمط المركب.
*/
public function render(): string
{
return "<label for=\"{$this->name}\">{$this->title}</label>\n" .
"<input name=\"{$this->name}\" type=\"{$this->type}\" value=\"{$this->data}\">\n";
}
}
/**
* تنفذ الفئةُ الأساسيةُ للمركب البنيةَ التحتيةَ لإدارة الكائنات الفرعية التي يعاد استخدامها
* بواسطة كل المركبات الحقيقية.
*
*/
abstract class FieldComposite extends FormElement
{
/**
* @var FormElement[]
*/
protected $fields = [];
/**
* أساليب إضافة أو حذف الكائنات الفرعية.
*/
public function add(FormElement $field): void
{
$name = $field->getName();
$this->fields[$name] = $field;
}
public function remove(FormElement $component): void
{
$this->fields = array_filter($this->fields, function ($child) use ($component) {
return $child == $component;
});
}
/**
* يجب أن يضع أسلوب المركب كائناته الفرعية في حساباته في حين أن أسلوب الورقة عادة ما ينفذ
* المهام بنفسه، ويستطيع المركب في تلك الحالة أن يقبل البيانات المهيكلة.
* @param array $data
*/
public function setData($data): void
{
foreach ($this->fields as $name => $field) {
if (isset($data[$name])) {
$field->setData($data[$name]);
}
}
}
/**
* الذي يعيد البيانات المهيكلة من المركب نفسه إن وجد (Getter) ينطبق نفس المنطق على المستخلِص
* وكل بيانات الفروع.
*/
public function getData(): array
{
$data = [];
foreach ($this->fields as $name => $field) {
$data[$name] = $field->getData();
}
return $data;
}
/**
* التنفيذ الأساسي لإخراج المركب يجمع نتائج كل فروعه، وستكون المركبات الحقيقية قادرة على إعادة
* استخدام هذا التنفيذ في استخدامات حقيقية.
*/
public function render(): string
{
$output = "";
foreach ($this->fields as $name => $field) {
$output .= $field->render();
}
return $output;
}
}
/**
* هو مركب حقيقي fieldset عنصر.
*/
class Fieldset extends FieldComposite
{
public function render(): string
{
// fieldset لاحظ كيف تُدمج نتيجة الإخراج المُجمع للفروع في وسم
$output = parent::render();
return "<fieldset><legend>{$this->title}</legend>\n$output</fieldset>\n";
}
}
/**
* form وكذا عنصر .
*/
class Form extends FieldComposite
{
protected $url;
public function __construct(string $name, string $title, string $url)
{
parent::__construct($name, $title);
$this->url = $url;
}
public function render(): string
{
$output = parent::render();
return "<form action=\"{$this->url}\">\n<h3>{$this->title}</h3>\n$output</form>\n";
}
}
/**
* تحصل شيفرة العميل على واجهة مريحة لبناء هياكل شجرية معقدة.
*/
function getProductForm(): FormElement
{
$form = new Form('product', "Add product", "/product/add");
$form->add(new Input('name', "Name", 'text'));
$form->add(new Input('description', "Description", 'text'));
$picture = new Fieldset('photo', "Product photo");
$picture->add(new Input('caption', "Caption", 'text'));
$picture->add(new Input('image', "Image", 'file'));
$form->add($picture);
return $form;
}
/**
* يمكن ملء هياكل الاستمارات ببيانات من مصادر مختلفة، وليس على العميل أن يفحص كل حقول الاستمارات
* لتعيين بيانات إلى الحقول المختلفة بما أن الاستمارة نفسها يمكنها معالجة ذلك.
*/
function loadProductData(FormElement $form)
{
$data = [
'name' => 'Apple MacBook',
'description' => 'A decent laptop.',
'photo' => [
'caption' => 'Front photo.',
'image' => 'photo1.png',
],
];
$form->setData($data);
}
/**
* تستطيع شيفرة العميل أن تعمل مع عناصر الاستمارات باستخدام الواجهة المجردة، وهكذا لا يهم
* إن كان العميل يعمل مع عناصر بسيطة أو شجرة مركب معقدة.
*/
function renderProduct(FormElement $form)
{
// ..
echo $form->render();
// ..
}
$form = getProductForm();
loadProductData($form);
renderProduct($form);
Output.txt: المخرجات
<form action="/product/add">
<h3>Add product</h3>
<label for="name">Name</label>
<input name="name" type="text" value="Apple MacBook">
<label for="description">Description</label>
<input name="description" type="text" value="A decent laptop.">
<fieldset><legend>Product photo</legend>
<label for="caption">Caption</label>
<input name="caption" type="text" value="Front photo.">
<label for="image">Image</label>
<input name="image" type="file" value="photo1.png">
</fieldset>
</form>
الاستخدام في لغة بايثون
المستوى: ★ ★ ☆
الانتشار: ★ ★ ★
أمثلة الاستخدام: يكثر استخدام نمط المركَّب في لغة بايثون، إذ يُستخدم لتمثيل هرميات لعناصر من واجهة المستخدم أو شيفرة تعمل مع الرسوم التوضيحية (Graphs).
يمكن ملاحظة نمط المركب من خلال الأساليب السلوكية التي تأخذ نسخة من نفس النوع التجريدي/التنفيذي إلى هيكل شجري.
مثال: مثال تصوري
يوضح هذا المثال بنية نمط الجسر، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
main.py: مثال تصوري
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import List
class Component(ABC):
"""
تصرح فئة العنصر الأساسي عن العمليات المشتركة للكائنات البسيطة والمعقدة لمركب ما.
"""
@property
def parent(self) -> Component:
return self._parent
@parent.setter
def parent(self, parent: Component):
"""
لفئة العنصر الأساسي أن تصرح عن واجهة لإعداد أحد الأصول لعنصر ما والوصول إليه في هيكل شجري
كما تضيف بعض الاستخدامات الافتراضية لهذه الأساليب.
"""
self._parent = parent
"""
قد يفيد في بعض الحالات أن تعرّف عمليات إدارة الفروع في فئة العنصر الأساسي مباشرة، وهكذا لن تحتاج
إلى كشف الفئات الحقيقية للعناصر إلى شيفرة العميل حتى أثناء تجميع الشجرة. لكن المشكلة هنا هي أن
هذه الأساليب ستكون فارغة للعناصر التي في مستوى الورقة.
"""
def add(self, component: Component) -> None:
pass
def remove(self, component: Component) -> None:
pass
def is_composite(self) -> bool:
"""
تستطيع إضافة أسلوب يسمح لشيفرة العميل أن تعرف إن كان عنصر ما يستطيع استيعاب فروع له
"""
return False
@abstractmethod
def operation(self) -> str:
"""
قد ينفذ العنصر الأساسي بعض السلوك الافتراضي أو لا ينفذه للفئات الحقيقية من خلال التصريح عن
الأسلوب الحاوي له على أنه تجريدي.
"""
pass
class Leaf(Component):
"""
تمثل فئة الورقةُ الكائناتِ النهائيةَ لمركب ما، فلا يمكن لكائن الورقة أن يحتوي على كائنات فرعية
عادة ما تنفذ كائنات الأوراق المهام التي توكل إليها بينما تفوض الكائنات المركبة تلك المهام إلى
ما دونها.
"""
def operation(self) -> str:
return "Leaf"
class Composite(Component):
"""
تمثل فئة المركب العناصر المعقدة التي قد تحتوي على فروع، وعادة ما تفوض
كائنات المركب الأعمال الحقيقية لما دونها ثم تجمع النتيجة النهائية.
"""
def __init__(self) -> None:
self._children: List[Component] = []
"""
يستطيع الكائن المركب أن يضيف أو يحذف عناصرً أخرى -بسيطة أو معقدة- من قائمة الفروع.
"""
def add(self, component: Component) -> None:
self._children.append(component)
component.parent = self
def remove(self, component: Component) -> None:
self._children.remove(component)
component.parent = None
def is_composite(self) -> bool:
return True
def operation(self) -> str:
"""
ينفذ المركب منطقه الأساسي بأن يمر على فروعه كلها بشكل تكراري جامعًا نتائجها، وبما أن فروع
المركب تمرر استدعاءاتها لما دونها فإن الشجرة بكاملها تُفحص نتيجة لهذا.
"""
results = []
for child in self._children:
results.append(child.operation())
return f"Branch({'+'.join(results)})"
def client_code(component: Component) -> None:
"""
تعمل شيفرة العميل مع كل العناصر من خلال الواجهة الأساسية
"""
print(f"RESULT: {component.operation()}", end="")
def client_code2(component1: Component, component2: Component) -> None:
"""
تستطيع شيفرة العميل أن تعمل مع أي عنصر بسيط أو معقد دون الاعتماد على الفئات الحقيقية
للعناصر، وذلك بفضل كون التصريح عن عمليات إدارة الفروع في فئة العنصر الأساسي.
"""
if component1.is_composite():
component1.add(component2)
print(f"RESULT: {component1.operation()}", end="")
if __name__ == "__main__":
# تستطيع شيفرة العميل بهذه الطريقة أن تدعم عناصر الورقة البسيطة.
simple = Leaf()
print("Client: I've got a simple component:")
client_code(simple)
print("\n")
# ... إضافة إلى المركبات المعقدة.
tree = Composite()
branch1 = Composite()
branch1.add(Leaf())
branch1.add(Leaf())
branch2 = Composite()
branch2.add(Leaf())
tree.add(branch1)
tree.add(branch2)
print("Client: Now I've got a composite tree:")
client_code(tree)
print("\n")
print("Client: I don't need to check the components classes even when managing the tree:")
client_code2(tree, simple)
Output.txt: المخرجات
Client: I've got a simple component:
RESULT: Leaf
Client: Now I've got a composite tree:
RESULT: Branch(Branch(Leaf+Leaf)+Branch(Leaf))
Client: I don't need to check the components classes even when managing the tree:
RESULT: Branch(Branch(Leaf+Leaf)+Branch(Leaf)+Leaf)
الاستخدام في لغة روبي
المستوى: ★ ★ ☆
الانتشار: ★ ★ ☆
أمثلة الاستخدام: يكثر استخدام نمط المركَّب في لغة بايثون، إذ يُستخدم لتمثيل هرميات لعناصر من واجهة المستخدم أو شيفرة تعمل مع الرسوم التوضيحية (Graphs).
يمكن ملاحظة نمط المركب من خلال الأساليب السلوكية التي تأخذ نسخة من نفس النوع التجريدي/التنفيذي إلى هيكل شجري.
مثال: مثال تصوري
يوضح هذا المثال بنية نمط الجسر، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
# تصرح فئة العنصر الأساسي عن العمليات المشتركة للكائنات
# البسيطة والمعقدة لمركب ما.
class Component
# @return [Component]
def parent
@parent
end
# لفئة العنصر الأساسي أن تصرح عن واجهة لإعداد أحد الأصول لعنصر ما
# والوصول إليه في هيكل شجري كما تضيف بعض الاستخدامات
# الافتراضية لهذه الأساليب.
def parent=(parent)
@parent = parent
end
# قد يفيد في بعض الحالات أن تعرّف عمليات إدارة الفروع في فئة العنصر الأساسي
# مباشرة، وهكذا لن تحتاج إلى كشف الفئات الحقيقية للعناصر إلى شيفرة العميل
# حتى أثناء تجميع الشجرة. لكن المشكلة هنا هي أن هذه الأساليب ستكون فارغة
# للعناصر التي في مستوى الورقة.
def add(component)
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
# @abstract
#
# @param [Component] component
def remove(component)
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
# تستطيع إضافة أسلوب يسمح لشيفرة العميل أن تعرف إن كان
# عنصر ما يستطيع استيعاب فروع له
def composite?
false
end
# قد يطبق العنصر الأساسي بعض السلوك الافتراضي أو لا يطبقه للفئات
# الحقيقية من خلال التصريح عن الأسلوب الحاوي له على أنه تجريدي.
def operation
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
end
# تمثل فئة الورقةُ الكائناتِ النهائيةَ لمركب ما، فلا يمكن
# لكائن الورقة أن يحتوي على كائنات فرعية
#
# عادة ما تنفذ كائنات الأوراق المهام التي توكل إليها
# بينما تفوض الكائنات المركبة تلك المهام إلى ما دونها.
class Leaf < Component
# return [String]
def operation
'Leaf'
end
end
# تمثل فئة المركب العناصر المعقدة التي قد تحتوي على فروع، وعادة ما تفوض
# كائنات المركب الأعمال الحقيقية لما دونها ثم تجمع النتيجة النهائية.
class Composite < Component
def initialize
@children = []
end
# يستطيع الكائن المركب أن يضيف أو يحذف عناصرً
# أخرى -بسيطة أو معقدة- من قائمة الفروع.
# @param [Component] component
def add(component)
@children.append(component)
component.parent = self
end
# @param [Component] component
def remove(component)
@children.remove(component)
component.parent = nil
end
# @return [Boolean]
def composite?
true
end
# ينفذ المركب منطقه الأساسي بأن يمر على فروعه كلها بشكل تكراري جامعًا
# نتائجها، وبما أن فروع المركب تمرر استدعاءاتها لما دونها
# فإن الشجرة بكاملها تُفحص نتيجة لهذا.
def operation
results = []
@children.each { |child| results.append(child.operation) }
"Branch(#{results.join('+')})"
end
end
# تعمل شيفرة العميل مع كل العناصر من خلال الواجهة الأساسية.
def client_code(component)
puts "RESULT: #{component.operation}"
end
# تستطيع شيفرة العميل أن تعمل مع أي عنصر بسيط أو معقد
# دون الاعتماد على الفئات الحقيقية للعناصر، وذلك بفضل وجود التصريح عن
# عمليات إدارة الفروع في فئة العنصر الأساسي
def client_code2(component1, component2)
component1.add(component2) if component1.composite?
print "RESULT: #{component1.operation}"
end
# تستطيع شيفرة العميل بهذه الطريقة أن تدعم عناصر الورقة البسيطة....
simple = Leaf.new
puts 'Client: I\'ve got a simple component:'
client_code(simple)
puts "\n"
# ...إضافة إلى المركبات المعقدة.
tree = Composite.new
branch1 = Composite.new
branch1.add(Leaf.new)
branch1.add(Leaf.new)
branch2 = Composite.new
branch2.add(Leaf.new)
tree.add(branch1)
tree.add(branch2)
puts 'Client: Now I\'ve got a composite tree:'
client_code(tree)
puts "\n"
puts 'Client: I don\'t need to check the components classes even when managing the tree:'
client_code2(tree, simple)
output.txt: نتائج التنفيذ
Client: I've got a simple component:
RESULT: Leaf
Client: Now I've got a composite tree:
RESULT: Branch(Branch(Leaf+Leaf)+Branch(Leaf))
Client: I don't need to check the components classes even when managing the tree:
RESULT: Branch(Branch(Leaf+Leaf)+Branch(Leaf)+Leaf)
الاستخدام في لغة Swift
المستوى: ★ ★ ☆
الانتشار: ★ ★ ★
أمثلة الاستخدام: يكثر استخدام نمط المركَّب في لغة Swift، إذ يُستخدم لتمثيل هرميات لعناصر من واجهة المستخدم أو شيفرة تعمل مع الرسوم التوضيحية (Graphs).
يمكن ملاحظة نمط المركب من خلال الأساليب السلوكية التي تأخذ نسخة من نفس النوع التجريدي/التنفيذي إلى هيكل شجري.
مثال: مثال تصوري
يوضح هذا المثال بنية نمط الجسر، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
بعد تعلم بنية النمط سيكون من السهل عليك استيعاب المثال التالي المبني على حالة واقعية في لغة Swift.
Example.swift: مثال تصوري
import XCTest
/// تصرح فئة العنصر الأساسي عن العمليات المشتركة للكائنات
/// البسيطة والمعقدة لمركب ما.
protocol Component {
/// لفئة العنصر الأساسي أن تصرح عن واجهة لإعداد أحد الأصول لعنصر ما والوصول
/// إليه في هيكل شجري كما تضيف بعض الاستخدامات الافتراضية لهذه الأساليب.
var parent: Component? { get set }
/// قد يفيد في بعض الحالات أن تعرّف عمليات إدارة الفروع في فئة
/// العنصر الأساسي مباشرة، وهكذا لن تحتاج إلى كشف الفئات الحقيقية
/// للعناصر إلى شيفرة العميل حتى أثناء تجميع الشجرة. لكن المشكلة هنا
/// هي أن هذه الأساليب ستكون فارغة للعناصر التي في مستوى الورقة.
func add(component: Component)
func remove(component: Component)
/// تستطيع إضافة أسلوب يسمح لشيفرة العميل أن تعرف إن كان عنصر
/// ما يستطيع استيعاب فروع له
func isComposite() -> Bool
/// قد يطبق العنصر الأساسي بعض السلوك الافتراضي أو يتركه للفئات الحقيقية.
func operation() -> String
}
extension Component {
func add(component: Component) {}
func remove(component: Component) {}
func isComposite() -> Bool {
return false
}
}
/// تمثل فئة الورقةُ الكائناتِ النهائيةَ لمركب ما، فلا
/// يمكن لكائن الورقة أن يحتوي على كائنات فرعية.
///
/// عادة ما تنفذ كائنات الأوراق المهام التي توكل إليها
/// بينما تفوض الكائنات المركبة تلك المهام إلى ما دونها.
class Leaf: Component {
var parent: Component?
func operation() -> String {
return "Leaf"
}
}
/// تمثل فئة المركب العناصر المعقدة التي قد تحتوي على فروع،
/// وعادة ما تفوض كائنات المركب الأعمال الحقيقية لما دونها
/// ثم تجمع النتيجة النهائية.
class Composite: Component {
var parent: Component?
/// يحتوي هذا الحقل على الشجرة الفرعية للعناصر.
private var children = [Component]()
/// يستطيع الكائن المركب أن يضيف أو يحذف عناصرً أخرى -بسيطة أو معقدة-
/// من قائمة الفروع.
func add(component: Component) {
var item = component
item.parent = self
children.append(item)
}
func remove(component: Component) {
// ...
}
func isComposite() -> Bool {
return true
}
/// ينفذ المركب منطقه الأساسي بأن يمر على فروعه كلها بشكل
/// تكراري جامعًا نتائجها، وبما أن فروع المركب تمرر استدعاءاتها لما
/// دونها فإن الشجرة بكاملها تُفحص نتيجة لهذا.
func operation() -> String {
let result = children.map({ $0.operation() })
return "Branch(" + result.joined(separator: " ") + ")"
}
}
class Client {
/// تعمل شيفرة العميل مع كل العناصر من خلال الواجهة الأساسية.
static func someClientCode(component: Component) {
print("Result: " + component.operation())
}
/// تستطيع شيفرة العميل أن تعمل مع أي عنصر بسيط أو معقد
/// دون الاعتماد على الفئات الحقيقية، وذلك بفضل وجود التصريح عن
/// عمليات إدارة الفروع في فئة العنصر الأساسي.
static func moreComplexClientCode(leftComponent: Component, rightComponent: Component) {
if leftComponent.isComposite() {
leftComponent.add(component: rightComponent)
}
print("Result: " + leftComponent.operation())
}
}
/// لنرى كيف سيعمل كل ذلك...
class CompositeConceptual: XCTestCase {
func testCompositeConceptual() {
/// تستطيع شيفرة العميل بهذه الطريقة أن تدعم عناصر الورقة البسيطة....
print("Client: I've got a simple component:")
Client.someClientCode(component: Leaf())
/// ...إضافة إلى المركبات المعقدة.
let tree = Composite()
let branch1 = Composite()
branch1.add(component: Leaf())
branch1.add(component: Leaf())
let branch2 = Composite()
branch2.add(component: Leaf())
branch2.add(component: Leaf())
tree.add(component: branch1)
tree.add(component: branch2)
print("\nClient: Now I've got a composite tree:")
Client.someClientCode(component: tree)
print("\nClient: I don't need to check the components classes even when managing the tree:")
Client.moreComplexClientCode(leftComponent: tree, rightComponent: Leaf())
}
}
Output.txt: نتائج التنفيذ
Client: I've got a simple component:
Result: Leaf
Client: Now I've got a composite tree:
Result: Branch(Branch(Leaf Leaf) Branch(Leaf Leaf))
Client: I don't need to check the components classes even when managing the tree:
Result: Branch(Branch(Leaf Leaf) Branch(Leaf Leaf) Leaf)
مثال واقعي
Example.swift: مثال واقعي
import UIKit
import XCTest
protocol Component {
func accept<T: Theme>(theme: T)
}
extension Component where Self: UIViewController {
func accept<T: Theme>(theme: T) {
view.accept(theme: theme)
view.subviews.forEach({ $0.accept(theme: theme) })
}
}
extension UIView: Component {}
extension UIViewController: Component {}
extension Component where Self: UIView {
func accept<T: Theme>(theme: T) {
print("\t\(description): has applied \(theme.description)")
backgroundColor = theme.backgroundColor
}
}
extension Component where Self: UILabel {
func accept<T: LabelTheme>(theme: T) {
print("\t\(description): has applied \(theme.description)")
backgroundColor = theme.backgroundColor
textColor = theme.textColor
}
}
extension Component where Self: UIButton {
func accept<T: ButtonTheme>(theme: T) {
print("\t\(description): has applied \(theme.description)")
backgroundColor = theme.backgroundColor
setTitleColor(theme.textColor, for: .normal)
setTitleColor(theme.highlightedColor, for: .highlighted)
}
}
protocol Theme: CustomStringConvertible {
var backgroundColor: UIColor { get }
}
protocol ButtonTheme: Theme {
var textColor: UIColor { get }
var highlightedColor: UIColor { get }
/// خصائص أخرى.
}
protocol LabelTheme: Theme {
var textColor: UIColor { get }
/// خصائص أخرى.
}
/// سمات الأزرار.
struct DefaultButtonTheme: ButtonTheme {
var textColor = UIColor.red
var highlightedColor = UIColor.white
var backgroundColor = UIColor.orange
var description: String { return "Default Buttom Theme" }
}
struct NightButtonTheme: ButtonTheme {
var textColor = UIColor.white
var highlightedColor = UIColor.red
var backgroundColor = UIColor.black
var description: String { return "Night Buttom Theme" }
}
/// Label themes سمات العناوين.
struct DefaultLabelTheme: LabelTheme {
var textColor = UIColor.red
var backgroundColor = UIColor.black
var description: String { return "Default Label Theme" }
}
struct NightLabelTheme: LabelTheme {
var textColor = UIColor.white
var backgroundColor = UIColor.black
var description: String { return "Night Label Theme" }
}
class CompositeRealWorld: XCTestCase {
func testCompositeRealWorld() {
print("\nClient: Applying 'default' theme for 'UIButton'")
apply(theme: DefaultButtonTheme(), for: UIButton())
print("\nClient: Applying 'night' theme for 'UIButton'")
apply(theme: NightButtonTheme(), for: UIButton())
print("\nClient: Let's use View Controller as a composite!")
/// السمة الليلية.
print("\nClient: Applying 'night button' theme for 'WelcomeViewController'...")
apply(theme: NightButtonTheme(), for: WelcomeViewController())
print()
print("\nClient: Applying 'night label' theme for 'WelcomeViewController'...")
apply(theme: NightLabelTheme(), for: WelcomeViewController())
print()
/// السمة الافتراضية.
print("\nClient: Applying 'default button' theme for 'WelcomeViewController'...")
apply(theme: DefaultButtonTheme(), for: WelcomeViewController())
print()
print("\nClient: Applying 'default label' theme for 'WelcomeViewController'...")
apply(theme: DefaultLabelTheme(), for: WelcomeViewController())
print()
}
func apply<T: Theme>(theme: T, for component: Component) {
component.accept(theme: theme)
}
}
class WelcomeViewController: UIViewController {
class ContentView: UIView {
var titleLabel = UILabel()
var actionButton = UIButton()
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder decoder: NSCoder) {
super.init(coder: decoder)
setup()
}
func setup() {
addSubview(titleLabel)
addSubview(actionButton)
}
}
override func loadView() {
view = ContentView()
}
}
/// من أجل مخرجات أفضل description property غير خصائص الوصف .
extension WelcomeViewController {
open override var description: String { return "WelcomeViewController" }
}
extension WelcomeViewController.ContentView {
override var description: String { return "ContentView" }
}
extension UIButton {
open override var description: String { return "UIButton" }
}
extension UILabel {
open override var description: String { return "UILabel" }
}
Output.txt: نتائج التنفيذ
Client: Applying 'default' theme for 'UIButton'
UIButton: has applied Default Buttom Theme
Client: Applying 'night' theme for 'UIButton'
UIButton: has applied Night Buttom Theme
Client: Let's use View Controller as a composite!
Client: Applying 'night button' theme for 'WelcomeViewController'...
ContentView: has applied Night Buttom Theme
UILabel: has applied Night Buttom Theme
UIButton: has applied Night Buttom Theme
الاستخدام في لغة TypeScript
المستوى: ★ ★ ☆
الانتشار: ★ ★ ★
أمثلة الاستخدام: يكثر استخدام نمط المركَّب في لغة TypeScript، إذ يُستخدم لتمثيل هرميات لعناصر من واجهة المستخدم أو شيفرة تعمل مع الرسوم التوضيحية (Graphs).
يمكن ملاحظة نمط المركب من خلال الأساليب السلوكية التي تأخذ نسخة من نفس النوع التجريدي/التنفيذي إلى هيكل شجري.
مثال: مثال تصوري
يوضح هذا المثال بنية نمط الجسر، ويركز على إجابة الأسئلة التالية:
- ما الفئات التي يتكون منها؟
- ما الأدوار التي تلعبها هذه الفئات؟
- كيف ترتبط عناصر النمط ببعضها؟
index.ts: مثال تصوري
/**
* تصرح فئة العنصر الأساسي عن العمليات المشتركة للكائنات
* البسيطة والمعقدة لمركب ما.
*/
abstract class Component {
protected parent: Component;
/**
* لفئة العنصر الأساسي أن تصرح عن واجهة لإعداد أحد الأصول لعنصر ما والوصول
* إليه في هيكل شجري كما تضيف بعض الاستخدامات الافتراضية لهذه الأساليب.
*/
public setParent(parent: Component) {
this.parent = parent;
}
public getParent(): Component {
return this.parent;
}
/**
* قد يفيد في بعض الحالات أن تعرّف عمليات إدارة الفروع في فئة
* العنصر الأساسي مباشرة، وهكذا لن تحتاج إلى كشف الفئات الحقيقية
* للعناصر إلى شيفرة العميل حتى أثناء تجميع الشجرة. لكن المشكلة هنا
* هي أن هذه الأساليب ستكون فارغة للعناصر التي في مستوى الورقة.
*/
public add(component: Component): void { }
public remove(component: Component): void { }
/**
* تستطيع إضافة أسلوب يسمح لشيفرة العميل أن تعرف إن كان عنصر
* ما يستطيع استيعاب فروع له
*/
public isComposite(): boolean {
return false;
}
/**
* قد يطبق العنصر الأساسي بعض السلوك الافتراضي أو يتركه للفئات الحقيقية.
* عبر التصريح عن الأسلوب الحاوي للسلوك على أنه مجرد.
*/
public abstract operation(): string;
}
/**
* تمثل فئة الورقةُ الكائناتِ النهائيةَ لمركب ما، فلا
* يمكن لكائن الورقة أن يحتوي على كائنات فرعية.
*
* عادة ما تنفذ كائنات الأوراق المهام التي توكل إليها
* بينما تفوض الكائنات المركبة تلك المهام إلى ما دونها.
*/
class Leaf extends Component {
public operation(): string {
return 'Leaf';
}
}
/**
* تمثل فئة المركب العناصر المعقدة التي قد تحتوي على فروع،
* وعادة ما تفوض كائنات المركب الأعمال الحقيقية لما دونها
* ثم تجمع النتيجة النهائية.
*/
class Composite extends Component {
protected children: Component[] = [];
/**
* يستطيع الكائن المركب أن يضيف أو يحذف عناصرً أخرى -بسيطة أو معقدة-
* من قائمة الفروع.
*/
public add(component: Component): void {
this.children.push(component);
component.setParent(this);
}
public remove(component: Component): void {
const componentIndex = this.children.indexOf(component);
this.children.splice(componentIndex, 1);
component.setParent(null);
}
public isComposite(): boolean {
return true;
}
/**
* ينفذ المركب منطقه الأساسي بأن يمر على فروعه كلها بشكل
* تكراري جامعًا نتائجها، وبما أن فروع المركب تمرر استدعاءاتها لما
* دونها فإن الشجرة بكاملها تُفحص نتيجة لهذا.
*/
public operation(): string {
const results = [];
for (const child of this.children) {
results.push(child.operation());
}
return `Branch(${results.join('+')})`;
}
}
/**
* تعمل شيفرة العميل مع كل العناصر من خلال الواجهة الأساسية.
*/
function clientCode(component: Component) {
// ...
console.log(`RESULT: ${component.operation()}`);
// ...
}
/**
* تستطيع شيفرة العميل بهذه الطريقة أن تدعم عناصر الورقة البسيطة....
*/
const simple = new Leaf();
console.log('Client: I\'ve got a simple component:');
clientCode(simple);
console.log('');
/**
* ...إضافة إلى المركبات المعقدة.
*/
const tree = new Composite();
const branch1 = new Composite();
branch1.add(new Leaf());
branch1.add(new Leaf());
const branch2 = new Composite();
branch2.add(new Leaf());
tree.add(branch1);
tree.add(branch2);
console.log('Client: Now I\'ve got a composite tree:');
clientCode(tree);
console.log('');
/**
* تستطيع شيفرة العميل أن تعمل مع أي عنصر بسيط أو معقد.
* دون الاعتماد على الفئات الحقيقية، وذلك بفضل وجود التصريح عن
* عمليات إدارة الفروع في فئة العنصر الأساسي.
*/
function clientCode2(component1: Component, component2: Component) {
// ...
if (component1.isComposite()) {
component1.add(component2);
}
console.log(`RESULT: ${component1.operation()}`);
// ...
}
console.log('Client: I don\'t need to check the components classes even when managing the tree:');
clientCode2(tree, simple);
Output.txt: نتائج التنفيذ
Client: I've got a simple component:
RESULT: Leaf
Client: Now I've got a composite tree:
RESULT: Branch(Branch(Leaf+Leaf)+Branch(Leaf))
Client: I don't need to check the components classes even when managing the tree:
RESULT: Branch(Branch(Leaf+Leaf)+Branch(Leaf)+Leaf)