الفرق بين المراجعتين لصفحة: «Design Patterns/observer»

من موسوعة حسوب
1.0: عنوان الصفحة
 
2.0 محتوى
سطر 1: سطر 1:
<noinclude>{{DISPLAYTITLE:نمط المراقِب (Observer)}}</noinclude>
<noinclude>{{DISPLAYTITLE:نمط المراقِب (Observer)}}</noinclude>
نمط المراقب هو نمط تصميم سلوكي يسمح لك بتحديد آلية اشتراك لتنبيه عدة كائنات بأي أحداث تقع للكائن الذي يراقبونه.
== المشكلة ==
تخيل أن لديك نوعين من الكائنات: العميل<code>customer</code> والمتجر <code>store</code>، يهتم العميل بماركة بعينها من منتج ما (لنقل أنه إصدار جديد من آيفون)، وسيتاح هذا المنتج في المتجر قريبًا. بإمكان العميل أن يذهب كل يوم إلى المتجر ليرى إن كان الهاتف قد وصل أم لا، لكن أغلب تلك الزيارات ستكون هدرًا في الغالب إن كان الهاتف لم يأت، أليس كذلك؟ (انظر ش.1)
ضع الصورة. (ش.1) زيارة المتجر في مقابل إرسال السبام.
بينما على الناحية الأخرى، فإن المتجر يستطيع أن يرسل مئات الرسائل (التي ستقع في الغالب في مجلدات السبام) إلى كل العملاء في كل مرة يتاح فيها منتج ما، هذا قد يوفر على بعض المستخدمين زيارات مهدرة للمتجر، لكن في نفس الوقت ستزعج بقية العملاء الذين لا يرغبون في المنتجات الجديدة.
ونحن الآن أمام معضلة هنا، فإما أن يهدر العميل وقته في تفقد المنتجات الجديدة ذهابًا بنفسه إلى المتجر، وإما أن يهدر المتجر موارده في تنبيه العملاء الذين قد لا يرغبون في تلك المنتجات الجديدة.
== الحل ==
يسمى الكائن الذي عليه الاهتمام هنا بالهدف (subject)، لكن بما أنه سينبه كائنات أخرى بالتغييرات التي ستحدث لحالته، فسنسميه الناشر (publisher)، ويسمى الكائن الذي يرغب في تتبع التغير الحاصل في حالة الناشر بالمشترك (subscriber).
ويقترح نمط المراقب أن تضيف آلية اشتراك إلى فئة الناشر كي تستطيع الكائنات المنفردة الاشتراك وإلغاء الاشتراك من سيل الأحداث القادم من ذلك الناشر. وتتكون تلك الآلية من حقل مصفوفة لتخزين قائمة من المراجع إلى كائنات المشتركين، وبضعة أساليب عامة تسمح بإضافة المشتركين إلى القائمة وإزالتهم منها كذلك. (انظر ش.2)، والآن، كلما وقع حدث مهم للناشر فإنه يذهب إلى المشتركين ويستدعي أسلوب تنبيه محدد لكائناتهم.
ضع الصورة (ش.2) تسمح آلية الاشتراك للكائنات المنفردة بالاشتراك في إشعارات حدث ما.
قد تحتوي التطبيقات الحقيقية على عشرات الفئات من المشتركين المهتمين بتتبع أحداث لنفس فئة الناشر، لكن ليس من المناسب ربط الناشر إلى هذه الفئات كلها. إضافة إلى أنك قد لا تعرف بشأن بعضها مسبقًا إن كانت فئة الناشر يستخدمها أشخاص آخرون.
لهذا من الضروري أن يستخدم كل المشتركين نفس الواجهة، ويجب أن تصرح تلك الواجهة عن أسلوب تنبيه إضافة إلى مجموعة عوامل يمكن للناشر أن يستخدمها لتمرير بعض البيانات السياقية مع الإشعار أو التنبيه. (انظر ش.3)
ضع الصورة. (ش.3) ينبه الناشر المشتركين باستدعاء أسلوب تنبيه محدد إلى كائناتهم.
وإن كان تطبيقك به عدة أنواع من الناشرين وتريد أن تجعل المشتركين يتوافقون معهم جميعًا، فيمكنك جعل كل الناشرين يتبعون نفس الواجهة، ولا تحتاج هذه الواجهة إلا إلى وصف أساليب اشتراك معدودة. وستسمح الواجهة للمشتركين بمراقبة حالة الناشرين دون ربطهم بفئاتهم الحقيقية.
== مثال واقعي ==
ضع الصورة (ش.4) اشتراكات المجلات والصحف.
إن اشتركت في صحيفة أو مجلة فلست في حاجة إلى الذهاب إلى متجر لترى إن كان العدد الجديد قد صدر أم لا، بل يرسل لك الناشر الأعداد الجديدة مباشرة إلى بريدك بعد النشر أو ربما حتى بشكل مسبق. ويحافظ الناشر على قائمة من المشتركين ويعرف أي المجلات يهتمون بها، ويستطيع المشتركون إلغاء الاشتراك من تلك القائمة في أي وقت.
== البنية ==
ضع الصورة
# يرسل الناشر (publisher) الأحداث المهمة للكائنات الأخرى، وتقع هذه الأحداث حين يغير الناشر حالته أو ينفذ بعض الأساليب، وتحتوي الكائنات الناشرة على بنية اشتراك تحتية تسمح للمشتركين الجديد بالانضمام، وللمشتركين الحاليين بترك القائمة.
# عند وقوع حدث جديد فإن الناشر يذهب لقائمة الاشتراك ويستدعي ألوب التنبيه المصرح عنه في واجهة المشترك الموجودة في كل كائنِ مشترِك.
# تصرح واجهة المشترك (subscriber) عن واجهة التنبيه، وفي الغالب تتكون تلك الواجهة من أسلوب <code>update</code> واحد، وقد يحتوي الأسلوب على عدة عوامل تسمح للناشر بتمرير بعض تفاصيل الأحداث مع التحديث.
# ينفذ المشتركون الحقيقيون (Concrete Subscribers) بعض الإجراءات كرد على التنبيهات التي أرسلها الناشر، ويجب أن تستخدم كل تلك الفئات نفس الواجهة كي لا يُربط الناشر إلى الفئات الحقيقية.
# عادة ما يحتاج المشتركون إلى بعض المعلومات السياقية لمعالجة التحديث بشكل سليم، ولهذا فإن الناشرون عادة ما يمررون بعض البيانات السياقية كوسائط (arguments) لأسلوب التنبيه، ويستطيع الناشر أن يمرر نفسه كوسيط (argument)، سامحًا للمشترك بجلب أي بيانات مطلوبة مباشرة.
# ينشئ العميلُ (Client) الناشرَ وكائنات المشترِك بشكل منفصل ثم يسجل المشتركين في تحديثات الناشر.
== مثال توضيحي ==
يتيح نمط المراقب في هذا المثال لكائن المحرر النصي أن ينبه كائنات الخدمة الأخرى بالتغييرات في حالته. (انظر ش.6)
(ش.6) تنبيه الكائنات بالأحداث التي تقع للكائنات الأخرى.
تُجمَّع قائمة المشتركين ديناميكيًا: تستطيع الكائنات بدء أو إيقاف الاستماع إلى التنبيهات أثناء وقت التشغيل، بناءً على السلوك المرغوب فيه لتطبيقك.
وفي هذا الاستخدام فإن فئة المحرر لا تحتفظ بقائمة الاشتراك بنفسها، بل تفوض تلك المهمة إلى كائن مساعدة خاص مختص بتلك الوظيفة حصرًا، ويمكنك ترقية ذلك الكائن ليتصرف كمرسِل أحداث مركزي، سامحًا لأي كائن أن يتصرف كناشر. أيضًا، لا تتطلب إضافة مشتركين جدد إلى البرنامج أي تغييرات في فئات الناشر الحالية، طالما أنها تعمل مع كل المشتركين من خلال نفس الواجهة.<syntaxhighlight lang="java">
// تحتوي فئة الناشر الأساسية على شيفرة إدارة الاشتراك وأساليب التنبيه.
class EventManager is
    private field listeners: hash map of event types and listeners
    method subscribe(eventType, listener) is
        listeners.add(eventType, listener)
    method unsubscribe(eventType, listener) is
        listeners.remove(eventType, listener)
    method notify(eventType, data) is
        foreach (listener in listeners.of(eventType)) do
            listener.update(data)
