نمط التفويض

في هندسة البرمجيات ، يُعد نمط التفويض نمط تصميم موجه للكائنات يسمح بتكوين الكائنات لتحقيق نفس إعادة استخدام التعليمات البرمجية مثل الوراثة .

في التفويض، يُعالج كائنٌ طلبًا بتفويضه إلى كائنٍ ثانٍ ( المفوَّض ). المفوَّض هو كائنٌ مساعد ، لكنه يحتفظ بالسياق الأصلي . مع دعم لغة البرمجة للتفويض، يتم ذلك ضمنيًا من خلال selfإشارة المفوَّض إلى الكائن الأصلي (المُرسِل)، وليس إلى المفوَّض (الكائن المُستقبِل). في نمط المفوَّض، يتم ذلك بتمرير الكائن الأصلي صراحةً إلى المفوَّض، كوسيطٍ لدالة. [ 1 ] غالبًا ما يُستخدم مصطلح "التفويض" بشكلٍ فضفاض للإشارة إلى مفهوم إعادة التوجيه ، حيث يستخدم الكائن المُرسِل العضو المُناسب في الكائن المُستقبِل، مُقيَّمًا في سياق الكائن المُستقبِل ، وليس الكائن الأصلي.

تستخدم هذه المقالة مصطلح "كائن الإرسال/كائن الاستقبال" للكائنين، بدلاً من "كائن الاستقبال/المفوض"، مما يؤكد على الكائنات التي ترسل وتستقبل مكالمة التفويض، وليس المكالمة الأصلية.

تعريف

في مقدمة كتاب جاما وآخرون عام 1994، تم تعريف التفويض على النحو التالي:

يُعدّ التفويض وسيلةً لجعل التركيب بنفس قوة الوراثة في إعادة الاستخدام [Lie86, JZ91]. في التفويض، يتشارك كائنان في معالجة الطلب: كائن مُستقبِل يُفوِّض العمليات إلى مُفوَّضه . يُشابه هذا قيام الفئات الفرعية بتأجيل الطلبات إلى الفئات الأصلية. ولكن مع الوراثة، يُمكن للعملية الموروثة دائمًا الإشارة إلى الكائن المُستقبِل من خلال thisمتغير العضو في لغة C++ ولغة selfSmalltalk. لتحقيق التأثير نفسه مع التفويض، يُمرِّر الكائن المُستقبِل نفسه إلى المُفوَّض للسماح للعملية المُفوَّضة بالإشارة إلى الكائن المُستقبِل. [ 2 ]

مثال

في المثال أدناه (باستخدام لغة البرمجة Kotlin )، يقوم الصنف Window بتفويض الاستدعاء area()إلى كائن Rectangle الداخلي الخاص به (المفوض الخاص به).

