نمط المراقب

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

يُنشئ هذا النمط تبعية من نوع واحد إلى متعدد، حيث يمكن لعدة مراقبين الاستماع إلى موضوع واحد، ولكن عادةً ما يكون هذا الترابط متزامنًا ومباشرًا - إذ يستدعي الموضوع أساليب المراقبين عند حدوث تغييرات، مع إمكانية استخدام تطبيقات غير متزامنة باستخدام قوائم انتظار الأحداث. وعلى عكس نمط النشر والاشتراك ، لا يوجد وسيط؛ فالموضوع والمراقبون لديهم مراجع مباشرة لبعضهم البعض.

يُستخدم هذا النمط عادةً لتنفيذ أنظمة معالجة الأحداث في البرمجة الموجهة بالأحداث ، وخاصةً الأنظمة الداخلية مثل أدوات واجهة المستخدم الرسومية أو أطر عمل MVC. وهذا ما يجعله مناسبًا تمامًا لمعالجة البيانات التي تصل بشكل غير متوقع، مثل مدخلات المستخدم ، وطلبات HTTP ، وإشارات GPIO ، والتحديثات من قواعد البيانات الموزعة ، أو التغييرات في نموذج واجهة المستخدم الرسومية .

ملخص

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

يلبي نمط المراقب المتطلبات التالية: [ 2 ]

  • ينبغي تعريف علاقة التبعية من واحد إلى متعدد بين الكائنات دون جعل الكائنات مترابطة بشكل وثيق.
  • عندما يتغير وضع أحد الكائنات، يجب تحديث عدد غير محدد من الكائنات التابعة تلقائيًا.
  • يمكن للكائن الواحد إخطار عدة كائنات أخرى.

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

يوفر نمط المراقب بديلاً أكثر مرونة من خلال وضع بروتوكول إخطار قياسي:

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

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

العلاقة بين النشر والاشتراك

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

يلخص الجدول أدناه الاختلافات الرئيسية:

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

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

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

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

تاريخياً، في أنظمة التشغيل الرسومية المبكرة مثل OS/2 ومايكروسوفت ويندوز ، كان مصطلحا "النشر والاشتراك" و"البرمجة الموجهة بالأحداث" يستخدمان غالباً كمرادفين لنمط المراقب. [ 5 ]

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

تشمل الأنماط ذات الصلة الوسيط والنمط الفردي .

القيود والحلول

المراجع القوية مقابل المراجع الضعيفة

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

التقييد والفصل الزمني

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

في مثل هذه الحالات، يمكن تعديل نمط المراقب لفصل الإشعارات زمنيًا عن طريق إدخال آلية للتحكم في معدل الإرسال، مثل المؤقت. فبدلاً من التحديث عند كل تغيير في الحالة، يقوم المراقب باستطلاع الموضوع أو يتم إخطاره على فترات منتظمة، مما يوفر رؤية تقريبية ولكنها مستقرة للنموذج.

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

يسمح هذا الشكل من الفصل الزمني للمراقبين بالبقاء متجاوبين دون أن تغمرهم التحديثات عالية التردد، مع الاستمرار في عكس الاتجاه العام أو التقدم المحرز في حالة الموضوع.

بناء

فئات UML ومخطط التسلسل

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

في مخطط فئات UML هذا ، لا يقوم الصنف بتحديث حالة الكائنات التابعة له مباشرةً. بدلاً من ذلك، يشير إلى الواجهة ( ) لتحديث الحالة، مما يجعل الصنف مستقلاً عن كيفية تحديث حالة الكائنات التابعة له. تُنفذ الفئتان و الواجهة من خلال مزامنة حالتهما مع حالة الكائن الرئيسي.SubjectSubjectObserverupdate()SubjectObserver1Observer2Observer

يُظهر مخطط تسلسل UML التفاعلات أثناء التشغيل: تستدعي الكائنات و لتسجيل نفسها. بافتراض أن حالة تتغير، تستدعي نفسها. تستدعي الكائنات و المسجلة ، والتي بدورها تطلب البيانات المتغيرة من لتحديث (مزامنة) حالتها.Observer1Observer2attach(this)Subject1Subject1Subject1notify()notify()update()Observer1Observer2getState()Subject1

مخطط فئات UML

مخطط فئات UML لنمط المراقب

