نمط الزائر
نمط الزائر هو نمط تصميم برمجي يفصل الخوارزمية عن بنية الكائن . وبفضل هذا الفصل، يمكن إضافة عمليات جديدة إلى هياكل الكائنات الموجودة دون تعديلها. وهو أحد طرق تطبيق مبدأ الانفتاح/الإغلاق في البرمجة الكائنية وهندسة البرمجيات .
باختصار، يسمح الزائر بإضافة دوال افتراضية جديدة إلى مجموعة من الفئات ، دون تعديل الفئات نفسها. بدلاً من ذلك، يتم إنشاء فئة زائر تُنفذ جميع التخصصات المناسبة للدالة الافتراضية. يأخذ الزائر مرجع النسخة كمدخل، ويُنفذ الهدف من خلال الإرسال المزدوج .
إن لغات البرمجة التي تحتوي على أنواع الجمع ومطابقة الأنماط تلغي العديد من فوائد نمط الزائر، حيث أن فئة الزائر قادرة على التفرع بسهولة بناءً على نوع الكائن وتوليد خطأ في المترجم إذا تم تعريف نوع كائن جديد لا يتعامل معه الزائر بعد.
ملخص
نمط تصميم الزائر [ 1 ] هو واحد من أنماط تصميم عصابة الأربعة الثلاثة والعشرين .
المشاكل التي يمكن أن يحلها هذا النمط
- ينبغي أن يكون من الممكن تعريف عملية جديدة لبعض فئات بنية الكائن دون تغيير الفئات.
عندما تكون هناك حاجة إلى عمليات جديدة بشكل متكرر ويتكون هيكل الكائن من العديد من الفئات غير ذات الصلة، يصبح من غير المرن إضافة فئات فرعية جديدة في كل مرة تكون فيها عملية جديدة مطلوبة لأن "توزيع كل هذه العمليات عبر فئات العقد المختلفة يؤدي إلى نظام يصعب فهمه وصيانته وتغييره". [ 1 ]
الحل الموصوف بالنمط
- قم بتعريف كائن منفصل (زائر) يقوم بتنفيذ عملية يتم إجراؤها على عناصر بنية الكائن.
- يتنقل العملاء عبر بنية الكائن ويستدعون عملية إرسال تقبل (زائر) على عنصر ما، والتي "ترسل" (تفوض) الطلب إلى "كائن الزائر المقبول". ثم يقوم كائن الزائر بتنفيذ العملية على العنصر ("يزور العنصر").
وهذا يجعل من الممكن إنشاء عمليات جديدة بشكل مستقل عن فئات بنية الكائن عن طريق إضافة كائنات زائر جديدة.
انظر أيضًا إلى مخطط فئات وتسلسل UML أدناه.
تعريف
تُعرّف عصابة الأربعة الزائر على النحو التالي:
يمثل هذا الإجراء عمليةً تُنفَّذ على عناصر بنية كائن. يتيح لك الزائر تعريف عملية جديدة دون تغيير فئات العناصر التي تُطبَّق عليها.
إن طبيعة الزائر تجعله نمطًا مثاليًا للتكامل مع واجهات برمجة التطبيقات العامة، مما يسمح لعملائه بإجراء عمليات على فئة باستخدام فئة "زائرة" دون الحاجة إلى تعديل المصدر. [ 2 ]
المزايا
يُعد نقل العمليات إلى فئات الزوار مفيدًا عندما
- يتطلب الأمر إجراء العديد من العمليات غير المترابطة على بنية كائن واحد،
- الفئات التي تشكل بنية الكائن معروفة ولا يُتوقع تغييرها.
- يجب إضافة عمليات جديدة بشكل متكرر،
- تتضمن الخوارزمية عدة فئات من بنية الكائن، ولكن من المرغوب إدارتها في مكان واحد.
- يجب أن تعمل الخوارزمية عبر عدة تسلسلات هرمية مستقلة للفئات.
لكن من عيوب هذا النمط أنه يجعل توسيع التسلسل الهرمي للفئات أكثر صعوبة، حيث تتطلب الفئات الجديدة عادةً visitإضافة طريقة جديدة لكل زائر.
طلب
لنفترض تصميم نظام تصميم بمساعدة الحاسوب ثنائي الأبعاد (CAD). يتكون هذا النظام أساسًا من عدة أنواع لتمثيل الأشكال الهندسية الأساسية كالدائرة والخط والقوس. تُرتّب هذه العناصر في طبقات، وعلى رأس التسلسل الهرمي للأنواع يوجد الرسم، وهو ببساطة قائمة بالطبقات، بالإضافة إلى بعض الخصائص الإضافية.
تُعدّ عملية حفظ الرسم بتنسيق الملف الأصلي للنظام عمليةً أساسيةً في هذا التسلسل الهرمي للأنواع. قد يبدو للوهلة الأولى إضافة دوال حفظ محلية لجميع الأنواع في هذا التسلسل الهرمي أمرًا مرغوبًا فيه. ولكن قد يكون من المفيد أيضًا حفظ الرسومات بتنسيقات ملفات أخرى. إضافة المزيد من الدوال لحفظ الرسومات بتنسيقات ملفات مختلفة قد يُعقّد بنية البيانات الهندسية الأصلية.
إحدى الطرق البسيطة لحل هذه المشكلة هي إنشاء دوال منفصلة لكل تنسيق ملف. تأخذ دالة الحفظ هذه رسمًا كمدخل، وتفحصه، ثم تشفره إلى تنسيق الملف المحدد. ومع تكرار هذه العملية لكل تنسيق جديد، يتراكم التكرار بين الدوال. على سبيل المثال، يتطلب حفظ شكل دائري بتنسيق نقطي شيفرة برمجية متشابهة جدًا بغض النظر عن تنسيق الصورة النقطية المستخدم، وهو يختلف عن حفظ الأشكال الهندسية الأخرى. وينطبق الأمر نفسه على الأشكال الهندسية الأخرى كالخطوط والمضلعات. وبالتالي، تصبح الشيفرة البرمجية عبارة عن حلقة خارجية كبيرة تفحص الكائنات، مع شجرة قرارات كبيرة داخل الحلقة تستعلم عن نوع الكائن. مشكلة أخرى في هذا النهج هي سهولة إغفال شكل ما في دالة حفظ واحدة أو أكثر، أو إضافة شكل هندسي جديد، ولكن يتم تنفيذ روتين الحفظ لنوع ملف واحد فقط دون غيره، مما يؤدي إلى مشاكل في توسيع الشيفرة البرمجية وصيانتها. ومع ازدياد عدد إصدارات الملف نفسه، تصبح صيانته أكثر تعقيدًا.
بدلاً من ذلك، يمكن تطبيق نمط الزائر. فهو يُشفّر العملية المنطقية (مثل save(image_tree)) على التسلسل الهرمي بأكمله في فئة واحدة (مثل Saver) تُنفّذ الطرق الشائعة لاجتياز الشجرة، وتصف طرقًا مساعدة افتراضية (مثل save_circle و save_square، إلخ) تُنفّذ لسلوكيات خاصة بالتنسيق. في حالة مثال التصميم بمساعدة الحاسوب (CAD)، تُنفّذ هذه السلوكيات الخاصة بالتنسيق بواسطة فئة فرعية من Visitor (مثل SaverPNG). وبذلك، تُزال جميع عمليات التحقق من النوع وخطوات الاجتياز المتكررة. بالإضافة إلى ذلك، يُصدر المُصرّف الآن تحذيرًا في حال حذف شكل ما، لأنه أصبح متوقعًا الآن بواسطة دالة الاجتياز/الحفظ الأساسية الشائعة.
حلقات التكرار
يمكن استخدام نمط الزائر للتكرار على هياكل البيانات الشبيهة بالحاويات ، تمامًا مثل نمط المُكرِّر ، ولكن بوظائف محدودة. [ 3 ] : 288 على سبيل المثال، يمكن تنفيذ التكرار على بنية دليل باستخدام فئة دالة بدلًا من نمط الحلقة التقليدي . وهذا يسمح باستخلاص معلومات مفيدة متنوعة من محتوى الأدلة من خلال تطبيق وظيفة زائر لكل عنصر مع إعادة استخدام كود التكرار. يُستخدم هذا النمط على نطاق واسع في أنظمة Smalltalk، ويمكن إيجاده أيضًا في لغة C++. [ 3 ] : 289 مع ذلك، من عيوب هذا النهج صعوبة الخروج من الحلقة أو التكرار المتزامن (أي اجتياز حاويتين في الوقت نفسه باستخدام iمتغير واحد). [ 3 ] : 289 يتطلب هذا الأخير كتابة وظائف إضافية للزائر لدعم هذه الميزات. [ 3 ] : 289
بناء
فئات UML ومخطط التسلسل