class Rectangle ( val width : Int , val height : Int ) { fun area () = width * height }class Window ( val bounds : Rectangle ) { // دالة التفويض area ( ) = bounds . area () }

الدعم اللغوي

تتضمن بعض اللغات دعمًا خاصًا للتفويض المدمج. على سبيل المثال، في لغة البرمجة Kotlin،by تقوم الكلمة المفتاحية [ 3 ] بالتفويض إلى واجهة كائن آخر:

واجهة ClosedShape { دالة area (): عدد صحيح }class Rectangle ( val width : Int , val height : Int ) : ClosedShape { override fun area () = width * height }// يُفوّض تطبيق ClosedShape الخاص بـ Window إلى تطبيق Rectangle الذي يُحدّد حدوده. class Window ( private val bounds : Rectangle ) : ClosedShape by bounds

ومن الأمثلة العملية الأخرى ما يلي:

واجهة المستودع { دالة حفظ ( البيانات : سلسلة نصية ) دالة تحميل ( المعرف : سلسلة نصية ): سلسلة نصية دالة حذف ( المعرف : سلسلة نصية ) }class FileRepository : Repository { override fun save ( data : String ) = println ( "saving $ data " ) override fun load ( id : String ): String = "data: $ id " override fun delete ( id : String ) = println ( "deleting $ id " ) }class LoggingRepository ( private val delegate : Repository ) : Repository by delegate { override fun save ( data : String ) { println ( "على وشك الحفظ" ) delegate . save ( data ) } }

في هذا المثال، تُطبّق LoggingRepository نفس واجهة Repository، مع تغليفها لنسخة أخرى من Repository. تُخصّص دالة save لإضافة التسجيل، بينما تُمرّر الدوال المتبقية تلقائيًا إلى كائن المندوب.

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

واجهة المستودع { دالة حفظ ( البيانات : سلسلة نصية ) دالة تحميل ( المعرف : سلسلة نصية ): سلسلة نصية دالة حذف ( المعرف : سلسلة نصية ) }افتح فئة FileRepository : Repository { تجاوز الدالة save ( data : String ) = println ( "حفظ $ data " ) تجاوز الدالة load ( id : String ): String = "data: $ id " تجاوز الدالة delete ( id : String ) = println ( "حذف $ id " ) }class LoggingRepository : FileRepository () { override fun save ( data : String ) { println ( "على وشك الحفظ" ) super . save ( data ) } }

يربط هذا النهج LoggingRepository بـ FileRepository، في حين أن تفويض الواجهة يسمح باستخدام نفس الغلاف مع أي تطبيق `Repository`.

في لغة Kotlin، يتم تنفيذ تفويض الواجهة باستخدام by بواسطة المُصرّف من خلال طرق التوجيه المُولّدة. على سبيل المثال، فئة Window السابقة تُعادل شيفرة مثل:

واجهة ClosedShape { دالة area (): عدد صحيح }class Rectangle ( val width : Int , val height : Int ) : ClosedShape { override fun area () = width * height }// يُفوّض تطبيق ClosedShape الخاص بـ Window إلى تطبيق Rectangle الذي يُحدد حدوده. class Window ( private val bounds : Rectangle ) : ClosedShape { override fun area () = bounds . area () }

عند دمج تفويض الواجهة مع التجاوز اليدوي، يتم تمرير الأعضاء الذين لم يتم تجاوزهم صراحةً فقط تلقائيًا. على سبيل المثال:

واجهة Greeter { دالة greet () دالة greetTwice () }class DefaultGreeter : Greeter { override fun greet () { println ( "hello" ) }override fun greetTwice () { greet () greet () } }class LoudGreeter ( private val delegate : Greeter ) : Greeter by delegate { override fun greet () { println ( "HELLO" ) } }fun main () { val g = LoudGreeter ( DefaultGreeter ())g.greet ( ) g.greetTwice ( ) }

سيقوم هذا البرنامج بالطباعة

مرحبًا مرحبًا مرحبًا 

تُظهر النتيجة أن دالة greet تُعالج بواسطة التجاوز الصريح في LoudGreeter، بينما تُمرر دالة greetTwice إلى DefaultGreeter. داخل DefaultGreeter.greetTwice، يظل المُستقبِل هو كائن التفويض نفسه، لذا فإن الاستدعاءات الداخلية لدالة greet() تُحل إلى DefaultGreeter.greet() بدلاً من LoudGreeter.greet().

لهذا السبب، غالبًا ما توصف ميزة "by" في لغة Kotlin بأنها تفويض، ولكنها من الناحية العملية أقرب إلى التوجيه الذي يولده المترجم منها إلى أشكال أقوى من التفويض التي يتم فيها الحفاظ على سياق المتلقي الأصلي. [ 4 ]

انظر أيضاً

مراجع

  1. جاما وآخرون 1994
  2. غاما، إريك؛ هيلم، ريتشارد؛ جونسون، رالف؛ فليسيدس، جون (1995). أنماط التصميم  : عناصر البرمجيات القابلة لإعادة الاستخدام والموجهة للكائنات (الطبعة الرابعة عشرة  المطبوعة). ريدينغ، ماساتشوستس: أديسون-ويسلي. ص 20. ISBN  0-201-63361-2.
  3. "التفويض - لغة برمجة كوتلن" . كوتلن . تم الاسترجاع في 23-03-2019 .
  4. https://en.wikipedia.org/wiki/Delegation_(object-oriented_programming)