مثال

java.util.Observerعلى الرغم من وجود فئات المكتبة java.util.Observable، فقد تم إهمالها في Java 9 لأن النموذج المطبق كان محدودًا للغاية.

فيما يلي مثال مكتوب بلغة جافا يستقبل مدخلات لوحة المفاتيح ويتعامل مع كل سطر إدخال كحدث. عند إدخال سلسلة نصية System.in، يتم استدعاء الدالة notifyObservers()لإعلام جميع المراقبين بوقوع الحدث، وذلك عن طريق استدعاء دوال التحديث الخاصة بهم.

جافا

package org.wikipedia.examples ;استيراد java.util.ArrayList ؛ استيراد java.util.List ؛ استيراد java.util.Scanner ؛واجهة المراقب { void update ( String event ); } فئة مصدر الحدث { List < Observer > observers = new ArrayList <> (); public void notifyObservers ( String event ) { observers . forEach ( observer -> observer . update ( event )); } public void addObserver ( Observer observer ) { observers . add ( observer ); } public void scanSystemIn () { Scanner scanner = new Scanner ( System . in ); while ( scanner . hasNextLine ()) { String line = scanner . nextLine (); notifyObservers ( line ); } } }public class ObserverDemo { public static void main ( String [] args ) { System . out . println ( "أدخل النص: " ); EventSource eventSource = new EventSource (); eventSource . addObserver ( event -> System . out . printf ( "تم استلام الاستجابة: %s%n" , event ));eventSource.scanSystemIn ( ) ; } }

سي شارب

IObservableتوفر لغة C# الواجهات [ 7 ] و IObserver[ 8 ] بالإضافة إلى وثائق حول كيفية تطبيق نمط التصميم. [ 9 ]

namespace Wikipedia.Examples ;باستخدام System ؛ باستخدام System.Collections.Generic ؛class Payload { internal string Message { get ; init ; } }class Subject : IObservable < Payload > { private readonly List < IObserver < Payload >> _observers = new ();IDisposable IObservable <Payload> .Subscribe ( IObserver <Payload> observer ) { if ( ! _observers.Contains ( observer ) ) { _observers.Add ( observer ) ; }return new Unsubscriber ( observer , _observers ); }internal void SendMessage ( string message ) { foreach ( IObserver < Payload > observer in _observers ) { observer . OnNext ( new Payload { Message = message }); } } }فئة داخلية Unsubscriber : IDisposable { خاص للقراءة فقط IObserver < Payload > _observer ; خاص للقراءة فقط ICollection < IObserver < Payload >> _observers ;إلغاء الاشتراك الداخلي ( مراقب < حمولة > المراقب ، مجموعة < مراقب < حمولة >> المراقبون ) { _المراقب = المراقب ؛ _المراقبون = المراقبون ؛ }void IDisposable.Dispose ( ) { if ( _observer ! = null && _observers.Contains ( _observer ) ) { _observers.Remove ( _observer ) ; } } }فئة داخلية Observer : IObserver < Payload > { سلسلة خاصة _message ;public void OnCompleted () { }public void OnError ( Exception error ) { }public void OnNext ( Payload value ) { _message = value . Message ; }internal IDisposable Register ( IObservable < Payload > subject ) { return subject . Subscribe ( this ); } }

لغة سي++

هذا تطبيق بلغة C++23.

استيراد std ؛باستخدام std :: reference_wrapper ؛ باستخدام std :: vector ؛class Subject ; // تعريف مسبق للاستخدام في Observerclass Observer { private : // مرجع إلى كائن Subject لفصله في المُدمِّر Subject & subject ; public : explicit Observer ( Subject & subj ) : subject { subj } { subject . attach ( * this ); }virtual ~ Observer () { subject . detach ( * this ); } Observer ( const Observer & ) = delete ; Observer & operator = ( const Observer & ) = delete ;virtual void update ( Subject & s ) const = 0 ; };// الفئة Subject هي الفئة الأساسية لتوليد الأحداث class Subject { private : vector <refObserver> observers ; public : using RefObserver = reference_wrapper < const Observer > ; // إشعار جميع المراقبين المرفقين void notify () { for ( const Observer & x : observers ) { x.get ( ). update ( * this ) ; } } // إضافة مراقب void attach ( const Observer & observer ) { observers.push_back ( observer ); } // إزالة مراقب void detach ( Observer & observer ) { observers.remove_if ([ & observer ] ( const RefObserver & obj ) - > bool { return & obj.get ( ) == & observer ; } ) ; } } ;// مثال على الاستخدام class ConcreteObserver : public Observer { public : explicit ConcreteObserver ( Subject & subj ) : Observer ( subj ) {} // الحصول على إشعار void update ( Subject & ) const override { std :: println ( "تم الحصول على إشعار" ); } };int main ( int argc , char * argv []) { Subject cs ; ConcreteObserver co1 ( cs ); ConcreteObserver co2 ( cs ); cs . notify (); }

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