في مخطط فئات UML أعلاه، لا تُنفّذ الفئة عملية جديدة بشكل مباشر. بدلاً من ذلك، تُنفّذ عملية إرسال تُرسل (تُفوّض) طلبًا إلى كائن الزائر المقبول . تُنفّذ الفئة العملية . ثم تُنفّذ عملية أخرى عن طريق الإرسال إلى كائن الزائر المقبول . تُنفّذ الفئة العملية .ElementAElementAaccept(visitor)visitor.visitElementA(this)Visitor1visitElementA(e:ElementA)ElementBaccept(visitor)visitor.visitElementB(this)Visitor1visitElementB(e:ElementB)
يُظهر مخطط تسلسل UML التفاعلات أثناء التشغيل: يتنقل الكائن عبر عناصر بنية الكائن ( ) ويستدعي كل عنصر. أولًا، يستدعي العنصر ، الذي يستدعي بدوره الكائن المُستلم . ثم يُمرر العنصر نفسه ( ) إلى الكائن حتى يتمكن من "زيارته" (استدعاء ). بعد ذلك، يستدعي العنصر ، الذي يستدعي بدوره الكائن الذي "يزوره" (استدعاء ).ClientElementA,ElementBaccept(visitor)Clientaccept(visitor)ElementAvisitElementA(this)visitorthisvisitorElementAoperationA()Clientaccept(visitor)ElementBvisitElementB(this)visitorElementBoperationB()
مخطط الفئات


