نمط مركب

في هندسة البرمجيات ، يُعدّ نمط التركيب نمط تصميم للتقسيم . يصف هذا النمط مجموعة من الكائنات التي تُعامل بنفس طريقة معاملة كائن واحد من نفس النوع. يهدف نمط التركيب إلى "تجميع" الكائنات في هياكل شجرية لتمثيل التسلسلات الهرمية بين الجزء والكل. يُمكّن تطبيق نمط التركيب العملاء من التعامل مع الكائنات الفردية والتراكيب بشكل موحد. [ 1 ]

ملخص

يُعد نمط التصميم المركب [ 2 ] أحد أنماط تصميم GoF الثلاثة والعشرين المعروفة التي تصف كيفية حل مشكلات التصميم المتكررة لتصميم برامج كائنية التوجه مرنة وقابلة لإعادة الاستخدام، أي كائنات يسهل تنفيذها وتغييرها واختبارها وإعادة استخدامها.

مشاكل

يحل النمط المركب هذه المشاكل:

  • قم بتمثيل التسلسل الهرمي للجزء والكل بحيث يمكن للعملاء التعامل مع أجزاء وأشياء الكل بشكل موحد.
  • قم بتمثيل التسلسل الهرمي للجزء والكل كهيكل شجري.

عند تعريف (1) Partالكائنات و(2) Wholeالكائنات التي تعمل كحاويات Partللكائنات، يجب على العملاء التعامل معها بشكل منفصل، مما يعقد كود العميل. [ 3 ]

حل

  • قم بتعريف واجهة موحدة Componentلكائنات الجزء ( Leaf) وكائنات الكل ( Composite).
  • Leafتقوم الكائنات الفردية بتنفيذ Componentالواجهة مباشرة، وتقوم Compositeالكائنات بإعادة توجيه الطلبات إلى مكوناتها الفرعية.

يُمكّن هذا العملاء من العمل عبر Componentالواجهة للتعامل Leafمع Compositeالكائنات بشكل موحد: Leafحيث تُنفّذ الكائنات الطلب مباشرةً، ثم Compositeتُعيد توجيه الطلب إلى مكوناتها الفرعية بشكل متكرر نزولاً في بنية الشجرة. وهذا يُسهّل تنفيذ فئات العميل وتغييرها واختبارها وإعادة استخدامها.

انظر أيضًا إلى مخطط فئات وكائنات UML أدناه.

تحفيز

عند التعامل مع البيانات ذات البنية الشجرية، غالبًا ما يضطر المبرمجون إلى التمييز بين العقدة الطرفية والفرع. هذا يجعل الشيفرة البرمجية أكثر تعقيدًا، وبالتالي أكثر عرضة للأخطاء. الحل هو واجهة تسمح بمعاملة الكائنات المعقدة والبدائية بشكل موحد. في البرمجة كائنية التوجه ، الكائن المركب هو كائن مصمم كتركيب لكائن واحد أو أكثر من الكائنات المتشابهة، والتي تُظهر جميعها وظائف متشابهة. يُعرف هذا بعلاقة " يحتوي على " بين الكائنات. [ 4 ] المفهوم الأساسي هو أنه يمكنك التعامل مع نسخة واحدة من الكائن تمامًا كما تتعامل مع مجموعة منها. غالبًا ما تكون العمليات التي يمكنك إجراؤها على جميع الكائنات المركبة مرتبطة بعلاقة القاسم المشترك الأدنى . على سبيل المثال، عند تعريف نظام لعرض أشكال مجمعة على الشاشة، سيكون من المفيد تعريف تغيير حجم مجموعة من الأشكال للحصول على نفس التأثير (بمعنى ما) لتغيير حجم شكل واحد.

متى يُستخدم

يُفضّل استخدام الكائنات المركبة عندما يتجاهل العملاء الفرق بين تركيب الكائنات والكائنات الفردية. [ 1 ] إذا وجد المبرمجون أنهم يستخدمون كائنات متعددة بنفس الطريقة، وغالبًا ما يكون لديهم كود متطابق تقريبًا للتعامل مع كل منها، فإن الكائنات المركبة تُعد خيارًا جيدًا؛ إذ يُصبح التعامل مع الكائنات الأولية والمركبة على أنها متجانسة أقل تعقيدًا في هذه الحالة.

بناء

مخطط فئات وكائنات UML

نموذج لرسم تخطيطي لفئات وكائنات UML لنمط التصميم المركب. [ 5 ]

في مخطط فئات UML أعلاه ، لا تشير الفئة إلى الفئتين و بشكل مباشر (بشكل منفصل). بدلاً من ذلك، تشير إلى الواجهة المشتركة ويمكنها التعامل مع و بشكل موحد. لا تحتوي الفئة على أي فئات فرعية وتُنفذ الواجهة مباشرةً. تحتفظ الفئة بحاوية للكائنات الفرعية ( ) وتُعيد توجيه الطلبات إليها ( ). ClientLeafCompositeClientComponentLeafCompositeLeafComponentCompositeComponentchildrenchildrenfor each child in children: child.operation()