تلقيت إشعارًا

رائع

class EventSource { private observers = []private notifyObservers ( String event ) { observers . each { it ( event ) } }void addObserver ( observer ) { observers += observer }void scanSystemIn () { var scanner = new Scanner ( System . in ) while ( scanner ) { var line = scanner . nextLine () notifyObservers ( line ) } } }println 'أدخل النص: ' var eventSource = new EventSource ()eventSource.addObserver { event- > println "تم استلام الاستجابة: $ event " }eventSource.scanSystemIn ( )

كوتلين

استيراد java.util.Scannertypealias Observer = ( event : String ) -> Unit ;class EventSource { private var observers = mutableListOf < Observer > ()دالة خاصة تُعلم المراقبين ( حدث : سلسلة نصية ) { المراقبون . لكل { مراقب ( حدث ) } }fun addObserver ( observer : Observer ) { observers += observer }fun scanSystemIn ( ) { val scanner = Scanner ( System.`in` ) while ( scanner.hasNext ( ) ) { val line = scanner.nextLine ( ) notifyObservers ( line ) } } }
fun main ( arg : List < String > ) { println ( "أدخل النص: " ) val eventSource = EventSource ()eventSource.addObserver { event- > println ( " تم استلام الاستجابة : $ event " ) }eventSource.scanSystemIn ( ) }

دلفي

يستخدم System.Generics.Collections و System.SysUtils ؛نوع IObserver = واجهة [ '{0C8F4C5D-1898-4F24-91DA-63F1DD66A692}' ] إجراء التحديث ( const AValue : سلسلة ) ; نهاية ;نوع TObserverManager = فئة خاصة FObservers : TList <IObserver> ; مُنشئ عام Create ; تحميل زائد ; مُدمِّر Destroy ; تجاوز ; إجراء NotifyObservers ( const AValue : string ) ; إجراء AddObserver ( const AObserver : IObserver ) ; إجراء UnregisterObserver ( const AObserver : IObserver ) ; نهاية ;type TListener = class ( TInterfacedObject , IObserver ) private FName : string ; public constructor Create ( const AName : string ) ; reintroduce ; procedure Update ( const AValue : string ) ; end ;الإجراء TObserverManager.AddObserver ( const AObserver : IObserver ) ; ابدأ إذا لم يكن FObservers.Contains ( AObserver ) ثم FObservers.Add ( AObserver ) ; انتهى ؛ابدأ FreeAndNil ( FObservers ) ؛ موروث ؛ نهاية ؛الإجراء TObserverManager.NotifyObservers ( const AValue : string ) ; var i : Integer ; begin for i : = 0 to FObservers.Count - 1 do FObservers [ i ] .Update ( AValue ) ; end ;الإجراء TObserverManager.UnregisterObserver ( const AObserver : IObserver ) ; ابدأ إذا كان FObservers.Contains ( AObserver ) ثم FObservers.Remove ( AObserver ) ; انتهى ؛constructor TListener.Create ( const AName : string ) ; begin inherited Create ; FName : = AName ; end ;الإجراء TListener.Update ( const AValue : string ) ; ابدأ WriteLn ( FName + ' تم استلام إشعار من المستمع: ' + AValue ) ; انتهى ;الإجراء TMyForm.ObserverExampleButtonClick ( Sender : TObject ) ; var LDoorNotify : TObserverManager ; LListenerHusband : IObserver ; LListenerWife : IObserver ; begin LDoorNotify : = TObserverManager.Create ; try LListenerHusband : = TListener.Create ( ' Husband ' ) ; LDoorNotify.AddObserver ( LListenerHusband ) ; LListenerWife : = TListener.Create ( ' Wife ' ) ; LDoorNotify.AddObserver ( LListenerWife ) ; LDoorNotify.NotifyObservers ( ' شخص ما يطرق الباب ' ) ; finally FreeAndNil ( LDoorNotify ) ; end ;نهاية ؛