// يحتوي الناشر الحقيقي على منطق عمل واقعي مهم لبعض المشتركين
// نستطيع استخلاص هذه الفئة من الناشر الأساسي لكن هذا ليس ممكنًا دومًا
// في الحياة الواقعية إذ أن الناشر الحقيقي قد يكون فئة فرعية أصلًا.
// وفي تلك الحالة فيمكن تصحيح منطق الاشتراك بالتركيب، كما فعلنا هنا.
class Editor is
    public field events: EventManager
    private field file: File
    constructor Editor() is
        events = new EventManager()
    // تستطيع أساليب منطق العمل أن تنبه المشتركين بالتغييرات.
    method openFile(path) is
        this.file = new File(path)
        events.notify("open", file.name)
    method saveFile() is
        file.write()
        events.notify("save", file.name)
    // ...
// هذه واجهة المشترك، إن كانت لغة البرمجة التي تستخدمها تدعم
// الأنواع الوظيفية فيمكنك استبدال هرمية المشترك كلها بمجموعة وظائف.
interface EventListener is
    method update(filename)
// يتفاعل المشتركون الحقيقيون مع التحديثات المرسلة من قبل الناشر المتصلين به.
class LoggingListener implements EventListener is
    private field log: File
    private field message
    constructor LoggingListener(log_filename, message) is
        this.log = new File(log_filename)
        this.message = message
    method update(filename) is
        log.write(replace('%s',filename,message))
class EmailAlertsListener implements EventListener is
    private field email: string
    constructor EmailAlertsListener(email, message) is
        this.email = email
        this.message = message
    method update(filename) is
        system.email(email, replace('%s',filename,message))
// يستطيع التطبيق أن يهيئ الناشرين والمشتركين أثناء وقت التشغيل.
class Application is
    method config() is
        editor = new TextEditor()
        logger = new LoggingListener(
            "/path/to/log.txt",
            "Someone has opened the file: %s");
        editor.events.subscribe("open", logger)
        emailAlerts = new EmailAlertsListener(
            "admin@example.com",
            "Someone has changed the file: %s")
        editor.events.subscribe("save", emailAlerts)