يوضح مخطط تعاون الكائنات التفاعلات أثناء التشغيل: في هذا المثال، Clientيرسل الكائن طلبًا إلى الكائن الأعلى مستوى Composite(من النوع Component) في بنية الشجرة. يُعاد توجيه الطلب (يُنفذ على) جميع Componentالكائنات الفرعية ( Leafو Compositeالكائنات) نزولًا في بنية الشجرة.

تحديد العمليات المتعلقة بالأطفال
تحديد العمليات المتعلقة بالأبناء في نمط التصميم المركب. [ 6 ]

يوجد نوعان من تصميمات تعريف وتنفيذ العمليات المتعلقة بالعناصر الفرعية مثل إضافة/إزالة عنصر فرعي من/إلى الحاوية ( add(child)/remove(child)) والوصول إلى عنصر فرعي ( getChild()):

  • التصميم من أجل الشفافية: تُعرَّف العمليات المتعلقة بالعناصر الفرعية في Componentالواجهة. وهذا يُمكّن العملاء من التعامل Leafمع Compositeالكائنات بشكل موحد. ولكن سلامة النوع تُفقد لأن العملاء يستطيعون إجراء عمليات متعلقة بالعناصر الفرعية على Leafالكائنات.
  • تصميمٌ يضمن سلامة الأنواع: تُعرَّف العمليات المتعلقة بالعناصر الفرعية داخل Compositeالفئة فقط. يجب على العملاء التعامل Leafمع Compositeالكائنات بشكل مختلف. ولكن تتحقق سلامة الأنواع لأن العملاء لا يستطيعون إجراء عمليات متعلقة بالعناصر الفرعية على Leafالكائنات.

يقدم مؤلفو GoF نسخة معدلة من نمط التصميم المركب تركز على الشفافية أكثر من سلامة النوع، ويناقشون المفاضلات بين النهجين. [ 1 ]

يُعدّ النهج الآمن من حيث النوع مقبولاً بشكل خاص إذا كانت البنية المركبة ثابتة بعد إنشائها: لا يتطلب رمز الإنشاء شفافية لأنه يحتاج إلى معرفة الأنواع المستخدمة لإنشاء البنية المركبة. إذا لم يكن الرمز اللاحق بحاجة إلى تعديل البنية، فلا داعي لوجود عمليات معالجة العناصر الفرعية على Componentالواجهة.

مخطط فئات UML

نمط مركب في لغة النمذجة الموحدة (UML) .
عنصر
  • هو التجريد لجميع المكونات، بما في ذلك المكونات المركبة
  • يُعلن عن واجهة الكائنات في التركيب
  • (اختياري) يُعرّف واجهة للوصول إلى العنصر الأصل للمكون في البنية التكرارية، ويُنفّذها إذا كان ذلك مناسبًا.
ورقة
  • يمثل أشكال الأوراق في التكوين
  • ينفذ جميع أساليب المكونات
مركب
  • يمثل مكونًا مركبًا (مكونًا له مكونات فرعية)
  • يطبق أساليب للتلاعب بالأطفال
  • ينفذ جميع أساليب المكون، بشكل عام عن طريق تفويضها إلى أبنائه.
نمط مركب في LePUS3 .

تفاوت

كما هو موضح في أنماط التصميم ، يتضمن النمط أيضًا تضمين أساليب معالجة العناصر الفرعية في واجهة المكون الرئيسية، وليس فقط في الفئة الفرعية المركبة. أحيانًا تحذف الأوصاف الأحدث هذه الأساليب. [ 7 ]

مثال

يعتمد هذا التطبيق بلغة C++23 على التطبيق السابق لـ C++98 الموجود في الكتاب.