الناتج

تلقى الزوج المستمع إشعارًا: شخص ما يطرق الباب تلقت الزوجة المستمعة إشعارًا: شخص ما يطرق الباب 

بايثون

مثال مشابه في بايثون :

من typing استورد Anyclass Subject : _observers : list [ Observer ]def __init__ ( self ) -> None : self . _observers = []def register_observer ( self , observer : Observer ) - > None : self._observers.append ( observer )دالة notify_observers ( self , * args : tuple [ Any , ... ], ** kwargs : dict [ str , Any ]) -> None : for observer in self . _observers : observer . notify ( self , * args , ** kwargs )class Observer : def __init__ ( self , subject : Subject ) -> None : subject . register_observer ( self )دالة notify ( self , subject : Subject , * args : tuple [ Any , ... ], ** kwargs : dict [ str , Any ]) -> None : print ( f "تم استلام { args } , { kwargs } من { subject } " )الموضوع : الموضوع = الموضوع () المراقب : المراقب = المراقب ( الموضوع ) الموضوع.إشعار_المراقبين ( "اختبار" ، kw = " بايثون " )# يطبع: تم الحصول على ('test',) {'kw': 'python'} من <__main__.Observable object at 0x0000019757826FD0>

جافا سكريبت

تحتوي لغة جافا سكريبت على دالة مهملة Object.observeكانت تمثل تطبيقًا أكثر دقة لنمط المراقب. [ 10 ] كانت هذه الدالة تُطلق أحداثًا عند حدوث تغيير في الكائن المُراقَب. بدون Object.observeالدالة المهملة، يمكن تطبيق النمط باستخدام كود أكثر وضوحًا: [ 11 ]

let Subject = { _state : 0 , _observers : [], add : function ( observer ) { this . _observers . push ( observer ); }, getState : function () { return this . _state ; }, setState : function ( value ) { this . _state = value ; for ( let i = 0 ; i < this . _observers . length ; i ++ ) { this . _observers [ i ]. signal ( this ); } } };let Observer = { signal : function ( subject ) { let currentValue = subject . getState (); console . log ( currentValue ); } }Subject.add ( Observer ) ; Subject.setState ( 10 ) ; // الإخراج في console.log - 10

انظر أيضاً

مراجع

  1. 1 2 إريك غاما؛ ريتشارد هيلم؛ رالف جونسون؛ جون فليسيدس (1994). أنماط التصميم: عناصر البرمجيات القابلة لإعادة الاستخدام والموجهة للكائنات . أديسون ويسلي. ص 293 وما بعدها . ISBN  0-201-63361-2.
  2. "نمط تصميم المراقب" . www.geeksforgeeks.org .
  3. مقارنة بين تطبيقات أنماط المراقبة المختلفة — موشيه بيندلر، 2015 (GitHub)
  4. الاختلافات بين نمط النشر/الاشتراك ونمط المراقب نمط المراقب بقلم عدي عثماني (كتب سفاري على الإنترنت)
  5. تجربة برمجة ويندوز ، تشارلز بيتزولد ، 10 نوفمبر 1992، مجلة الكمبيوتر الشخصي ( كتب جوجل )
  6. "نمط تصميم المراقب - البنية والتعاون" . w3sDesign.com . تم الاطلاع عليه بتاريخ 12 أغسطس 2017 .
  7. "واجهة IObservable (النظام)" . learn.microsoft.com . تم الاطلاع عليه بتاريخ 9 نوفمبر 2024 .
  8. "واجهة مراقب الإدخال/الإخراج (النظام)" . learn.microsoft.com . تم الاطلاع عليه بتاريخ 9 نوفمبر 2024 .
  9. "نمط تصميم المراقب - .NET" . learn.microsoft.com . 25 مايو 2023. تم الاطلاع عليه في 9 نوفمبر 2024 .
  10. "jQuery - الاستماع لتغييرات المتغيرات في جافا سكريبت" .
  11. "Jquery - الاستماع لتغييرات المتغيرات في JavaScript" .