</syntaxhighlight>
== قابلية التطبيق ==
* '''استخدم نمط المراقب حين تتطلب التغييرات على حالة كائن واحد تغيير كائنات أخرى، وتكون مجموعة الكائنات الفعلية غير معروفة مسبقًا أو تتغير بديناميكية.'''
تستطيع رؤية هذه المشكلة عند العمل مع فئات واجهة المستخدم الرسومية، فمثلًا لنقل أنك أنشات فئات أزرار خاصة وتريد السماح للعملاء بربط بعض الشيفرات المخصصة لأزرارك كي تُنفَّذ إن ضغط المستخدم على زر ما.
هنا يسمح نمط المراقب لأي كائن يستخدم واجهة المشترك أن يشترك في تنبيهات الأحداث في كائنات الناشر، وتستطيع إضافة آلية الاشتراك إلى أزرارك سامحًا للعملاء بربط شيفراتهم المخصصة من خلال فئات مشترك مخصصة أيضًا.
* '''استخدم النمط عند حاجة بعض الكائنات في برنامج إلى مراقبة كائنات أخرى لفترة محدودة أو في حالات محددة.'''
قائمة الاشتراك مرنة، لذا فإن المشتركين يستطيعون الانضمام وإلغاء الاشتراك كذلك متى شاؤوا.
== كيفية الاستخدام ==
# ألق نظرة على منطق العمل لديك وحاول تقسيمه إلى جزئين، أولهما سيكون الوظيفية الأساسية (Core Functionality) الذي سيكون مستقلًا عن بقية الشيفرة والذي سيتصرف على أنه الناشر، أما الباقي فسيتحول إلى مجموعة من فئات المشترِك.
# صرّح عن واجهة المشترك، يجب أن تصرح عن أسلوب <code>update</code> واحد على الأقل.
# صرح عن واجهة الناشر وَصِف زوجًا من الأساليب لإضافة كائن المشترك وإزالته من القائمة، تذكر أن الناشرين يجب أن يعملوا مع المشتركين من خلال واجهة المشترِك حصرًا.
# حدد أين ستضع قائمة الاشتراك الفعلية وتطبيق أساليب الاشتراك (Implementation of subscribtion methods)، عادة ما تبدو تلك الشيفرة متطابقة لكل أنواع الناشرين، لذا يكون المكان البديهي لها هو فئة مجردة (abstract) مشتقة مباشرة من واجهة الناشر. يوسِّع الناشرون الحقيقيون (Concrete Publishers) هذه الفئة مكتسبين سلوك الاشتراك. لكن، إن كنت تطبق النمط على هرمية فئوية موجودة مسبقًا فجرب منظورًا مبنيًا على التركيب (Composition)، بأن تضع منطق الاشتراك في كائن منفصل، وتجعل كل الناشرين يستخدمونه.
# أنشئ فئات ناشر حقيقية. يجب أن تنبه هذه الفئاتُ كل المشتركين عند حدوث شيء مهم لأحد الناشرين.
# استخدم أساليب تنبيه التحديث (update notification methods) في فئات المشترك الحقيقية، سيحتاج أغلب المشتركين بعض البيانات السياقية عن الحدث، ويمكن تمريرها كوسيط (argument) لأسلوب التنبيه. يوجد خيار آخر يقترح أن يجلب المشترك أي بيانات مباشرة من التنبيه عند استلام ذلك التنبيه، وفي تلك الحالة يجب أن يمرر الناشر نفسه من خلال أسلوب التحديث. كذلك يوجد خيار ثالث أقل مرونة، وهو أن يُربط الناشر بالمشترك مباشرة من خلال منشئ (Constructor).
# يجب أن ينشئ العميل كل المشتركين ويسجلهم مع الناشرين المناسبين لهم.
== المزايا والعيوب ==
=== المزايا ===
* مبدأ المفتوح/المغلق. يمكنك إدخال فئات مشترك جديدة دون الحاجة إلى تغيير شيفرة الناشر (والعكس صحيح إن وُجدت واجهة للناشر).
* تستطيع إنشاء علاقات بين الكائنات أثناء وقت التشغيل.
=== العيوب ===
* يُنبه المشتركون عشوائيًا.
== العلاقات مع الأنماط الأخرى ==
* تقدم أنماط <nowiki/>[[Design Patterns/chain of responsibility|سلسلة المسؤولية]] و<nowiki/>[[Design Patterns/command|الأمر]] و[null الوسيط] و<nowiki/>[[Design Patterns/observer|المراقب]] طرقًا مختلفة لتوصيل مرسِلات الطلبات ومستقبلاتها ببعضها، وذلك على النحو التالي:
# تمرر سلسلة المسؤولية الطلب بشكل تسلسلي مع سلسلة ديناميكية من المستقبلات المحتملة إلى أن يعالجها أحد المستقبلات.
# ينشئ الأمر اتصالات أحادية الاتجاه بين المرسِلات والمستقبِلات.
# يزيل الوسيط الاتصالات المباشرة بين المرسِلات والمستقبلات مجبرًا إياها على التواصل بشكل غير مباشر من خلال كائن وسيط.
# يسمح المراقب للمستقبلات بالاشتراك في استقبال الطلبات وكذلك إلغاء الاشتراك بمرونة.
* عادة ما يكون الفرق بين نمطي الوسيط و<nowiki/>[[Design Patterns/observer|المراقب]] محيرًا، ففي أغلب الحالات تستطيع استخدام نمط واحد فحسب منهما، لكن في ظروف تشغيل أخرى قد تستطيع استخدام كليهما معًا، وذلك على النحو التالي:
الهدف الأساسي من الوسيط هو إزالة الاعتماديات المتبادلة (mutual dependencies) بين مجموعة من عناصر النظام، وتصبح تلك العناصر معتمدة على كائن وسيط واحد. أما الهدف من المراقب هو إنشاء اتصالات مرنة أحادية الاتجاه بين الكائنات، حيث تتصرف بعض الكائنات كتوابع لغيرها.
ولدينا استخدام شائع لنمط الوسيط يعتمد فيه على نمط المراقب، يلعب فيه كائن الوسيط دور الناشر (publisher)، ويتصرف كل عنصر كمشترك يستطيع الاشتراك في أحداث الوسيط (mediator events) وكذلك إلغاء الاشتراك منها، وتطبيق نمط الوسيط بهذا الشكل يجعله قريب الشبه جدًا من نمط المراقب. تذكر أنك تستطيع استخدام نمط الوسيط بطرق أخرى، فيمكنك مثلًا ربط كل العناصر إلى نفس الكائن الوسيط، ورغم أن هذا الاستخدام لا يشبه نمط المراقب لكن لا يزال صورة من نمط الوسيط.
وتخيل الآن برنامجًا تلعب فيه كل العناصر دور الناشر، سامحة باتصالات مرنة بين بعضها البعض، عندئذ لن يكون هناك كائن وسيط مركزي، بل مجموعة موزعة من المراقبين.
== الاستخدام في لغة جافا ==
المستوى: ★ ★ ☆
الانتشار:  ★ ★ ★
أمثلة الاستخدام: يكثر استخدام نمط المراقب في لغة جافا، خاصة في عناصر الواجهة الرسومية، إذ يوفر طريقة للتفاعل مع الأحداث الواقعة في كائنات أخرى دون الحاجة للربط بفئاتها. إليك بعض الأمثلة من مكتبات جافا:
* [http://docs.oracle.com/javase/8/docs/api/java/util/Observer.html java.util.Observer]/[http://docs.oracle.com/javase/8/docs/api/java/util/Observable.html java.util.Observable]، نادرًا ما تستخدم في الحياة الواقعية.
* جميع تطبيقات [http://docs.oracle.com/javase/8/docs/api/java/util/EventListener.html java.util.EventListener] (عمليًا، موجودة في كل مكونات Swing).
* [http://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpSessionBindingListener.html javax.servlet.http.HttpSessionBindingListener]
* [http://docs.oracle.com/javaee/7/api/javax/servlet/http/HttpSessionAttributeListener.html javax.servlet.http.HttpSessionAttributeListener]
* [http://docs.oracle.com/javaee/7/api/javax/faces/event/PhaseListener.html javax.faces.event.PhaseListener]
يمكن ملاحظة النمط من خلال أساليب الاشتراك التي تخزن الكائنات في قائمة وعبر الاستدعاءات إلى أسلوب التحديث المرسَل إلى الكائنات داخل تلك القائمة.
=== الاشتراك في الأحداث ===
في هذا المثال ينشئ نمط المراقب تعاونًا غير مباشر بين كائنات محرر نصي، ففي كل مرة يتغير كائن <code>Editor</code>، فإنه ينبه المشتركين به، ويتفاعل كل من <code>EmailNotificationListener</code> و <code>LogOpenListener</code> مع هذه التنبيهات بتنفيذ سلوكياتهم الرئيسية (primary behaviours).
ولا تُربط فئات المشترك بفئة المحرر، ويمكن إعادة استخدامها في برامج أخرى عند الحاجة لذلك، وكذلك تعتمد فئة <code>Editor</code> على واجهة المشترك المجردة فقط، ويسمح هذا بإضافة أنواع مشتركين جديدة دون تغيير شيفرة المحرر.
==== الناشر publisher ====
===== publisher/EventManager.java: الناشر الأساسي Basic Publisher =====
<syntaxhighlight lang="java">
package refactoring_guru.observer.example.publisher;
import refactoring_guru.observer.example.listeners.EventListener;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class EventManager {
    Map<String, List<EventListener>> listeners = new HashMap<>();
    public EventManager(String... operations) {
        for (String operation : operations) {
            this.listeners.put(operation, new ArrayList<>());
        }
    }
    public void subscribe(String eventType, EventListener listener) {
        List<EventListener> users = listeners.get(eventType);
        users.add(listener);
    }
    public void unsubscribe(String eventType, EventListener listener) {
        List<EventListener> users = listeners.get(eventType);
        users.remove(listener);
    }
    public void notify(String eventType, File file) {
        List<EventListener> users = listeners.get(eventType);
        for (EventListener listener : users) {
            listener.update(eventType, file);
        }
    }
}
</syntaxhighlight>
==== المحرر editor ====
===== editor/Editor.java: الناشر الحقيقي، تتبَّعه الكائنات الأخرى =====
<syntaxhighlight lang="java">
package refactoring_guru.observer.example.editor;
import refactoring_guru.observer.example.publisher.EventManager;
import java.io.File;
public class Editor {
    public EventManager events;
    private File file;
    public Editor() {
        this.events = new EventManager("open", "save");
    }
    public void openFile(String filePath) {
        this.file = new File(filePath);
        events.notify("open", file);
    }
    public void saveFile() throws Exception {
        if (this.file != null) {
            events.notify("save", file);
        } else {
            throw new Exception("Please open a file first.");
        }
    }
}
</syntaxhighlight>
==== المستمعين listeners ====
===== listeners/EventListener.java: واجهة المراقب المشتركة =====
<syntaxhighlight lang="java">
package refactoring_guru.observer.example.listeners;
import java.io.File;
public interface EventListener {
    void update(String eventType, File file);
}
</syntaxhighlight>
===== listeners/EmailNotificationListener.java: يرسل رسائل بريدية عند استلام تنبيهات =====
<syntaxhighlight lang="java">
package refactoring_guru.observer.example.listeners;
import java.io.File;
public class EmailNotificationListener implements EventListener {
    private String email;
    public EmailNotificationListener(String email) {
        this.email = email;
    }
    @Override
    public void update(String eventType, File file) {
        System.out.println("Email to " + email + ": Someone has performed " + eventType + " operation with the following file: " + file.getName());
    }
}
</syntaxhighlight>
===== listeners/LogOpenListener.java: يكتب رسالة إلى سجل (log) عند استلام تنبيهات =====
<syntaxhighlight lang="java">
package refactoring_guru.observer.example.listeners;
import java.io.File;
public class LogOpenListener implements EventListener {
    private File log;
    public LogOpenListener(String fileName) {
        this.log = new File(fileName);
    }
    @Override
    public void update(String eventType, File file) {
        System.out.println("Save to log " + log + ": Someone has performed " + eventType + " operation with the following file: " + file.getName());
    }
}
</syntaxhighlight>
===== Demo.java: ِشيفرة البدء (Initialization code) =====
<syntaxhighlight lang="java">
package refactoring_guru.observer.example;
import refactoring_guru.observer.example.editor.Editor;
import refactoring_guru.observer.example.listeners.EmailNotificationListener;
import refactoring_guru.observer.example.listeners.LogOpenListener;
public class Demo {
    public static void main(String[] args) {
        Editor editor = new Editor();
        editor.events.subscribe("open", new LogOpenListener("/path/to/log/file.txt"));
        editor.events.subscribe("save", new EmailNotificationListener("admin@example.com"));
        try {
            editor.openFile("test.txt");
            editor.saveFile();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
</syntaxhighlight>
==== OutputDemo.txt: نتائج التنفيذ ====
<syntaxhighlight>
Save to log \path\to\log\file.txt: Someone has performed open operation with the following file: test.txt
Email to admin@example.com: Someone has performed save operation with the following file: test.txt
</syntaxhighlight>
== الاستخدام في لغة #C ==
المستوى: ★ ★ ☆
الانتشار:  ★ ★ ★
أمثلة الاستخدام: يكثر استخدام نمط المراقب في لغة جافا، خاصة في عناصر الواجهة الرسومية، إذ يوفر طريقة للتفاعل مع الأحداث الواقعة في كائنات أخرى دون الحاجة للربط بفئاتها.
يمكن ملاحظة النمط من خلال أساليب الاشتراك التي تخزن الكائنات في قائمة وعبر الاستدعاءات إلى أسلوب التحديث المرسَل إلى الكائنات داخل تلك القائمة.
=== مثال تصوري ===
يوضح هذا المثال بنية نمط المراقب، ويركز على إجابة الأسئلة التالية:
* ما الفئات التي يتكون منها؟
* ما الأدوار التي تلعبها هذه الفئات؟
* كيف ترتبط عناصر النمط ببعضها؟
==== Program.cs: مثال تصوري ====
<syntaxhighlight lang="c#">
using System;
using System.Collections.Generic;
using System.Threading;
namespace RefactoringGuru.DesignPatterns.Observer.Conceptual
{
    public interface IObserver
    {
        // Receive update from subject
        void Update(ISubject subject);
    }
    public interface ISubject
    {
        // Attach an observer to the subject.
        void Attach(IObserver observer);
        // Detach an observer from the subject.
        void Detach(IObserver observer);
        // Notify all observers about an event.
        void Notify();
    }
    // The Subject owns some important state and notifies observers when the
    // state changes.
    public class Subject : ISubject
    {
        // For the sake of simplicity, the Subject's state, essential to all
        // subscribers, is stored in this variable.
        public int State { get; set; } = -0;
        // List of subscribers. In real life, the list of subscribers can be
        // stored more comprehensively (categorized by event type, etc.).
        private List<IObserver> _observers = new List<IObserver>();
        // The subscription management methods.
        public void Attach(IObserver observer)
        {
            Console.WriteLine("Subject: Attached an observer.");
            this._observers.Add(observer);
        }
        public void Detach(IObserver observer)
        {
            this._observers.Remove(observer);
            Console.WriteLine("Subject: Detached an observer.");
        }
        // Trigger an update in each subscriber.
        public void Notify()
        {
            Console.WriteLine("Subject: Notifying observers...");
            foreach (var observer in _observers)
            {
                observer.Update(this);
            }
        }
        // Usually, the subscription logic is only a fraction of what a Subject
        // can really do. Subjects commonly hold some important business logic,
        // that triggers a notification method whenever something important is
        // about to happen (or after it).
        public void SomeBusinessLogic()
        {
            Console.WriteLine("\nSubject: I'm doing something important.");
            this.State = new Random().Next(0, 10);
            Thread.Sleep(15);
            Console.WriteLine("Subject: My state has just changed to: " + this.State);
            this.Notify();
        }
    }
    // Concrete Observers react to the updates issued by the Subject they had
    // been attached to.
    class ConcreteObserverA : IObserver
    {
        public void Update(ISubject subject)
        {           
            if ((subject as Subject).State < 3)
            {
                Console.WriteLine("ConcreteObserverA: Reacted to the event.");
            }
        }
    }
    class ConcreteObserverB : IObserver
    {
        public void Update(ISubject subject)
        {
            if ((subject as Subject).State == 0 || (subject as Subject).State >= 2)
            {
                Console.WriteLine("ConcreteObserverB: Reacted to the event.");
            }
        }
    }
   
    class Program
    {
        static void Main(string[] args)
        {
            // The client code.
            var subject = new Subject();
            var observerA = new ConcreteObserverA();
            subject.Attach(observerA);
            var observerB = new ConcreteObserverB();
            subject.Attach(observerB);
            subject.SomeBusinessLogic();
            subject.SomeBusinessLogic();
            subject.Detach(observerB);
            subject.SomeBusinessLogic();
        }
    }
}
</syntaxhighlight>
== الاستخدام في لغة PHP ==
المستوى: ★ ★ ☆
الانتشار:  ★ ★ ★
أمثلة الاستخدام: يكثر استخدام نمط المراقب في لغة جافا، خاصة في عناصر الواجهة الرسومية، إذ يوفر طريقة للتفاعل مع الأحداث الواقعة في كائنات أخرى دون الحاجة للربط بفئاتها.
=== مثال تصوري ===
يوضح هذا المثال بنية نمط الوسيط، ويركز على إجابة الأسئلة التالية:
* ما الفئات التي يتكون منها؟
* ما الأدوار التي تلعبها هذه الفئات؟
* كيف ترتبط عناصر النمط ببعضها؟
== الاستخدام في لغة بايثون ==
المستوى: ★ ★ ☆
الانتشار:  ★ ★ ★
أمثلة الاستخدام: يكثر استخدام نمط المراقب في لغة جافا، خاصة في عناصر الواجهة الرسومية، إذ يوفر طريقة للتفاعل مع الأحداث الواقعة في كائنات أخرى دون الحاجة للربط بفئاتها.
=== مثال تصوري ===
يوضح هذا المثال بنية نمط الوسيط، ويركز على إجابة الأسئلة التالية:
* ما الفئات التي يتكون منها؟
* ما الأدوار التي تلعبها هذه الفئات؟
* كيف ترتبط عناصر النمط ببعضها؟
== الاستخدام في لغة روبي ==
المستوى: ★ ★ ☆
الانتشار:  ★ ★ ★
أمثلة الاستخدام: يكثر استخدام نمط المراقب في لغة جافا، خاصة في عناصر الواجهة الرسومية، إذ يوفر طريقة للتفاعل مع الأحداث الواقعة في كائنات أخرى دون الحاجة للربط بفئاتها.
=== مثال تصوري ===
يوضح هذا المثال بنية نمط الوسيط، ويركز على إجابة الأسئلة التالية:
* ما الفئات التي يتكون منها؟
* ما الأدوار التي تلعبها هذه الفئات؟
* كيف ترتبط عناصر النمط ببعضها؟
== الاستخدام في لغة Swift ==
المستوى: ★ ★ ☆
الانتشار:  ★ ★ ★
أمثلة الاستخدام: يكثر استخدام نمط المراقب في لغة جافا، خاصة في عناصر الواجهة الرسومية، إذ يوفر طريقة للتفاعل مع الأحداث الواقعة في كائنات أخرى دون الحاجة للربط بفئاتها.
=== مثال تصوري ===
يوضح هذا المثال بنية نمط الوسيط، ويركز على إجابة الأسئلة التالية:
* ما الفئات التي يتكون منها؟
* ما الأدوار التي تلعبها هذه الفئات؟
* كيف ترتبط عناصر النمط ببعضها؟
== الاستخدام في لغة Typescript ==
المستوى: ★ ★ ☆
الانتشار:  ★ ★ ★
أمثلة الاستخدام: يكثر استخدام نمط المراقب في لغة جافا، خاصة في عناصر الواجهة الرسومية، إذ يوفر طريقة للتفاعل مع الأحداث الواقعة في كائنات أخرى دون الحاجة للربط بفئاتها.
=== مثال تصوري ===
يوضح هذا المثال بنية نمط الوسيط، ويركز على إجابة الأسئلة التالية:
* ما الفئات التي يتكون منها؟
* ما الأدوار التي تلعبها هذه الفئات؟
* كيف ترتبط عناصر النمط ببعضها؟
== انظر أيضًا ==
== مصادر ==

مراجعة 06:12، 17 سبتمبر 2019

نمط المراقب هو نمط تصميم سلوكي يسمح لك بتحديد آلية اشتراك لتنبيه عدة كائنات بأي أحداث تقع للكائن الذي يراقبونه.

المشكلة

تخيل أن لديك نوعين من الكائنات: العميلcustomer والمتجر store، يهتم العميل بماركة بعينها من منتج ما (لنقل أنه إصدار جديد من آيفون)، وسيتاح هذا المنتج في المتجر قريبًا. بإمكان العميل أن يذهب كل يوم إلى المتجر ليرى إن كان الهاتف قد وصل أم لا، لكن أغلب تلك الزيارات ستكون هدرًا في الغالب إن كان الهاتف لم يأت، أليس كذلك؟ (انظر ش.1)

ضع الصورة. (ش.1) زيارة المتجر في مقابل إرسال السبام.

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

ونحن الآن أمام معضلة هنا، فإما أن يهدر العميل وقته في تفقد المنتجات الجديدة ذهابًا بنفسه إلى المتجر، وإما أن يهدر المتجر موارده في تنبيه العملاء الذين قد لا يرغبون في تلك المنتجات الجديدة.

الحل

يسمى الكائن الذي عليه الاهتمام هنا بالهدف (subject)، لكن بما أنه سينبه كائنات أخرى بالتغييرات التي ستحدث لحالته، فسنسميه الناشر (publisher)، ويسمى الكائن الذي يرغب في تتبع التغير الحاصل في حالة الناشر بالمشترك (subscriber).

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

ضع الصورة (ش.2) تسمح آلية الاشتراك للكائنات المنفردة بالاشتراك في إشعارات حدث ما.

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

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

ضع الصورة. (ش.3) ينبه الناشر المشتركين باستدعاء أسلوب تنبيه محدد إلى كائناتهم.

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

مثال واقعي

ضع الصورة (ش.4) اشتراكات المجلات والصحف.

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

البنية

ضع الصورة

  1. يرسل الناشر (publisher) الأحداث المهمة للكائنات الأخرى، وتقع هذه الأحداث حين يغير الناشر حالته أو ينفذ بعض الأساليب، وتحتوي الكائنات الناشرة على بنية اشتراك تحتية تسمح للمشتركين الجديد بالانضمام، وللمشتركين الحاليين بترك القائمة.
  2. عند وقوع حدث جديد فإن الناشر يذهب لقائمة الاشتراك ويستدعي ألوب التنبيه المصرح عنه في واجهة المشترك الموجودة في كل كائنِ مشترِك.
  3. تصرح واجهة المشترك (subscriber) عن واجهة التنبيه، وفي الغالب تتكون تلك الواجهة من أسلوب update واحد، وقد يحتوي الأسلوب على عدة عوامل تسمح للناشر بتمرير بعض تفاصيل الأحداث مع التحديث.
  4. ينفذ المشتركون الحقيقيون (Concrete Subscribers) بعض الإجراءات كرد على التنبيهات التي أرسلها الناشر، ويجب أن تستخدم كل تلك الفئات نفس الواجهة كي لا يُربط الناشر إلى الفئات الحقيقية.
  5. عادة ما يحتاج المشتركون إلى بعض المعلومات السياقية لمعالجة التحديث بشكل سليم، ولهذا فإن الناشرون عادة ما يمررون بعض البيانات السياقية كوسائط (arguments) لأسلوب التنبيه، ويستطيع الناشر أن يمرر نفسه كوسيط (argument)، سامحًا للمشترك بجلب أي بيانات مطلوبة مباشرة.
  6. ينشئ العميلُ (Client) الناشرَ وكائنات المشترِك بشكل منفصل ثم يسجل المشتركين في تحديثات الناشر.

مثال توضيحي

يتيح نمط المراقب في هذا المثال لكائن المحرر النصي أن ينبه كائنات الخدمة الأخرى بالتغييرات في حالته. (انظر ش.6)

(ش.6) تنبيه الكائنات بالأحداث التي تقع للكائنات الأخرى.

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

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

// تحتوي فئة الناشر الأساسية على شيفرة إدارة الاشتراك وأساليب التنبيه.
class EventManager is
    private field listeners: hash map of event types and listeners

    method subscribe(eventType, listener) is
        listeners.add(eventType, listener)

    method unsubscribe(eventType, listener) is
        listeners.remove(eventType, listener)

    method notify(eventType, data) is
        foreach (listener in listeners.of(eventType)) do
            listener.update(data)

// يحتوي الناشر الحقيقي على منطق عمل واقعي مهم لبعض المشتركين
// نستطيع استخلاص هذه الفئة من الناشر الأساسي لكن هذا ليس ممكنًا دومًا
// في الحياة الواقعية إذ أن الناشر الحقيقي قد يكون فئة فرعية أصلًا.
// وفي تلك الحالة فيمكن تصحيح منطق الاشتراك بالتركيب، كما فعلنا هنا.
class Editor is
    public field events: EventManager
    private field file: File

    constructor Editor() is
        events = new EventManager()

    // تستطيع أساليب منطق العمل أن تنبه المشتركين بالتغييرات.
    method openFile(path) is
        this.file = new File(path)
        events.notify("open", file.name)

    method saveFile() is
        file.write()
        events.notify("save", file.name)

    // ...


// هذه واجهة المشترك، إن كانت لغة البرمجة التي تستخدمها تدعم
// الأنواع الوظيفية فيمكنك استبدال هرمية المشترك كلها بمجموعة وظائف.
interface EventListener is
    method update(filename)

// يتفاعل المشتركون الحقيقيون مع التحديثات المرسلة من قبل الناشر المتصلين به.
class LoggingListener implements EventListener is
    private field log: File
    private field message

    constructor LoggingListener(log_filename, message) is
        this.log = new File(log_filename)
        this.message = message

    method update(filename) is
        log.write(replace('%s',filename,message))

class EmailAlertsListener implements EventListener is
    private field email: string

    constructor EmailAlertsListener(email, message) is
        this.email = email
        this.message = message

    method update(filename) is
        system.email(email, replace('%s',filename,message))


// يستطيع التطبيق أن يهيئ الناشرين والمشتركين أثناء وقت التشغيل.
class Application is
    method config() is
        editor = new TextEditor()

        logger = new LoggingListener(
            "/path/to/log.txt",
            "Someone has opened the file: %s");
        editor.events.subscribe("open", logger)

        emailAlerts = new EmailAlertsListener(
            "admin@example.com",
            "Someone has changed the file: %s")
        editor.events.subscribe("save", emailAlerts)

قابلية التطبيق

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

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

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

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

قائمة الاشتراك مرنة، لذا فإن المشتركين يستطيعون الانضمام وإلغاء الاشتراك كذلك متى شاؤوا.

كيفية الاستخدام

  1. ألق نظرة على منطق العمل لديك وحاول تقسيمه إلى جزئين، أولهما سيكون الوظيفية الأساسية (Core Functionality) الذي سيكون مستقلًا عن بقية الشيفرة والذي سيتصرف على أنه الناشر، أما الباقي فسيتحول إلى مجموعة من فئات المشترِك.
  2. صرّح عن واجهة المشترك، يجب أن تصرح عن أسلوب update واحد على الأقل.
  3. صرح عن واجهة الناشر وَصِف زوجًا من الأساليب لإضافة كائن المشترك وإزالته من القائمة، تذكر أن الناشرين يجب أن يعملوا مع المشتركين من خلال واجهة المشترِك حصرًا.
  4. حدد أين ستضع قائمة الاشتراك الفعلية وتطبيق أساليب الاشتراك (Implementation of subscribtion methods)، عادة ما تبدو تلك الشيفرة متطابقة لكل أنواع الناشرين، لذا يكون المكان البديهي لها هو فئة مجردة (abstract) مشتقة مباشرة من واجهة الناشر. يوسِّع الناشرون الحقيقيون (Concrete Publishers) هذه الفئة مكتسبين سلوك الاشتراك. لكن، إن كنت تطبق النمط على هرمية فئوية موجودة مسبقًا فجرب منظورًا مبنيًا على التركيب (Composition)، بأن تضع منطق الاشتراك في كائن منفصل، وتجعل كل الناشرين يستخدمونه.
  5. أنشئ فئات ناشر حقيقية. يجب أن تنبه هذه الفئاتُ كل المشتركين عند حدوث شيء مهم لأحد الناشرين.
  6. استخدم أساليب تنبيه التحديث (update notification methods) في فئات المشترك الحقيقية، سيحتاج أغلب المشتركين بعض البيانات السياقية عن الحدث، ويمكن تمريرها كوسيط (argument) لأسلوب التنبيه. يوجد خيار آخر يقترح أن يجلب المشترك أي بيانات مباشرة من التنبيه عند استلام ذلك التنبيه، وفي تلك الحالة يجب أن يمرر الناشر نفسه من خلال أسلوب التحديث. كذلك يوجد خيار ثالث أقل مرونة، وهو أن يُربط الناشر بالمشترك مباشرة من خلال منشئ (Constructor).
  7. يجب أن ينشئ العميل كل المشتركين ويسجلهم مع الناشرين المناسبين لهم.

المزايا والعيوب

المزايا

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

العيوب

  • يُنبه المشتركون عشوائيًا.

العلاقات مع الأنماط الأخرى

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

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

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

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

الاستخدام في لغة جافا

المستوى: ★ ★ ☆

الانتشار:  ★ ★ ★

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

يمكن ملاحظة النمط من خلال أساليب الاشتراك التي تخزن الكائنات في قائمة وعبر الاستدعاءات إلى أسلوب التحديث المرسَل إلى الكائنات داخل تلك القائمة.

الاشتراك في الأحداث

في هذا المثال ينشئ نمط المراقب تعاونًا غير مباشر بين كائنات محرر نصي، ففي كل مرة يتغير كائن Editor، فإنه ينبه المشتركين به، ويتفاعل كل من EmailNotificationListener و LogOpenListener مع هذه التنبيهات بتنفيذ سلوكياتهم الرئيسية (primary behaviours).

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

الناشر publisher

 publisher/EventManager.java: الناشر الأساسي Basic Publisher
package refactoring_guru.observer.example.publisher;

import refactoring_guru.observer.example.listeners.EventListener;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class EventManager {
    Map<String, List<EventListener>> listeners = new HashMap<>();

    public EventManager(String... operations) {
        for (String operation : operations) {
            this.listeners.put(operation, new ArrayList<>());
        }
    }

    public void subscribe(String eventType, EventListener listener) {
        List<EventListener> users = listeners.get(eventType);
        users.add(listener);
    }

    public void unsubscribe(String eventType, EventListener listener) {
        List<EventListener> users = listeners.get(eventType);
        users.remove(listener);
    }

    public void notify(String eventType, File file) {
        List<EventListener> users = listeners.get(eventType);
        for (EventListener listener : users) {
            listener.update(eventType, file);
        }
    }
}

المحرر editor

 editor/Editor.java: الناشر الحقيقي، تتبَّعه الكائنات الأخرى
package refactoring_guru.observer.example.editor;

import refactoring_guru.observer.example.publisher.EventManager;

import java.io.File;

public class Editor {
    public EventManager events;
    private File file;

    public Editor() {
        this.events = new EventManager("open", "save");
    }

    public void openFile(String filePath) {
        this.file = new File(filePath);
        events.notify("open", file);
    }

    public void saveFile() throws Exception {
        if (this.file != null) {
            events.notify("save", file);
        } else {
            throw new Exception("Please open a file first.");
        }
    }
}

المستمعين listeners

 listeners/EventListener.java: واجهة المراقب المشتركة
package refactoring_guru.observer.example.listeners;

import java.io.File;

public interface EventListener {
    void update(String eventType, File file);
}
 listeners/EmailNotificationListener.java: يرسل رسائل بريدية عند استلام تنبيهات
package refactoring_guru.observer.example.listeners;

import java.io.File;

public class EmailNotificationListener implements EventListener {
    private String email;

    public EmailNotificationListener(String email) {
        this.email = email;
    }

    @Override
    public void update(String eventType, File file) {
        System.out.println("Email to " + email + ": Someone has performed " + eventType + " operation with the following file: " + file.getName());
    }
}
 listeners/LogOpenListener.java: يكتب رسالة إلى سجل (log) عند استلام تنبيهات
package refactoring_guru.observer.example.listeners;

import java.io.File;

public class LogOpenListener implements EventListener {
    private File log;

    public LogOpenListener(String fileName) {
        this.log = new File(fileName);
    }

    @Override
    public void update(String eventType, File file) {
        System.out.println("Save to log " + log + ": Someone has performed " + eventType + " operation with the following file: " + file.getName());
    }
}
 Demo.java: ِشيفرة البدء (Initialization code)
package refactoring_guru.observer.example;

import refactoring_guru.observer.example.editor.Editor;
import refactoring_guru.observer.example.listeners.EmailNotificationListener;
import refactoring_guru.observer.example.listeners.LogOpenListener;

public class Demo {
    public static void main(String[] args) {
        Editor editor = new Editor();
        editor.events.subscribe("open", new LogOpenListener("/path/to/log/file.txt"));
        editor.events.subscribe("save", new EmailNotificationListener("admin@example.com"));

        try {
            editor.openFile("test.txt");
            editor.saveFile();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 OutputDemo.txt: نتائج التنفيذ

Save to log \path\to\log\file.txt: Someone has performed open operation with the following file: test.txt
Email to admin@example.com: Someone has performed save operation with the following file: test.txt

الاستخدام في لغة #C

المستوى: ★ ★ ☆

الانتشار:  ★ ★ ★

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

يمكن ملاحظة النمط من خلال أساليب الاشتراك التي تخزن الكائنات في قائمة وعبر الاستدعاءات إلى أسلوب التحديث المرسَل إلى الكائنات داخل تلك القائمة.

مثال تصوري

يوضح هذا المثال بنية نمط المراقب، ويركز على إجابة الأسئلة التالية:

  • ما الفئات التي يتكون منها؟
  • ما الأدوار التي تلعبها هذه الفئات؟
  • كيف ترتبط عناصر النمط ببعضها؟

 Program.cs: مثال تصوري

using System;
using System.Collections.Generic;
using System.Threading;

namespace RefactoringGuru.DesignPatterns.Observer.Conceptual
{
    public interface IObserver
    {
        // Receive update from subject
        void Update(ISubject subject);
    }

    public interface ISubject
    {
        // Attach an observer to the subject.
        void Attach(IObserver observer);

        // Detach an observer from the subject.
        void Detach(IObserver observer);

        // Notify all observers about an event.
        void Notify();
    }

    // The Subject owns some important state and notifies observers when the
    // state changes.
    public class Subject : ISubject
    {
        // For the sake of simplicity, the Subject's state, essential to all
        // subscribers, is stored in this variable.
        public int State { get; set; } = -0;

        // List of subscribers. In real life, the list of subscribers can be
        // stored more comprehensively (categorized by event type, etc.).
        private List<IObserver> _observers = new List<IObserver>();

        // The subscription management methods.
        public void Attach(IObserver observer)
        {
            Console.WriteLine("Subject: Attached an observer.");
            this._observers.Add(observer);
        }

        public void Detach(IObserver observer)
        {
            this._observers.Remove(observer);
            Console.WriteLine("Subject: Detached an observer.");
        }

        // Trigger an update in each subscriber.
        public void Notify()
        {
            Console.WriteLine("Subject: Notifying observers...");

            foreach (var observer in _observers)
            {
                observer.Update(this);
            }
        }

        // Usually, the subscription logic is only a fraction of what a Subject
        // can really do. Subjects commonly hold some important business logic,
        // that triggers a notification method whenever something important is
        // about to happen (or after it).
        public void SomeBusinessLogic()
        {
            Console.WriteLine("\nSubject: I'm doing something important.");
            this.State = new Random().Next(0, 10);

            Thread.Sleep(15);

            Console.WriteLine("Subject: My state has just changed to: " + this.State);
            this.Notify();
        }
    }

    // Concrete Observers react to the updates issued by the Subject they had
    // been attached to.
    class ConcreteObserverA : IObserver
    {
        public void Update(ISubject subject)
        {            
            if ((subject as Subject).State < 3)
            {
                Console.WriteLine("ConcreteObserverA: Reacted to the event.");
            }
        }
    }

    class ConcreteObserverB : IObserver
    {
        public void Update(ISubject subject)
        {
            if ((subject as Subject).State == 0 || (subject as Subject).State >= 2)
            {
                Console.WriteLine("ConcreteObserverB: Reacted to the event.");
            }
        }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            // The client code.
            var subject = new Subject();
            var observerA = new ConcreteObserverA();
            subject.Attach(observerA);

            var observerB = new ConcreteObserverB();
            subject.Attach(observerB);

            subject.SomeBusinessLogic();
            subject.SomeBusinessLogic();

            subject.Detach(observerB);

            subject.SomeBusinessLogic();
        }
    }
}

الاستخدام في لغة PHP

المستوى: ★ ★ ☆

الانتشار:  ★ ★ ★

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

مثال تصوري

يوضح هذا المثال بنية نمط الوسيط، ويركز على إجابة الأسئلة التالية:

  • ما الفئات التي يتكون منها؟
  • ما الأدوار التي تلعبها هذه الفئات؟
  • كيف ترتبط عناصر النمط ببعضها؟

الاستخدام في لغة بايثون

المستوى: ★ ★ ☆

الانتشار:  ★ ★ ★

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

مثال تصوري

يوضح هذا المثال بنية نمط الوسيط، ويركز على إجابة الأسئلة التالية:

  • ما الفئات التي يتكون منها؟
  • ما الأدوار التي تلعبها هذه الفئات؟
  • كيف ترتبط عناصر النمط ببعضها؟

الاستخدام في لغة روبي

المستوى: ★ ★ ☆

الانتشار:  ★ ★ ★

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

مثال تصوري

يوضح هذا المثال بنية نمط الوسيط، ويركز على إجابة الأسئلة التالية:

  • ما الفئات التي يتكون منها؟
  • ما الأدوار التي تلعبها هذه الفئات؟
  • كيف ترتبط عناصر النمط ببعضها؟

الاستخدام في لغة Swift

المستوى: ★ ★ ☆

الانتشار:  ★ ★ ★

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

مثال تصوري

يوضح هذا المثال بنية نمط الوسيط، ويركز على إجابة الأسئلة التالية:

  • ما الفئات التي يتكون منها؟
  • ما الأدوار التي تلعبها هذه الفئات؟
  • كيف ترتبط عناصر النمط ببعضها؟

الاستخدام في لغة Typescript

المستوى: ★ ★ ☆

الانتشار:  ★ ★ ★

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

مثال تصوري

يوضح هذا المثال بنية نمط الوسيط، ويركز على إجابة الأسئلة التالية:

  • ما الفئات التي يتكون منها؟
  • ما الأدوار التي تلعبها هذه الفئات؟
  • كيف ترتبط عناصر النمط ببعضها؟

انظر أيضًا

مصادر