تفاصيل
يتطلب نمط الزائر لغة برمجة تدعم الإرسال الفردي ، كما هو الحال في لغات البرمجة الكائنية الشائعة (مثل C++ ، و Java ، وSmalltalk ، و Objective-C ، وSwift ، و JavaScript ، و Python ، و C# ). في هذه الحالة، لنفترض وجود كائنين، كل منهما من نوع فئة معينة؛ أحدهما يُسمى العنصر ، والآخر يُسمى الزائر .
أشياء
زائر
يُعرّف الزائر دالةً تأخذ visitالعنصر كوسيط، لكل فئة من فئات العناصر. تُشتق الزوار الملموسةvisit من فئة الزائر وتُنفّذ هذه الدوال، حيث تُنفّذ كل دالة جزءًا من الخوارزمية التي تعمل على بنية الكائن. ويتم الاحتفاظ بحالة الخوارزمية محليًا بواسطة فئة الزائر الملموسة.
عنصر
يُعلن العنصر عن دالة لاستقبال كائن زائر، تأخذ الكائن الزائر كوسيط. تُنفّذ العناصر الملموسة ، المشتقة من فئة العنصر، هذه الدالة. في أبسط صورها، لا تعدو هذه الدالة كونها استدعاءً لدالة الكائن الزائر. أما العناصر المركبة ، التي تحتفظ بقائمة من الكائنات الفرعية، فتُكرر عادةً هذه القائمة، مستدعيةً دالة كل كائن فرعي.acceptacceptvisitaccept
عميل
يقوم العميل بإنشاء بنية الكائن، بشكل مباشر أو غير مباشر، ويُنشئ الزوار الفعليين. عند تنفيذ عملية مُطبقة باستخدام نمط الزائر، فإنه يستدعي acceptدالة العنصر (أو العناصر) الرئيسية.
طُرق
يقبل
عند acceptاستدعاء الدالة في البرنامج، يتم اختيار تنفيذها بناءً على كلٍ من النوع الديناميكي للعنصر والنوع الثابت للزائر. وعند visitاستدعاء الدالة المرتبطة بها، يتم اختيار تنفيذها بناءً على كلٍ من النوع الديناميكي للزائر والنوع الثابت للعنصر، كما هو معروف من داخل تنفيذ الدالة accept، وهو نفس النوع الديناميكي للعنصر. (كميزة إضافية، إذا لم يتمكن الزائر من معالجة وسيط من نوع العنصر المُعطى، فسيكتشف المُصرّف الخطأ).
يزور
وبالتالي، يتم اختيار طريقة التنفيذ visitبناءً على كلٍ من النوع الديناميكي للعنصر والنوع الديناميكي للزائر. وهذا يُنفذ فعليًا الإرسال المزدوج . بالنسبة للغات التي تدعم أنظمة الكائنات فيها الإرسال المتعدد، وليس الإرسال الأحادي فقط، مثل Common Lisp أو C# عبر بيئة تشغيل اللغة الديناميكية (DLR)، يتم تبسيط تنفيذ نمط الزائر بشكل كبير (المعروف أيضًا باسم الزائر الديناميكي) من خلال السماح باستخدام تحميل الدوال البسيط لتغطية جميع الحالات التي تتم زيارتها. يتوافق الزائر الديناميكي، شريطة أن يعمل على البيانات العامة فقط، مع مبدأ الانفتاح/الإغلاق (لأنه لا يُعدّل الهياكل الموجودة) ومع مبدأ المسؤولية الواحدة (لأنه يُنفذ نمط الزائر في مكون منفصل).
وبهذه الطريقة، يمكن كتابة خوارزمية واحدة لاجتياز رسم بياني للعناصر، ويمكن تنفيذ العديد من أنواع العمليات المختلفة أثناء هذا الاجتياز من خلال توفير أنواع مختلفة من الزوار للتفاعل مع العناصر بناءً على الأنواع الديناميكية لكل من العناصر والزوار.
أمثلة
سي شارب
يُعرّف هذا المثال فئةً منفصلةً ExpressionPrintingVisitorتتولى عملية الطباعة. في حال الرغبة بإضافة زائر جديد، سيتم إنشاء فئة جديدة لتطبيق واجهة الزائر، وسيتم توفير تطبيقات جديدة لأساليب الزيارة. ستبقى الفئتان الحاليتان (Literal و Addition) دون تغيير.
namespace Wikipedia.Examples ;باستخدام النظام ؛واجهة IVisitor { void Visit ( Literal literal ); void Visit ( Addition addition ); }class ExpressionPrintingVisitor : IVisitor { public void Visit ( Literal literal ) { Console . WriteLine ( literal . Value ); }public void Visit ( Addition addition ) { double leftValue = addition . Left . GetValue (); double rightValue = addition . Right . GetValue (); double sum = addition . GetValue (); Console . WriteLine ( $"{leftValue} + {rightValue} = {sum}" ); } }فئة مجردة Expression { دالة عامة مجردة void Accept ( IVisitor visitor ); دالة عامة مجردة double GetValue (); }class Literal : Expression { public Literal ( double value ) { this . Value = value ; }public double Value { get ; set ; }public override void Accept ( IVisitor visitor ) { visitor . Visit ( this ); } public override double GetValue () { return Value ; } }class Addition : Expression { public Addition ( Expression left , Expression right ) { Left = left ; Right = right ; }public Expression Left { get ; set ; } public Expression Right { get ; set ; }public override void Accept ( IVisitor visitor ) { Left . Accept ( visitor ); Right . Accept ( visitor ); visitor . Visit ( this ); } public override double GetValue () { return Left . GetValue () + Right . GetValue (); } }public static class Program { public static void Main ( string [] args ) { // محاكاة عملية جمع 1 + 2 + 3 e = new ( new Addition ( new Literal ( 1 ), new Literal ( 2 ) ), new Literal ( 3 ) );ExpressionPrintingVisitor printingVisitor = new ( ); e.Accept ( printingVisitor ) ; Console.ReadKey ( ) ; } }أحاديث قصيرة
في هذه الحالة، تقع على عاتق الكائن مسؤولية معرفة كيفية طباعة نفسه على دفق البيانات. وبالتالي، فإن الزائر هنا هو الكائن، وليس دفق البيانات.
"لا توجد صيغة محددة لإنشاء فئة. يتم إنشاء الفئات عن طريق إرسال رسائل إلى فئات أخرى." فئة فرعية من WriteStream : #ExpressionPrinter أسماء متغيرات المثيل: '' أسماء متغيرات الفئة: '' الحزمة: 'Wikipedia' .ExpressionPrinter >>write: anObject "يُفوِّض الإجراء إلى الكائن. لا يشترط أن يكون الكائن من أي فئة خاصة؛ يكفي أن يكون قادرًا على فهم الرسالة #putOn:" anObject putOn: self . ^ anObject .فئة فرعية للكائن : #Expression instanceVariableNames: '' classVariableNames: '' package: 'Wikipedia' .فئة فرعية للتعبير : #Literal instanceVariableNames: 'value' classVariableNames: '' package: 'Wikipedia' .فئة حرفية >> مع: aValue "طريقة الفئة لإنشاء نسخة من الفئة الحرفية" ^ قيمة جديدة ذاتية : aValue ; نفسك .حرفي >> القيمة: aValue "مُعدِّل للقيمة" القيمة := aValue .Literal >>putOn: aStream "يعرف كائن Literal كيفية طباعة نفسه" aStream nextPutAll: value asString .فئة فرعية للتعبير : # أسماء متغيرات المثيل: 'يسار يمين' أسماء متغيرات الفئة: '' الحزمة: 'ويكيبيديا' .فئة الجمع >> اليسار: أ اليمين: ب "طريقة الفئة لإنشاء نسخة من فئة الجمع" ^ self جديد اليسار: أ ؛ اليمين: ب ؛ نفسك .إضافة >>اليسار: تعبير "مُعدِّل لليسار" اليسار := تعبير .إضافة >>يمين: تعبير "مُعدِّل لليمين" يمين := تعبير .إضافة >>putOn: aStream "يعرف كائن الإضافة كيفية طباعة نفسه" aStream nextPut: $( . left putOn: aStream . aStream nextPut: $+ . right putOn: aStream . aStream nextPut: $) .Object subclass: #Program instanceVariableNames: '' classVariableNames: '' package: 'Wikipedia' .البرنامج >> الرئيسي | دفق التعبيرات | التعبير := الجمع الأيسر: ( الجمع الأيسر: ( القيمة الحرفية مع: 1 ) الأيمن: ( القيمة الحرفية مع: 2 )) الأيمن: ( القيمة الحرفية مع: 3 ) . الدفق := ExpressionPrinter on: ( String new: 100 ) . كتابة الدفق : التعبير . عرض السجل : محتويات الدفق . مسح السجل .يذهب
لا تدعم لغة Go تحميل الدوال بشكل زائد، لذا تحتاج دوال الزيارة إلى أسماء مختلفة. قد تكون واجهة الزائر النموذجية كالتالي:
واجهة نوع الزائر { زيارة العجلة ( العجلة ) سلسلة نصية زيارة المحرك ( المحرك ) سلسلة نصية زيارة الهيكل ( الهيكل ) سلسلة نصية زيارة السيارة ( السيارة ) سلسلة نصية }جافا
يوضح المثال التالي، المكتوب بلغة جافا ، كيفية طباعة محتويات شجرة من العقد (التي تصف في هذه الحالة مكونات السيارة). بدلاً من إنشاء printدوال لكل فئة فرعية من العقد ( Wheel) Engine، ، Body، و Car)، تقوم فئة زائر واحدة ( CarElementPrintVisitor) بتنفيذ عملية الطباعة المطلوبة. ولأن الفئات الفرعية المختلفة للعقد تتطلب إجراءات مختلفة قليلاً للطباعة بشكل صحيح، CarElementPrintVisitorفإن تُوزّع الإجراءات بناءً على فئة الوسيط المُمرر إلى دالتها visit. CarElementDoVisitorوبالمثل، تقوم الدالة ، التي تُشابه عملية الحفظ لملف بتنسيق مختلف، بنفس الطريقة.
رسم بياني

مصادر
package org.wikipedia.examples ;استيراد java.util.List ;واجهة عنصر السيارة { void accept ( CarElementVisitor visitor ); }واجهة CarElementVisitor { void visit ( Body body ); void visit ( Car car ); void visit ( Engine engine ); void visit ( Wheel wheel ); }class Wheel implements CarElement { private final String name ;public Wheel ( final String name ) { this . name = name ; }public String getName () { return name ; }@Override public void accept ( CarElementVisitor visitor ) { /* * تُنفذ الدالة accept(CarElementVisitor) في Wheel * الدالة accept(CarElementVisitor) في CarElement، لذا فإن استدعاء * الدالة accept مرتبط بوقت التشغيل. يمكن اعتبار هذا * الإرسال *الأول*. ومع ذلك، يمكن اتخاذ قرار استدعاء * الدالة visit(Wheel) (بدلاً من visit(Engine) وما إلى ذلك) * أثناء وقت الترجمة لأن 'this' معروف في وقت الترجمة * بأنه Wheel. علاوة على ذلك، يُنفذ كل تطبيق من * CarElementVisitor الدالة visit(Wheel)، وهو * قرار آخر يتم اتخاذه في وقت التشغيل. يمكن اعتبار هذا * الإرسال *الثاني*. */ visitor . visit ( this ); } }class Body implements CarElement { @Override public void accept ( CarElementVisitor visitor ) { visitor . visit ( this ); } }class Engine implements CarElement { @Override public void accept ( CarElementVisitor visitor ) { visitor . visit ( this ); } }class Car implements CarElement { private final List <CarElement> elements ;public Car ( ) { this.elements = List.of ( new Wheel ( " front left" ), new Wheel ( "front right" ), new Wheel ( "back left" ), new Wheel ( " back right" ), new Body (), new Engine ( ) ); }@Override public void accept ( CarElementVisitor visitor ) { for ( CarElement element : elements ) { element . accept ( visitor ); } visitor . visit ( this ); } }class CarElementDoVisitor implements CarElementVisitor { @Override public void visit ( Body body ) { System . out . println ( "Moving my body" ); }@Override public void visit ( Car car ) { System . out . println ( "تشغيل سيارتي" ); }@Override public void visit ( Wheel wheel ) { System . out . printf ( "أركل عجلة %s الخاصة بي %n" , wheel . getName ()); }@Override public void visit ( Engine engine ) { System . out . println ( "بدء تشغيل المحرك" ); } }class CarElementPrintVisitor implements CarElementVisitor { @Override public void visit ( Body body ) { System . out . println ( "زيارة body" ); }@Override public void visit ( Car car ) { System . out . println ( "زيارة السيارة" ); }@Override public void visit ( Engine engine ) { System . out . println ( "زيارة المحرك" ); }@Override public void visit ( Wheel wheel ) { System . out . printf ( "زيارة العجلة %s %n" , wheel . getName ()); } }public class VisitorDemo { public static void main ( String [] args ) { Car car = new Car ();car.accept ( new CarElementPrintVisitor ( ) ); car.accept ( new CarElementDoVisitor ( ) ) ; } }الناتج
زيارة العجلة الأمامية اليسرى زيارة العجلة الأمامية اليمنى زيارة العجلة الخلفية اليسرى زيارة العجلة الخلفية اليمنى الهيئة الزائرة محرك الزيارة سيارة الزيارة ركلت العجلة الأمامية اليسرى ركلت العجلة الأمامية اليمنى ركلت العجلة الخلفية اليسرى ركلت العجلة الخلفية اليمنى تحريك جسدي تشغيل محركي تشغيل سيارتي
لغة الشفرة الشائعة
مصادر
( defclass auto () (( elements :initarg :elements )))( defclass auto-part () (( name :initarg :name :initform "<unnamed-car-part>" )))( defmethod print-object (( p auto-part ) stream ) ( print-object ( slot-value p 'name ) stream ))( defclass wheel ( auto-part ) ())( defclass body ( auto-part ) ())( defclass engine ( auto-part ) ())( defgeneric traverse ( function object other-object ))( defmethod traverse ( function ( a auto ) other-object ) ( with-slots ( elements ) a ( dolist ( e elements ) ( funcall function e other-object ))))زيارات لفعل شيء ما;; catch all ( defmethod do-something ( object other-object ) ( format t "لا أعرف كيف يجب أن تتفاعل ~s و ~s~%" object other-object ));; زيارة تتضمن عجلة وعدد صحيح ( defmethod do-something (( object wheel ) ( other-object integer )) ( format t "kicking wheel ~s ~s times~%" object other-object ));; زيارة تتضمن العجلة والرمز ( defmethod do-something (( object wheel ) ( other-object symbol )) ( format t "kicking wheel ~s symbolically using symbol ~s~%" object other-object ))( defmethod do-something (( object engine ) ( other-object integer )) ( format t "starting engine ~s ~s times~%" object other-object ))( defmethod do-something (( object engine ) ( other-object symbol )) ( format t "starting engine ~s symbolically using symbol ~s~%" object other-object ))( let (( a ( make-instance 'auto :elements ` ( , ( make-instance 'wheel :name "front-left-wheel" ) , ( make-instance 'wheel :name "front-right-wheel" ) , ( make-instance 'wheel :name "rear-left-wheel" ) , ( make-instance 'wheel :name "rear-right-wheel" ) , ( make-instance 'body :name "body" ) , ( make-instance 'engine :name "engine" ))))) ;; traverse to print elements ;; stream *standard-output* plays the role of other-object here ( traverse #' print a *standard-output* )( terpri ) ;; طباعة سطر جديد;; اجتياز سياق عشوائي من كائن آخر ( اجتياز #' فعل شيء ما a 42 );; اجتياز سياق عشوائي من كائن آخر ( اجتياز #' فعل شيء ما a 'abc ))الناتج
"العجلة الأمامية اليسرى" "العجلة الأمامية اليمنى" "العجلة الخلفية اليسرى" "العجلة الخلفية اليمنى" "جسم" "محرك" ركل العجلة الأمامية اليسرى 42 مرة ركل العجلة الأمامية اليمنى 42 مرة ركل العجلة الخلفية اليسرى 42 مرة ركل العجلة الخلفية اليمنى 42 مرة لا أعرف كيف يجب أن يتفاعل "الجسم" و42 تشغيل المحرك "engine" 42 مرة ركل العجلة "العجلة الأمامية اليسرى" بشكل رمزي باستخدام الرمز ABC ركل العجلة "العجلة الأمامية اليمنى" بشكل رمزي باستخدام الرمز ABC ركل العجلة "العجلة الخلفية اليسرى" بشكل رمزي باستخدام الرمز ABC ركل العجلة "العجلة الخلفية اليمنى" بشكل رمزي باستخدام الرمز ABC لا أعرف كيف ينبغي أن يتفاعل "الجسم" و ABC تشغيل المحرك "engine" رمزياً باستخدام الرمز ABC
ملحوظات
المعامل other-objectزائد في هذه الحالة traverse. والسبب هو أنه من الممكن استخدام دالة مجهولة تستدعي الطريقة المستهدفة المطلوبة باستخدام كائن تم التقاطه معجميًا:
( defmethod traverse ( function ( a auto )) ;; تم حذف الكائن الآخر ( with-slots ( elements ) a ( dolist ( e elements ) ( funcall function e )))) ;; من هنا أيضًا;; ...;; طريقة بديلة للطباعة والتنقل ( traverse ( lambda ( o ) ( print o *standard-output* )) a );; طريقة بديلة للقيام بشيء ما باستخدام ;; عناصر من a والعدد الصحيح 42 ( traverse ( lambda ( o ) ( do-something o 42 )) a )يحدث الآن التوزيع المتعدد في الاستدعاء الصادر من جسم الدالة المجهولة، وبالتالي traverseفهي مجرد دالة ربط تُوزّع تطبيق دالة على عناصر كائن. وهكذا تختفي جميع آثار نمط الزائر، باستثناء دالة الربط، التي لا يوجد فيها دليل على وجود كائنين. كل المعلومات المتعلقة بوجود كائنين والتوزيع على أنواعهما موجودة في دالة لامدا.
بايثون
لا يدعم بايثون التحميل الزائد للأساليب بالمعنى الكلاسيكي (السلوك متعدد الأشكال وفقًا لنوع المعلمات التي تم تمريرها)، لذلك يجب أن يكون لأساليب "الزيارة" لأنواع النماذج المختلفة أسماء مختلفة.
مصادر
""" مثال على نمط الزائر. """من abc استورد ABCMeta و abstractmethod ، ومن typing استورد NoReturnNOT_IMPLEMENTED : str = "يجب عليك تنفيذ هذا."class CarElement ( metaclass = ABCMeta ): @abstractmethod def accept ( self , visitor : CarElementVisitor ) -> NoReturn : raise NotImplementedError ( NOT_IMPLEMENTED )class Body ( CarElement ): def accept ( self , visitor : CarElementVisitor ) -> None : visitor . visit_body ( self )class Engine ( CarElement ): def accept ( self , visitor : CarElementVisitor ) -> None : visitor . visit_engine ( self )class Wheel ( CarElement ): def __init__ ( self , name : str ) -> None : self . name = namedef accept ( self , visitor : CarElementVisitor ) -> None : visitor . visit_wheel ( self )class Car ( CarElement ): def __init__ ( self ) -> None : self.elements : list [ CarElement ] = [ Wheel ( " front left " ), Wheel ( "front right" ), Wheel ( "back left" ), Wheel ( "back right" ), Body (), Engine () ]دالة accept ( self , visitor ) : for element in self.elements : element.accept ( visitor ) visitor.visit_car ( self )class CarElementVisitor ( metaclass = ABCMeta ): @abstractmethod def visit_body ( self , element : CarElement ) -> NoReturn : raise NotImplementedError ( NOT_IMPLEMENTED )@abstractmethod def visit_engine ( self , element : CarElement ) -> NoReturn : raise NotImplementedError ( NOT_IMPLEMENTED )@abstractmethod def visit_wheel ( self , element : CarElement ) -> NoReturn : raise NotImplementedError ( NOT_IMPLEMENTED )@abstractmethod def visit_car ( self , element : CarElement ) -> NoReturn : raise NotImplementedError ( NOT_IMPLEMENTED )class CarElementDoVisitor ( CarElementVisitor ): def visit_body ( self , body : Body ) -> None : print ( "تحريك جسمي." )def visit_car ( self , car : Car ) -> None : print ( “تشغيل سيارتي.” )def visit_wheel ( self , wheel : Wheel ) -> None : print ( f "ركل عجلة { wheel . name } الخاصة بي." )def visit_engine ( self , engine : Engine ) -> None : print ( “تشغيل المحرك.” )class CarElementPrintVisitor ( CarElementVisitor ): def visit_body ( self , body : Body ) -> None : print ( "زيارة body." )def visit_car ( self , car : Car ) -> None : print ( "زيارة السيارة." )def visit_wheel ( self , wheel : Wheel ) -> None : print ( f "زيارة العجلة { wheel . name } ." )def visit_engine ( self , engine : Engine ) -> None : print ( "زيارة المحرك." )إذا كان __name__ يساوي " __main__" : car : Car = Car ( ) car.accept ( CarElementPrintVisitor ( )) car.accept ( CarElementDoVisitor ( ))الناتج
أفحص العجلة الأمامية اليسرى. أفحص العجلة الأمامية اليمنى. أفحص العجلة الخلفية اليسرى. أفحص العجلة الخلفية اليمنى. أفحص الهيكل. أفحص المحرك. أفحص السيارة. أركل العجلة الأمامية اليسرى. أركل العجلة الأمامية اليمنى. أركل العجلة الخلفية اليسرى. أركل العجلة الخلفية اليمنى. أحرك جسدي. أشغل المحرك. أشغل السيارة.التجريد
يتيح استخدام بايثون 3 أو أعلى إمكانية إنشاء تطبيق عام لطريقة القبول:
class Visitable : def accept ( self , visitor : Visitor ) -> Any : lookup : str = f "visit_ { self . __qualname__ . replace ( "." , "_" ) } " return getattr ( visitor , lookup )( self )يمكن توسيع هذا ليشمل تكرار ترتيب تنفيذ أساليب الفئة إذا رغب المستخدم في الرجوع إلى الفئات المُنفذة مسبقًا. كما يمكن استخدام ميزة ربط الفئات الفرعية لتحديد عملية البحث مسبقًا.
أنماط التصميم ذات الصلة
- نمط المُكرِّر – يُعرِّف مبدأ اجتياز مثل نمط الزائر، دون إجراء تمييز نوعي داخل الكائنات التي يتم اجتيازها
- ترميز الكنيسة - مفهوم ذو صلة من البرمجة الوظيفية، حيث يمكن نمذجة أنواع الاتحاد/المجموع الموسومة باستخدام سلوكيات "الزوار" على هذه الأنواع، وهو ما يمكّن نمط الزائر من محاكاة المتغيرات والأنماط .
انظر أيضاً
مراجع
- 1 2 غاما، إريك ؛ هيلم، ريتشارد ؛ جونسون، رالف ؛ فليسيدس، جون (1994). أنماط التصميم: عناصر البرمجيات القابلة لإعادة الاستخدام والموجهة للكائنات . أديسون ويسلي. ص 331 وما بعدها . ISBN 0-201-63361-2.
- ↑ كوجان، كوري (16 يونيو 2009). "نمط الزائر: مثال من العالم الحقيقي" - عبر WordPress.com .
- 1 2 3 4 باد، تيموثي (1997). مقدمة في البرمجة الكائنية التوجه ( الطبعة الثانية). ريدينغ، ماساتشوستس: أديسون-ويسلي. ISBN 0-201-82419-1. OCLC 34788238 .
- ↑ "نمط تصميم الزائر - الهيكل والتعاون" . w3sDesign.com . تم الاطلاع عليه بتاريخ 12 أغسطس 2017 .
{{cite web}}: CS1 maint: url-status ( link ) - ↑ ريدي، مارتن (2011). تصميم واجهات برمجة التطبيقات للغة C++ . بوسطن: مورغان كوفمان. ISBN 978-0-12-385004-1. OCLC 704559821 .
روابط خارجية
- مجموعة أنماط تصميم الزائر على موقع Wayback Machine (مؤرشفة بتاريخ 22 أكتوبر 2015). أرشيفات إضافية: 12 أبريل 2004 ، 5 مارس 2002. فصل تمهيدي من كتاب "مبادئ وأنماط وممارسات تطوير البرمجيات الرشيقة" لروبرت سي. مارتن ، دار برنتيس هول للنشر .
- نمط الزائر في لغة النمذجة الموحدة (UML) وفي LePUS3 (لغة وصف التصميم)
- مقال " التجزئة: مثال الزائر" بقلم برتراند ماير وكارين أرنوت، مجلة الكمبيوتر (IEEE)، المجلد 39، العدد 7، يوليو 2006، الصفحات 23-30.
- مقال: إعادة بناء نمط الزائر باستخدام نظرية الأنواع
- مقال بعنوان " جوهر نمط الزائر " بقلم ينس بالسبيرغ وسي . باري جاي . ورقة بحثية نُشرت عام 1997 في مؤتمر IEEE-CS COMPSAC تُظهر أن طرق accept() غير ضرورية عندما يكون الانعكاس متاحًا؛ وتُقدم مصطلح "Walkabout" لهذه التقنية.
- مقال بعنوان " وقت للتأمل " بقلم بروس والاس - بعنوان فرعي "إمكانيات الانعكاس في جافا 1.2 تُزيل أساليب accept() المرهقة من نمط الزائر الخاص بك"
- نمط الزائر باستخدام الانعكاس (جافا).
- مشروع PerfectJPattern مفتوح المصدر ، يوفر تطبيقًا خاليًا من السياق وآمنًا من حيث النوع لنمط الزائر في جافا استنادًا إلى المندوبين.
- نمط تصميم الزائر
- أنماط تصميم البرمجيات
- مقارنات لغات البرمجة