استيراد std ؛باستخدام std :: runtime_error ؛ باستخدام std :: shared_ptr ؛ باستخدام std :: string ؛ باستخدام std :: unique_ptr ؛ باستخدام std :: vector ؛// كائن المكون // يُعلن عن واجهة الكائنات في التركيب. class Equipment { private : string name ; double netPrice ; protected : Equipment () = default ;explicit Equipment ( const string & name ) : name { name }, netPrice { 0 } {} public : // يُنفذ السلوك الافتراضي للواجهة المشتركة بين جميع الفئات، حسب الاقتضاء. [[ nodiscard ]] virtual const string & getName () const noexcept { return name ; }دالة افتراضية void setName ( const string & name ) noexcept { this -> name = name ; }[[ nodiscard ]] virtual double getNetPrice () const noexcept { return netPrice ; }virtual void setNetPrice ( double netPrice ) noexcept { this -> netPrice = netPrice ; }// يُعلن عن واجهة للوصول إلى مكوناته الفرعية وإدارتها. virtual void add ( shared_ptr < Equipment > ) = 0 ; virtual void remove ( shared_ptr < Equipment > ) = 0 ; virtual ~ Equipment () = default ; };// كائن مُركّب // يُحدد سلوك المكونات التي تحتوي على مكونات فرعية. class CompositeEquipment : public Equipment { private : // يخزن المكونات الفرعية. using EquipmentList = vector < shared_ptr < Equipment >> ; EquipmentList equipments ; protected : CompositeEquipment () = default ;CompositeEquipment ( const string & name ) : Equipment ( name ), equipments { EquipmentList () } {} public : // تُنفذ العمليات المتعلقة بالعناصر الفرعية في واجهة Component. [[ nodiscard ]] virtual double getNetPrice () const noexcept override { double total = Equipment :: getNetPrice (); for ( const Equipment & i : equipments ) { total += i- > getNetPrice (); } return total ; }virtual void add ( shared_ptr < Equipment > equipment ) override { equipments . push_back ( equipment . get ()); }virtual void remove ( shared_ptr < Equipment > equipment ) override { equipments . remove ( equipment . get ()); } };// كائن الورقة // يمثل كائنات الورقة في التركيب. class FloppyDisk : public Equipment { public : explicit FloppyDisk ( const String & name ) : Equipment ( name ) {}// ليس للورقة أبناء. void add ( shared_ptr < Equipment > ) override { throw runtime_error ( "لا يمكن استدعاء FloppyDisk::add()!" ); }void remove ( shared_ptr < Equipment > ) override { throw runtime_error ( "لا يمكن استدعاء FloppyDisk::remove()!" ); } };class Chassis : public CompositeEquipment { public : explicit Chassis ( const string & name ) : CompositeEquipment ( name ) {} };int main () { shared_ptr < FloppyDisk > fd1 = std :: make_shared < FloppyDisk > ( "3.5in Floppy" ); fd1 -> setNetPrice ( 19.99 ); std :: println ( "{}: netPrice = {}" , fd1 -> getName (), fd1 -> getNetPrice );shared_ptr <FloppyDisk> fd2 = std :: make_shared <FloppyDisk> ( "5.25in Floppy" ) ; fd2- > setNetPrice ( 29.99 ) ; std :: println ( "{}: netPrice = {}" , fd2- > getName ( ) , fd2- > getNetPrice );unique_ptr <Chassis> ch = std :: make_unique <Chassis> ( " PC Chassis " ); ch- > setNetPrice ( 39.99 ); ch- > add ( fd1 ); ch- > add ( fd2 ); std :: println ( "{}: netPrice = {}" , ch- > getName ( ), ch- > getNetPrice );fd2 -> add ( fd1 ); }

مخرجات البرنامج هي

قرص مرن 3.5 بوصة : السعر الصافي = 19.99، قرص مرن 5.25 بوصة : السعر الصافي = 29.99، هيكل الكمبيوتر الشخصي : السعر الصافي = 89.97. تم إنهاء العملية بعد طرح نسخة من ' std :: runtime_error '. ما ( ) : FloppyDisk :: add

انظر أيضاً

مراجع

  1. 1 2 3 غاما، إريك؛ ريتشارد هيلم؛ رالف جونسون؛ جون إم. فليسيدس (1995). أنماط التصميم: عناصر البرمجيات القابلة لإعادة الاستخدام والموجهة للكائنات . أديسون-ويسلي. ص 395. ISBN  0-201-63361-2.
  2. إريك غاما، ريتشارد هيلم، رالف جونسون، جون فليسيدس (1994). أنماط التصميم: عناصر البرمجيات القابلة لإعادة الاستخدام والموجهة للكائنات . أديسون ويسلي. ص 163 وما بعدها . ISBN  0-201-63361-2.{{cite book}}: صيانة CS1: أسماء متعددة: قائمة المؤلفين ( رابط )
  3. "نمط التصميم المركب - المشكلة والحل والتطبيق" . w3sDesign.com . تم الاطلاع عليه بتاريخ 12 أغسطس 2017 .
  4. سكوت والترز (2004). كتاب أنماط تصميم بيرل . مؤرشف من الأصل بتاريخ 2016-03-08 . تم الاطلاع عليه بتاريخ 2010-01-18 .
  5. "نمط التصميم المركب - البنية والتعاون" . w3sDesign.com . تم الاطلاع عليه بتاريخ 12 أغسطس 2017 .
  6. "نمط التصميم المركب - التنفيذ" . w3sDesign.com . تم الاطلاع عليه بتاريخ 12 أغسطس 2017 .
  7. جيري، ديفيد (13 سبتمبر 2002). "نظرة على نمط التصميم المركب" . أنماط تصميم جافا. جافا وورلد . تم الاسترجاع في 20 يوليو 2020 .