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

من موسوعة حسوب
2.8 تمام المحتوى
3.0 إضافة صور
سطر 4: سطر 4:
== المشكلة ==
== المشكلة ==
إليك مثالًا يوضح الحالة التي قد ترغب فيها بتقييد الوصول إلى إلى كائن ما، لنفرض أن لديك كائنًا كبيرًا جدًا يستهلك موارد النظام بشراهة، وأنت لا تريده أصلًا بشكل مستمر، بل من وقت لآخر.
إليك مثالًا يوضح الحالة التي قد ترغب فيها بتقييد الوصول إلى إلى كائن ما، لنفرض أن لديك كائنًا كبيرًا جدًا يستهلك موارد النظام بشراهة، وأنت لا تريده أصلًا بشكل مستمر، بل من وقت لآخر.
 
[[ملف:dpprx.problem.png]]
ضع الصورة. استعلامات قاعدة البيانات قد تكون بطيئة جدًا. قد تستفيد من أسلوب البدء الكسول/المؤجَّل (Lazy Initialization) بأن تنشئ هذا الكائن عند الحاجة إليه فقط، وسيحتاج كل عملاء هذا الكائن أن ينفذوا شيفرة بدء مؤجل (Deferred Initialization) غير أننا سنخرج من هذا بشيفرات مكررة. وفي الحالات المثالية فإننا نريد أن نضع هذه الشيفرة مباشرة في فئة الكائن الخاص بنا، لكن هذا لا يمكن ضمانه في كل الحالات، ذلك أن الفئة قد تكون مثلًا جزءًا من مكتبة طرف ثالث مغلقة.
ضع الصورة. استعلامات قاعدة البيانات قد تكون بطيئة جدًا. قد تستفيد من أسلوب البدء الكسول/المؤجَّل (Lazy Initialization) بأن تنشئ هذا الكائن عند الحاجة إليه فقط، وسيحتاج كل عملاء هذا الكائن أن ينفذوا شيفرة بدء مؤجل (Deferred Initialization) غير أننا سنخرج من هذا بشيفرات مكررة. وفي الحالات المثالية فإننا نريد أن نضع هذه الشيفرة مباشرة في فئة الكائن الخاص بنا، لكن هذا لا يمكن ضمانه في كل الحالات، ذلك أن الفئة قد تكون مثلًا جزءًا من مكتبة طرف ثالث مغلقة.


== الحل ==
== الحل ==
يقترح نمط الوكيل أن تنشئ فئة وكيل جديدة بنفس الواجهة التي يستخدمها كائن الخدمة (Service Object)، ثم تحدِّث تطبيقك ليمرِّر كائن الوكيل إلى كل عملاء الكائن الأصلي، وعند استلام طلب من عميل ينشئ الوكيلُ كائنَ خدمةٍ حقيقي ويفوض كل المهام إليه.
يقترح نمط الوكيل أن تنشئ فئة وكيل جديدة بنفس الواجهة التي يستخدمها كائن الخدمة (Service Object)، ثم تحدِّث تطبيقك ليمرِّر كائن الوكيل إلى كل عملاء الكائن الأصلي، وعند استلام طلب من عميل ينشئ الوكيلُ كائنَ خدمةٍ حقيقي ويفوض كل المهام إليه.
 
[[ملف:dpprx.solution.png]]
الصورة. يتخفى الوكيل في هيئة قاعدة بيانات، ويمكنه التعامل مع البدء الكسول وحفظ النتائج دون ملاحظة العميل أو قاعدة البيانات الحقيقية.
الصورة. يتخفى الوكيل في هيئة قاعدة بيانات، ويمكنه التعامل مع البدء الكسول وحفظ النتائج دون ملاحظة العميل أو قاعدة البيانات الحقيقية.


سطر 16: سطر 16:
== مثال واقعي ==
== مثال واقعي ==
الصورة. يمكن استخدام بطاقات الائتمان للدفع مثل النقود الورقية تمامًا.
الصورة. يمكن استخدام بطاقات الائتمان للدفع مثل النقود الورقية تمامًا.
 
[[ملف:dpprx.live-example.png]]
تمثل بطاقات الائتمان وكيلًا للحساب البنكي الذي يكون وكيلًا لكمية مالية، وكلاهما يستخدم نفس الواجهة، أنهما يُستخدمان للدفع مقابل المشتريات. وذلك يسعد العميل لا شك لأنه ليس عليه حمل أمواله كلما حل وارتحل، كما يسعد البائع أيضًا لأنه يقيه مخاطر السرقة أثناء نقل المال إلى البنك، أو مخاطر فقد المال داخل المتجر.
تمثل بطاقات الائتمان وكيلًا للحساب البنكي الذي يكون وكيلًا لكمية مالية، وكلاهما يستخدم نفس الواجهة، أنهما يُستخدمان للدفع مقابل المشتريات. وذلك يسعد العميل لا شك لأنه ليس عليه حمل أمواله كلما حل وارتحل، كما يسعد البائع أيضًا لأنه يقيه مخاطر السرقة أثناء نقل المال إلى البنك، أو مخاطر فقد المال داخل المتجر.


== البنية ==
== البنية ==
[[ملف:dpprx.structure-indexed.png]]
#تصرح فئة '''Service Interface''' عن واجهة للخدمة، ويجب أن يتبع الوكيل هذه الواجهة ليتمكن من التخفي في صورة كائن خدمة.
#تصرح فئة '''Service Interface''' عن واجهة للخدمة، ويجب أن يتبع الوكيل هذه الواجهة ليتمكن من التخفي في صورة كائن خدمة.
#فئة الخدمة '''Service''' هي فئة تعطي بعض المنطق التجاري المفيد.
#فئة الخدمة '''Service''' هي فئة تعطي بعض المنطق التجاري المفيد.
سطر 26: سطر 27:
==مثال توضيحي==
==مثال توضيحي==
يبين هذا المثال كيف يمكن لنمط الوكيل Proxy أن يساهم في إدخال البدء الكسول (Lazy Initialization) والحفظ (Caching) إلى مكتبة تكامل من طرف ثالث ليوتيوب.
يبين هذا المثال كيف يمكن لنمط الوكيل Proxy أن يساهم في إدخال البدء الكسول (Lazy Initialization) والحفظ (Caching) إلى مكتبة تكامل من طرف ثالث ليوتيوب.
 
[[ملف:dpprx.example.png]]
الصورة. نتائج حفظ خدمة مع وكيل (Proxy)
الصورة. نتائج حفظ خدمة مع وكيل (Proxy)



مراجعة 14:05، 26 أبريل 2019

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

المشكلة

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

الحل

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

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

مثال واقعي

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

البنية

  1. تصرح فئة Service Interface عن واجهة للخدمة، ويجب أن يتبع الوكيل هذه الواجهة ليتمكن من التخفي في صورة كائن خدمة.
  2. فئة الخدمة Service هي فئة تعطي بعض المنطق التجاري المفيد.
  3. فئة Proxy لها حقل مرجعي يشير إلى كائن الخدمة، وبعد أن ينهي الوكيل عمليات معالجته (البدء الكسول والتسجيل والتحكم في الوصول والحفظ، إلخ) فإنه يمرر الطلب إلى كائن الخدمة، وعادة يدير الوكلاء دورة الحياة كاملة لكائنات الخدمة الخاصة بها.
  4. يجب أن تعمل فئة Client مع كل من الخدمات والوكلاء من خلال نفس الواجهة، وهكذا يمكنك تمرير وكيل إلى أي شيفرة تتوقع كائن خدمة.

مثال توضيحي

يبين هذا المثال كيف يمكن لنمط الوكيل Proxy أن يساهم في إدخال البدء الكسول (Lazy Initialization) والحفظ (Caching) إلى مكتبة تكامل من طرف ثالث ليوتيوب. الصورة. نتائج حفظ خدمة مع وكيل (Proxy)

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

// (Remote Service) واجهة خدمة بعيدة.
interface ThirdPartyYoutubeLib is
    method listVideos()
    method getVideoInfo(id)
    method downloadVideo(id)

// تستطيع أساليب هذه الفئة ،(Service Connector) الاستخدام الحقيقي لموصل الخدمة
// أن تطلب المعلومات من يوتيوب، وتعتمد سرعة الطلب على سرعة اتصال كل من المستخدم
// ويوتيوب بالإنترنت.
// سيبطئ أداء البرنامج إن حدثت طلبات كثيرة في نفس الوقت حتى لو طلبت كلها
// نقس البيانات.
class ThirdPartyYoutubeClass implements ThirdPartyYoutubeLib is
    method listVideos() is
        // إلى يوتيوب API أرسل طلب.

    method getVideoInfo(id) is
        // اجلب البيانات الوصفية لمقطع فيديو.

    method downloadVideo(id) is
        // حمِّل ملف فيديو من يوتيوب.

// نستطيع حفظ نتائج الطلب لبعض الوقت من أجل توفير البيانات، لكن قد يستحيل وضع 
// شيفرة كهذه في فئة الخدمة، فقد تكون جزءًا من مكتبة طرف ثالث و-أو معرّفة لفئة
// لهذا نضع الشيفرة المحفوظة في فئة وكيل جديدة تستخدم نفس الواجهة (Final) نهائية
// التي تستخدمها فئة الخدمة، وهي تفوض المهام إلى كائن الخدمة عند إرسال الطلبات
// الحقيقية فقط.
class CachedYoutubeClass implements ThirdPartyYouTubeLib is
    private field service: ThirdPartyYouTubeClass
    private field listCache, videoCache
    field needReset

    constructor CachedYoutubeClass(service: ThirdPartyYouTubeLib) is
        this.service = service

    method listVideos() is
        if (listCache == null || needReset)
            listCache = service.listVideos()
        return listCache

    method getVideoInfo(id) is
        if (videoCache == null || needReset)
            videoCache = service.getVideoInfo(id)
        return videoCache

    method downloadVideo(id) is
        if (!downloadExists(id) || needReset)
            service.downloadVideo(id)

// تظل فئة الواجهة الرسومية كما هي دون تغيير طالما أنها تعمل مع كائن الخدمة من خلال
// واجهة، هذه الفئة كانت تعمل مباشرة مع كائن الخدمة.
// ونستطيع تمرير كائن وكيل بدلًا من كائن خدمة حقيقي بما أنهما يستخدمان نفس الواجهة.
class YoutubeManager is
    protected field service: ThirdPartyYouTubeLib

    constructor YoutubeManager(service: ThirdPartyYouTubeLib) is
        this.service = service

    method renderVideoPage(id) is
        info = service.getVideoInfo(id)
        // صفحة الفيديو (Render) أخرج.

    method renderListPanel() is
        list = service.listVideos()
        // أخرج قائمة مصغَّرات الفيديو.

    method reactOnUserInput() is
        renderVideoPage()
        renderListPanel()

// يستطيع البرنامج إعداد الوكلاء عند الحاجة.
class Application is
    method init() is
        aYouTubeService = new ThirdPartyYouTubeClass()
        aYouTubeProxy = new CachedYouTubeClass(aYouTubeService)
        manager = new YouTubeManager(aYouTubeProxy)
        manager.reactOnUserInput()

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

توجد عشرات الاستخدامات لنمط الوكيل، إليك بعض أكثر هذه الاستخدامات شهرة:

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

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

  • التنفيذ المحلي لخدمة بعيدة (الوكيل البعيد Remote Proxy)، في حالة وجود كائن الخدمة على خادم بعيد، إذ في تلك الحالة يمرر الوكيلُ طلبَ العميلِ إلى الشبكة مسلِّمًا كل التفاصيل الخاصة بالعمل مع الشركة.

طلبات التسجيل (وكيل التسجيل Logging Proxy)، حين تريد أن تحافظ على تأريخ الطلبات الخاصة بكائن الخدمة. يستطيع الوكيل أن يسجل كل طلب قبل تمريره إلى الخدمة.

  • حفظ نتائج الطلب (وكيل الحفظ Caching Proxy)، حين تحتاج أن تحفظ نتائج طلبات عميل وتدير دورة حياة هذه المحفوظات، خاصة إن كانت النتائج كبيرة جدًا. يستطيع الوكيل أن يستخدم الحفظ للطلبات المتكررة التي تخرج نفس النتائج دومًا، وقد يستخدم معامِلات الطلبات كمفاتيح للحفظ (Cache Keys).

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

كذلك يستطيع الوكيل متابعة ما إن كان العميل قد عدَّل كائن الخدمة أم لا، ثم يمكن للعملاء الآخرين إعادة استخدام الكائنات التي لم تتغير.

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

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

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

المزايا

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

العيوب

  • قد تصبح الشيفرة معقدة بسبب حاجتك إلى إدخال فئات جديدة بكثرة.
  • قد تتأخر استجابة الخدمة.

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

  • يقدم نمط المحول (Adapter) واجهة مختلفة للكائن المغلَّف، بينما يزوده نمط الوكيل (Proxy) بنفس الواجهة، أما نمط المزخرف (Decorator) فيزوده بواجهة محسَّنة.
  • تتشابه الواجهة والوكيل في أنهما يعالجان كيانًا معقدًا ويبدءانه بشكل مستقل. وعلى عكس نمط الواجهة فإن الوكيل له نفس واجهة كائن الخدمة الخاص به، ما يجعلهما متبادليْن (Interchangeable).
  • يتشابه نمطا المزخرف والوكيل في بنيتيهما، لكن يختلفان في الغرض منهما، فكليهما بُنيا على مبدأ التركيب (Composition)، حيث يفترض بكائن ما أن يفوض بعض المهام إلى كائن آخر. والفرق بينهما هو أن الوكيل يدير دورة حياة كائن الخدمة الخاص به بنفسه، بينما يتحكم العميل دومًا في تركيب المزخرِفات.

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

المستوى: ★ ★ ☆

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

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

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

حفظ الوكيل

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

some_cool_media_library

some_cool_media_library/ThirdPartyYoutubeLib.java: واجهة الخدمة البعيدة
package refactoring_guru.proxy.example.some_cool_media_library;

import java.util.HashMap;

public interface ThirdPartyYoutubeLib {
    HashMap<String, Video> popularVideos();

    Video getVideo(String videoId);
}
 some_cool_media_library/ThirdPartyYoutubeClass.java: استخدام الخدمة البعيدة
package refactoring_guru.proxy.example.some_cool_media_library;

import java.util.HashMap;

public class ThirdPartyYoutubeClass implements ThirdPartyYoutubeLib {

    @Override
    public HashMap<String, Video> popularVideos() {
        connectToServer("http://www.youtube.com");
        return getRandomVideos();
    }

    @Override
    public Video getVideo(String videoId) {
        connectToServer("http://www.youtube.com/" + videoId);
        return getSomeVideo(videoId);
    }

    // -----------------------------------------------------------------------
    // أساليب وهمية لمحاكاة نشاط الشبكة، سرعة هذه الأساليب بطيئة جدًا.

    private int random(int min, int max) {
        return min + (int) (Math.random() * ((max - min) + 1));
    }

    private void experienceNetworkLatency() {
        int randomLatency = random(5, 10);
        for (int i = 0; i < randomLatency; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
        }
    }

    private void connectToServer(String server) {
        System.out.print("Connecting to " + server + "... ");
        experienceNetworkLatency();
        System.out.print("Connected!" + "\n");
    }

    private HashMap<String, Video> getRandomVideos() {
        System.out.print("Downloading populars... ");

        experienceNetworkLatency();
        HashMap<String, Video> hmap = new HashMap<String, Video>();
        hmap.put("catzzzzzzzzz", new Video("sadgahasgdas", "Catzzzz.avi"));
        hmap.put("mkafksangasj", new Video("mkafksangasj", "Dog play with ball.mp4"));
        hmap.put("dancesvideoo", new Video("asdfas3ffasd", "Dancing video.mpq"));
        hmap.put("dlsdk5jfslaf", new Video("dlsdk5jfslaf", "Barcelona vs RealM.mov"));
        hmap.put("3sdfgsd1j333", new Video("3sdfgsd1j333", "Programing lesson#1.avi"));

        System.out.print("Done!" + "\n");
        return hmap;
    }

    private Video getSomeVideo(String videoId) {
        System.out.print("Downloading video... ");

        experienceNetworkLatency();
        Video video = new Video(videoId, "Some video title");

        System.out.print("Done!" + "\n");
        return video;
    }

}
 some_cool_media_library/Video.java: ملف فيديو
package refactoring_guru.proxy.example.some_cool_media_library;

public class Video {
    public String id;
    public String title;
    public String data;

    Video(String id, String title) {
        this.id = id;
        this.title = title;
        this.data = "Random video.";
    }
}

الوكيل

 proxy/YoutubeCacheProxy.java: حفظ الوكيل
package refactoring_guru.proxy.example.proxy;

import refactoring_guru.proxy.example.some_cool_media_library.ThirdPartyYoutubeClass;
import refactoring_guru.proxy.example.some_cool_media_library.ThirdPartyYoutubeLib;
import refactoring_guru.proxy.example.some_cool_media_library.Video;

import java.util.HashMap;

public class YoutubeCacheProxy implements ThirdPartyYoutubeLib {
    private ThirdPartyYoutubeLib youtubeService;
    private HashMap<String, Video> cachePopular = new HashMap<String, Video>();
    private HashMap<String, Video> cacheAll = new HashMap<String, Video>();

    public YoutubeCacheProxy() {
        this.youtubeService = new ThirdPartyYoutubeClass();
    }

    @Override
    public HashMap<String, Video> popularVideos() {
        if (cachePopular.isEmpty()) {
            cachePopular = youtubeService.popularVideos();
        } else {
            System.out.println("Retrieved list from cache.");
        }
        return cachePopular;
    }

    @Override
    public Video getVideo(String videoId) {
        Video video = cacheAll.get(videoId);
        if (video == null) {
            video = youtubeService.getVideo(videoId);
            cacheAll.put(videoId, video);
        } else {
            System.out.println("Retrieved video '" + videoId + "' from cache.");
        }
        return video;
    }

    public void reset() {
        cachePopular.clear();
        cacheAll.clear();
    }
}

المحمِّل

 downloader/YoutubeDownloader.java: تطبيق تحميل الوسائط
package refactoring_guru.proxy.example.downloader;

import refactoring_guru.proxy.example.some_cool_media_library.ThirdPartyYoutubeLib;
import refactoring_guru.proxy.example.some_cool_media_library.Video;

import java.util.HashMap;

public class YoutubeDownloader {
    private ThirdPartyYoutubeLib api;

    public YoutubeDownloader(ThirdPartyYoutubeLib api) {
        this.api = api;
    }

    public void renderVideoPage(String videoId) {
        Video video = api.getVideo(videoId);
        System.out.println("\n-------------------------------");
        System.out.println("Video page (imagine fancy HTML)");
        System.out.println("ID: " + video.id);
        System.out.println("Title: " + video.title);
        System.out.println("Video: " + video.data);
        System.out.println("-------------------------------\n");
    }

    public void renderPopularVideos() {
        HashMap<String, Video> list = api.popularVideos();
        System.out.println("\n-------------------------------");
        System.out.println("Most popular videos on Youtube (imagine fancy HTML)");
        for (Video video : list.values()) {
            System.out.println("ID: " + video.id + " / Title: " + video.title);
        }
        System.out.println("-------------------------------\n");
    }
}
 Demo.java: شيفرة البدء
package refactoring_guru.proxy.example;

import refactoring_guru.proxy.example.downloader.YoutubeDownloader;
import refactoring_guru.proxy.example.proxy.YoutubeCacheProxy;
import refactoring_guru.proxy.example.some_cool_media_library.ThirdPartyYoutubeClass;

public class Demo {

    public static void main(String[] args) {
        YoutubeDownloader naiveDownloader = new YoutubeDownloader(new ThirdPartyYoutubeClass());
        YoutubeDownloader smartDownloader = new YoutubeDownloader(new YoutubeCacheProxy());

        long naive = test(naiveDownloader);
        long smart = test(smartDownloader);
        System.out.print("Time saved by caching proxy: " + (naive - smart) + "ms");

    }

    private static long test(YoutubeDownloader downloader) {
        long startTime = System.currentTimeMillis();

        // سلوك المستخدم في تطبيقنا:
        downloader.renderPopularVideos();
        downloader.renderVideoPage("catzzzzzzzzz");
        downloader.renderPopularVideos();
        downloader.renderVideoPage("dancesvideoo");
        // قد يزور المستخدمون نفس الصفحة كثيرًا.
        downloader.renderVideoPage("catzzzzzzzzz");
        downloader.renderVideoPage("someothervid");

        long estimatedTime = System.currentTimeMillis() - startTime;
        System.out.print("Time elapsed: " + estimatedTime + "ms\n");
        return estimatedTime;
    }
}
 OutputDemo.txt: نتائج التنفيذ
Connecting to http://www.youtube.com... Connected!
Downloading populars... Done!

-------------------------------
Most popular videos on Youtube (imagine fancy HTML)
ID: sadgahasgdas / Title: Catzzzz.avi
ID: asdfas3ffasd / Title: Dancing video.mpq
ID: 3sdfgsd1j333 / Title: Programing lesson#1.avi
ID: mkafksangasj / Title: Dog play with ball.mp4
ID: dlsdk5jfslaf / Title: Barcelona vs RealM.mov
-------------------------------

Connecting to http://www.youtube.com/catzzzzzzzzz... Connected!
Downloading video... Done!

-------------------------------
Video page (imagine fancy HTML)
ID: catzzzzzzzzz
Title: Some video title
Video: Random video.
-------------------------------

Connecting to http://www.youtube.com... Connected!
Downloading populars... Done!

-------------------------------
Most popular videos on Youtube (imagine fancy HTML)
ID: sadgahasgdas / Title: Catzzzz.avi
ID: asdfas3ffasd / Title: Dancing video.mpq
ID: 3sdfgsd1j333 / Title: Programing lesson#1.avi
ID: mkafksangasj / Title: Dog play with ball.mp4
ID: dlsdk5jfslaf / Title: Barcelona vs RealM.mov
-------------------------------

Connecting to http://www.youtube.com/dancesvideoo... Connected!
Downloading video... Done!

-------------------------------
Video page (imagine fancy HTML)
ID: dancesvideoo
Title: Some video title
Video: Random video.
-------------------------------

Connecting to http://www.youtube.com/catzzzzzzzzz... Connected!
Downloading video... Done!

-------------------------------
Video page (imagine fancy HTML)
ID: catzzzzzzzzz
Title: Some video title
Video: Random video.
-------------------------------

Connecting to http://www.youtube.com/someothervid... Connected!
Downloading video... Done!

-------------------------------
Video page (imagine fancy HTML)
ID: someothervid
Title: Some video title
Video: Random video.
-------------------------------

Time elapsed: 9354ms
Connecting to http://www.youtube.com... Connected!
Downloading populars... Done!

-------------------------------
Most popular videos on Youtube (imagine fancy HTML)
ID: sadgahasgdas / Title: Catzzzz.avi
ID: asdfas3ffasd / Title: Dancing video.mpq
ID: 3sdfgsd1j333 / Title: Programing lesson#1.avi
ID: mkafksangasj / Title: Dog play with ball.mp4
ID: dlsdk5jfslaf / Title: Barcelona vs RealM.mov
-------------------------------

Connecting to http://www.youtube.com/catzzzzzzzzz... Connected!
Downloading video... Done!

-------------------------------
Video page (imagine fancy HTML)
ID: catzzzzzzzzz
Title: Some video title
Video: Random video.
-------------------------------

Retrieved list from cache.

-------------------------------
Most popular videos on Youtube (imagine fancy HTML)
ID: sadgahasgdas / Title: Catzzzz.avi
ID: asdfas3ffasd / Title: Dancing video.mpq
ID: 3sdfgsd1j333 / Title: Programing lesson#1.avi
ID: mkafksangasj / Title: Dog play with ball.mp4
ID: dlsdk5jfslaf / Title: Barcelona vs RealM.mov
-------------------------------

Connecting to http://www.youtube.com/dancesvideoo... Connected!
Downloading video... Done!

-------------------------------
Video page (imagine fancy HTML)
ID: dancesvideoo
Title: Some video title
Video: Random video.
-------------------------------

Retrieved video 'catzzzzzzzzz' from cache.

-------------------------------
Video page (imagine fancy HTML)
ID: catzzzzzzzzz
Title: Some video title
Video: Random video.
-------------------------------

Connecting to http://www.youtube.com/someothervid... Connected!
Downloading video... Done!

-------------------------------
Video page (imagine fancy HTML)
ID: someothervid
Title: Some video title
Video: Random video.
-------------------------------

Time elapsed: 5875ms
Time saved by caching proxy: 3479ms

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

المستوى: ★ ★ ☆

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

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

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

 مثال تصوري

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

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

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

using System;

namespace RefactoringGuru.DesignPatterns.Proxy.Conceptual
{
    // RealSubject تصرح واجهة الكائن عن العمليات المشتركة لكل من فئة 
    // والوكيل، وستستطيع تمرير وكيل بدلًا من كائن حقيقي إلى العميل طالما 
    // باستخدام هذه الواجهة RealSubject يعمل العميل مع.
    public interface ISubject
    {
        void Request();
    }
    
    // على بعض المنطق التجاري، وعادة تستطيع فئات RealSubject تحتوي فئة 
    // أن تنفذ بعض المهام المفيدة التي قد تكون بطيئة أو حساسة RealSubject
    // كتصحيح بيانات الإدخال مثلًا، ويستطيع الوكيل حل هذه المشاكل دون أي
    // RealSubject تغييرات في شيفرة فئة.
    class RealSubject : ISubject
    {
        public void Request()
        {
            Console.WriteLine("RealSubject: Handling Request.");
        }
    }
    
    // RealSubject للوكيل واجهة مطابقة لواجهة فئة.
    class Proxy : ISubject
    {
        private RealSubject _realSubject;
        
        public Proxy(RealSubject realSubject)
        {
            this._realSubject = realSubject;
        }
        
        // (Lazy Loading) أكثر استخدامات الوكيل هي التحميل الكسول
        // وتقييد الوصول والتسجيل، إلخ (Caching) والحفظ.
        // يستطيع الوكيل تنفيذ أحد هذه الاستخدامات ثم يمرر التنفيذ إلى نفس الأسلوب
        // وذلك وفقًا لنتيجة تنفيذ ذلك الأسلوب ، RealSubject في كائن مرتبط به من فئة.
        public void Request()
        {
            if (this.CheckAccess())
            {
                this._realSubject = new RealSubject();
                this._realSubject.Request();

                this.LogAccess();
            }
        }
        
        public bool CheckAccess()
        {
        // هنا (Checks) ينبغي أن تُجرى تحققات 
            Console.WriteLine("Proxy: Checking access prior to firing a real request.");

            return true;
        }
        
        public void LogAccess()
        {
            Console.WriteLine("Proxy: Logging the time of request.");
        }
    }
    
    public class Client
    {
        // (Subjects) يفترض بشيفرة العميل أن تعمل مع كل الكائنات، سواء التوابع 
        // من أجل دعم التوابع الحقيقية والوكلاء ، (Subject) أو الوكلاء، من خلال واجهة التابع
        // لكن العملاء في الحياة الواقعية يعملون مع توابعهم الحقيقية مباشرة،
        // ولتسهيل استخدام النمط في تلك الحالة، يمكنك توسيع وكيلك من فئة
        // (Real Subject) التابع الحقيقي
        public void ClientCode(ISubject subject)
        {
            // ...
            
            subject.Request();
            
            // ...
        }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            Client client = new Client();
            
            Console.WriteLine("Client: Executing the client code with a real subject:");
            RealSubject realSubject = new RealSubject();
            client.ClientCode(realSubject);

            Console.WriteLine();

            Console.WriteLine("Client: Executing the same client code with a proxy:");
            Proxy proxy = new Proxy(realSubject);
            client.ClientCode(proxy);
        }
    }
}

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

Client: Executing the client code with a real subject:
RealSubject: Handling Request.

Client: Executing the same client code with a proxy:
Proxy: Checking access prior to firing a real request.
RealSubject: Handling Request.
Proxy: Logging the time of request.

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

المستوى: ★ ★ ☆

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

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

مثال تصوري

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

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

سيكون من السهل عليك استيعاب المثال التالي بعد تعلم بنية النمط، بناء على استخدام واقعي له في لغة PHP.

 index.php: مثال تصوري 

<?php

namespace RefactoringGuru\Proxy\Conceptual;

/**
 * RealSubject تصرح واجهة الكائن عن العمليات المشتركة لكل من فئة 
 * والوكيل، وستستطيع تمرير وكيل بدلًا من كائن حقيقي إلى العميل طالما 
 * باستخدام هذه الواجهة RealSubject يعمل العميل مع.
 */
interface Subject
{
    public function request(): void;
}

/**
 * على بعض المنطق التجاري، وعادة تستطيع فئات RealSubject تحتوي فئة 
 * أن تنفذ بعض المهام المفيدة التي قد تكون بطيئة أو حساسة RealSubject
 * كتصحيح بيانات الإدخال مثلًا، ويستطيع الوكيل حل هذه المشاكل دون أي
 * RealSubject تغييرات في شيفرة فئة.
 */
class RealSubject implements Subject
{
    public function request(): void
    {
        echo "RealSubject: Handling request.\n";
    }
}

/**
 *  RealSubject للوكيل واجهة مطابقة لواجهة فئة.
 */
class Proxy implements Subject
{
    /**
     * @var RealSubject
     */
    private $realSubject;

    /**
     * يحافظ الوكيل على مرجع إلى كائن من 
     * فئة RealSubject، وإما أن يكون محمَّلًا بكسَل (Lazy Loaded) أو ممررًا إلى 
     * الوكيل بواسطة العميل.
     */
    public function __construct(RealSubject $realSubject)
    {
        $this->realSubject = $realSubject;
    }

    /**
     * (Lazy Loading) أكثر استخدامات الوكيل هي التحميل الكسول
     * وتقييد الوصول والتسجيل، إلخ (Caching) والحفظ.
     * يستطيع الوكيل تنفيذ أحد هذه الاستخدامات ثم يمرر التنفيذ إلى 
     * RealSubject نفس الأسلوب في كائن مرتبط به من فئة
     * وذلك وفقًا لنتيجة تنفيذ ذلك الأسلوب.
     */
    public function request(): void
    {
        if ($this->checkAccess()) {
            $this->realSubject->request();
            $this->logAccess();
        }
    }

    private function checkAccess(): bool
    {
        // هنا (Checks) ينبغي أن تُجرى تحققات 
        echo "Proxy: Checking access prior to firing a real request.\n";

        return true;
    }

    private function logAccess(): void
    {
        echo "Proxy: Logging the time of request.\n";
    }
}

/**
 * (Subjects) يفترض بشيفرة العميل أن تعمل مع كل الكائنات، سواء التوابع 
 * أو الوكلاء، من خلال واجهة التابع، وذلك كي تدعم كلًا من التوابع الحقيقية
 * والوكلاء. لكن في الحياة الواقعية فإن ما يحدث هو أن العملاء تتعامل مع
 * توابعها الحقيقية مباشرة، وفي تلك الحالة تستطيع توسيع وكيلك من فئة
 * التابع الحقيقي، من أجل استخدام النمط بشكل أسهل.
 */
function clientCode(Subject $subject)
{
    // ...

    $subject->request();

    // ...
}

echo "Client: Executing the client code with a real subject:\n";
$realSubject = new RealSubject;
clientCode($realSubject);

echo "\n";

echo "Client: Executing the same client code with a proxy:\n";
$proxy = new Proxy($realSubject);
clientCode($proxy);

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

Client: Executing the client code with a real subject:
RealSubject: Handling request.

Client: Executing the same client code with a proxy:
Proxy: Checking access prior to firing a real request.
RealSubject: Handling request.
Proxy: Logging the time of request.

مثال واقعي

 index.php: مثال واقعي

<?php

namespace RefactoringGuru\Proxy\RealWorld;

/**
 * تصف واجهة التابعُ واجهةَ الكائنِ الحقيقي.
 * الحق أن تطبيقات كثيرة في الحياة الواقعية قد لا تكون هذه الواجهة 
 * فيها واضحة ومحددة بشكل صريح، فإن كانت تلك حالتك فإن أفضل خيار لك
 * هو توسيع الوكيل من إحدى فئات تطبيقك الموجودة فعلًا.
 * وإن كنت تقوم بهذا لأول مرة ولا تدري من أين تبدأ، فإن أول خطوة
 * هي استخراج الواجهة المناسبة.
 */
interface Downloader
{
    public function download(string $url): string;
}

/**
 * المهمة الحقيقية ولو لم تأت على الوجه (Real Subject) ينفذ التابع الحقيقي
 * الأمثل، وحين يحاول عميل أن يحمّل نفس الملف مرة ثانية فإن المحمِّل
 * الخاص بنا يقوم بهذا حرفيًا، أي يحمِّل الملف مرة أخرى بدلًا من جلبه
 * من الذاكرة المحفوظة قبلًا.
 */
class SimpleDownloader implements Downloader
{
    public function download(string $url): string
    {
        echo "Downloading a file from the Internet.\n";
        $result = file_get_contents($url);
        echo "Downloaded bytes: " . strlen($result) . "\n";
        
        return $result;
    }
}

/**
 * فئة الوكيل هي محاولتنا الأولى لجعل التحميل أكثر كفاءة، فهي تغلف كائن
 * المحمِّل الحقيقي وتفوض إليه أولى طلبات التحمبل، وتُحفظ النتيجة عندئذ
 * جاعلة الطلبات اللاحقة تعيد ملفات مما حُفظ عند تحميله أول مرة بدلًا من
 * تحميله مرة أخرى.
 *
 * لاحظ أن الوكيل يجب أن يستخدم نفس الواجهة التي يستخدمها التابع الحقيقي.
 */
class CachingDownloader implements Downloader
{
    /**
     * @var SimpleDownloader
     */
    private $downloader;

    /**
     * @var string[]
     */
    private $cache = [];

    public function __construct(SimpleDownloader $downloader)
    {
        $this->downloader = $downloader;
    }

    public function download(string $url): string
    {
        if (!isset($this->cache[$url])) {
            echo "CacheProxy MISS. ";
            $result = $this->downloader->download($url);
            $this->cache[$url] = $result;
        } else {
            echo "CacheProxy HIT. Retrieving result from cache.\n";
        }
        return $this->cache[$url];
    }
}

/**
 * قد ترسل شيفرة العميل طلبات تحميل متشابهة، وفي تلك الحالة فإن وكيل الحفظ
 * بإعادة نتائج حفظها من قبل (Traffic) يوفر الوقت ومعدل النقل.
 *
 * لا يدرك العميل أنه يتعامل مع وكيل بسبب أنه يعمل مع المحمِّلات من خلال واجهة
 * (Abstract) مجرَّدة
 */
function clientCode(Downloader $subject)
{
    // ...

    $result = $subject->download("http://example.com/");

    // قد تُحفظ طلبات التحميل المتكررة من أجل السرعة.

    $result = $subject->download("http://example.com/");

    // ...
}

echo "Executing client code with real subject:\n";
$realSubject = new SimpleDownloader;
clientCode($realSubject);

echo "\n";

echo "Executing the same client code with a proxy:\n";
$proxy = new CachingDownloader($realSubject);
clientCode($proxy);

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

Executing client code with real subject:
Downloading a file from the Internet.
Downloaded bytes: 1270
Downloading a file from the Internet.
Downloaded bytes: 1270

Executing the same client code with a proxy:
CacheProxy MISS. Downloading a file from the Internet.
Downloaded bytes: 1270
CacheProxy HIT. Retrieving result from cache.

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

المستوى: ★ ★ ☆

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

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

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

مثال تصوري

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

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

 main.py: مثال تصوري

from abc import ABC, abstractmethod


class Subject(ABC):
    """
    RealSubject تصرح واجهة الكائن عن العمليات المشتركة لكل من فئة 
    والوكيل، وستستطيع تمرير وكيل بدلًا من كائن حقيقي إلى العميل طالما 
    باستخدام هذه الواجهة RealSubject يعمل العميل مع.
    """

    @abstractmethod
    def request(self) -> None:
        pass


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

    """

    def request(self) -> None:
        print("RealSubject: Handling request.")


class Proxy(Subject):
    """
    RealSubject للوكيل واجهة مطابقة لواجهة فئة.
    """

    def __init__(self, real_subject: RealSubject) -> None:
        self._real_subject = real_subject

    def request(self) -> None:
        """
        (Lazy Loading) أكثر استخدامات الوكيل هي التحميل الكسول
        وتقييد الوصول والتسجيل، إلخ (Caching) والحفظ.
        يستطيع الوكيل تنفيذ أحد هذه الاستخدامات ثم يمرر التنفيذ إلى 
        RealSubject نفس الأسلوب في كائن مرتبط به من فئة
        وذلك وفقًا لنتيجة تنفيذ ذلك الأسلوب.

        """

        if self.check_access():
            self._real_subject.request()
            self.log_access()

    def check_access(self) -> bool:
        print("Proxy: Checking access prior to firing a real request.")
        return True

    def log_access(self) -> None:
        print("Proxy: Logging the time of request.", end="")


def client_code(subject: Subject) -> None:
    """
    (Subjects) يفترض بشيفرة العميل أن تعمل مع كل الكائنات، سواء التوابع 
    أو الوكلاء، من خلال واجهة التابع، وذلك كي تدعم كلًا من التوابع الحقيقية
    والوكلاء. لكن في الحياة الواقعية فإن ما يحدث هو أن العملاء تتعامل مع
    توابعها الحقيقية مباشرة، وفي تلك الحالة تستطيع توسيع وكيلك من فئة
    التابع الحقيقي، من أجل استخدام النمط بشكل أسهل.

    """

    # ...

    subject.request()

    # ...


if __name__ == "__main__":
    print("Client: Executing the client code with a real subject:")
    real_subject = RealSubject()
    client_code(real_subject)

    print("")

    print("Client: Executing the same client code with a proxy:")
    proxy = Proxy(real_subject)
    client_code(proxy)

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

Client: Executing the client code with a real subject:
RealSubject: Handling request.

Client: Executing the same client code with a proxy:
Proxy: Checking access prior to firing a real request.
RealSubject: Handling request.
Proxy: Logging the time of request.

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

المستوى: ★ ★ ☆

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

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

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

مثال تصوري

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

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

 main.rb: مثال تصوري

 # RealSubject تصرح واجهة الكائن عن العمليات المشتركة لكل من فئة 
 # والوكيل، وستستطيع تمرير وكيل بدلًا من كائن حقيقي إلى العميل طالما 
 # باستخدام هذه الواجهة RealSubject يعمل العميل مع.
class Subject
  # @abstract
  def request
    raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
  end
end

 # على بعض المنطق التجاري، وعادة تستطيع فئات RealSubject تحتوي فئة 
 # أن تنفذ بعض المهام المفيدة التي قد تكون بطيئة أو حساسة RealSubject
 # كتصحيح بيانات الإدخال مثلًا، ويستطيع الوكيل حل هذه المشاكل دون أي
 # RealSubject تغييرات في شيفرة فئة.
class RealSubject < Subject
  def request
    puts 'RealSubject: Handling request.'
  end
end

 # RealSubject للوكيل واجهة مطابقة لواجهة فئة.
class Proxy < Subject
  # @param [RealSubject] real_subject
  def initialize(real_subject)
    @real_subject = real_subject
  end

  # (Lazy Loading) أكثر استخدامات الوكيل هي التحميل الكسول
  # وتقييد الوصول والتسجيل، إلخ (Caching) والحفظ.
  # يستطيع الوكيل تنفيذ أحد هذه الاستخدامات ثم يمرر التنفيذ إلى 
  # RealSubject نفس الأسلوب في كائن مرتبط به من فئة
  # وذلك وفقًا لنتيجة تنفيذ ذلك الأسلوب.
  def request
    return unless check_access

    @real_subject.request
    log_access
  end

  # @return [Boolean]
  def check_access
    puts 'Proxy: Checking access prior to firing a real request.'
    true
  end

  def log_access
    print 'Proxy: Logging the time of request.'
  end
end

# (Subjects) يفترض بشيفرة العميل أن تعمل مع كل الكائنات، سواء التوابع 
# أو الوكلاء، من خلال واجهة التابع، وذلك كي تدعم كلًا من التوابع الحقيقية
# والوكلاء. لكن في الحياة الواقعية فإن ما يحدث هو أن العملاء تتعامل مع
# توابعها الحقيقية مباشرة، وفي تلك الحالة تستطيع توسيع وكيلك من فئة
# التابع الحقيقي، من أجل استخدام هذا النمط بشكل أسهل.
def client_code(subject)
  # ...

  subject.request

  # ...
end

puts 'Client: Executing the client code with a real subject:'
real_subject = RealSubject.new
client_code(real_subject)

puts "\n"

puts 'Client: Executing the same client code with a proxy:'
proxy = Proxy.new(real_subject)
client_code(proxy)

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

Client: Executing the client code with a real subject:
RealSubject: Handling request.

Client: Executing the same client code with a proxy:
Proxy: Checking access prior to firing a real request.
RealSubject: Handling request.
Proxy: Logging the time of request.

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

المستوى: ★ ★ ☆

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

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

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

مثال تصوري

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

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

 Example.swift: مثال تصوري

import XCTest

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

    func request()
}

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

    func request() {
        print("RealSubject: Handling request.")
    }
}

/// RealSubject للوكيل واجهة مطابقة لواجهة فئة.
class Proxy: Subject {

    private var realSubject: RealSubject

    /// يحافظ الوكيل على مرجع إلى كائن من 
    /// فئة RealSubject، وإما أن يكون محمَّلًا بكسَل (Lazy Loaded) أو ممررًا إلى 
    /// الوكيل بواسطة العميل.
    init(_ realSubject: RealSubject) {
        self.realSubject = realSubject
    }

    /// (Lazy Loading) أكثر استخدامات الوكيل هي التحميل الكسول
    /// وتقييد الوصول والتسجيل، إلخ (Caching) والحفظ.
    /// يستطيع الوكيل تنفيذ أحد هذه الاستخدامات ثم يمرر التنفيذ إلى 
    /// RealSubject نفس الأسلوب في كائن مرتبط به من فئة
    /// وذلك وفقًا لنتيجة تنفيذ هذا الأسلوب.
    func request() {

        if (checkAccess()) {
            realSubject.request()
            logAccess()
        }
    }

    private func checkAccess() -> Bool {

        /// هنا (Checks) ينبغي أن تُجرى تحققات.

        print("Proxy: Checking access prior to firing a real request.")

        return true
    }

    private func logAccess() {
        print("Proxy: Logging the time of request.")
    }
}

/// (Subjects) يفترض بشيفرة العميل أن تعمل مع كل الكائنات، سواء التوابع 
/// أو الوكلاء، من خلال واجهة التابع، وذلك كي تدعم كلًا من التوابع الحقيقية
/// والوكلاء. لكن في الحياة الواقعية فإن ما يحدث هو أن العملاء تتعامل مع
/// توابعها الحقيقية مباشرة، وفي تلك الحالة تستطيع توسيع وكيلك من فئة
/// التابع الحقيقي، من أجل استخدام النمط بشكل أسهل.
class Client {
    // ...
    static func clientCode(subject: Subject) {
        // ...
        print(subject.request())
        // ...
    }
    // ...
}

/// لنرى كيف سيعمل كل هذا معًا.
class ProxyConceptual: XCTestCase {

    func test() {
        print("Client: Executing the client code with a real subject:")
        let realSubject = RealSubject()
        Client.clientCode(subject: realSubject)

        print("\nClient: Executing the same client code with a proxy:")
        let proxy = Proxy(realSubject)
        Client.clientCode(subject: proxy)
    }
}

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

مثال واقعي

 Example.swift: مثال واقعي

import XCTest

class ProxyRealWorld: XCTestCase {

    /// نمط تصميم الوكيل
    ///
    /// الهدف: توفير بديل أو مسؤول لكائن آخر من أجل تقييد الوصول إلى الكائن
    /// الأصلي أو لإضافة مسؤوليات أخرى.
    ///
    /// (Caching) مثال: أمثلة استخدام الوكيل لا حصر لها، لكن أشهرها قد يكون الحفظ
    /// (Delayed Initialization) وتقييد الوصول والبدء المؤجل(Logging) والتسجيل.

    func testProxyRealWorld() {

        print("Client: Loading a profile WITHOUT proxy")
        loadBasicProfile(with: Keychain())
        loadProfileWithBankAccount(with: Keychain())

        print("\nClient: Let's load a profile WITH proxy")
        loadBasicProfile(with: ProfileProxy())
        loadProfileWithBankAccount(with: ProfileProxy())
    }

    func loadBasicProfile(with service: ProfileService) {

        service.loadProfile(with: [.basic], success: { profile in
            print("Client: Basic profile is loaded")
        }) { error in
            print("Client: Cannot load a basic profile")
            print("Client: Error: " + error.localizedSummary)
        }
    }

    func loadProfileWithBankAccount(with service: ProfileService) {

        service.loadProfile(with: [.basic, .bankAccount], success: { profile in
            print("Client: Basic profile with a bank account is loaded")
        }) { error in
            print("Client: Cannot load a profile with a bank account")
            print("Client: Error: " + error.localizedSummary)
        }
    }
}

enum AccessField {

    case basic
    case bankAccount
}

protocol ProfileService {

    typealias Success = (Profile) -> ()
    typealias Failure = (LocalizedError) -> ()

    func loadProfile(with fields: [AccessField], success: Success, failure: Failure)
}

class ProfileProxy: ProfileService {

    private let keychain = Keychain()

    func loadProfile(with fields: [AccessField], success: Success, failure: Failure) {

        if let error = checkAccess(for: fields) {
            failure(error)
        } else {
            /// لاحظ:
            /// 'failure' و 'success' (closures) إلى هنا، يمكن تمرير إغلاقات 
            /// (مباشرة إلى الخدمة الأصلية (كما هي الآن
            /// (أو تُوسَّع هنا للتعامل مع نتيجة (كالحفظ مثلًا.

            keychain.loadProfile(with: fields, success: success, failure: failure)
        }
    }

    private func checkAccess(for fields: [AccessField]) -> LocalizedError? {
        if fields.contains(.bankAccount) {
            switch BiometricsService.checkAccess() {
            case .authorized: return nil
            case .denied: return ProfileError.accessDenied
            }
        }
        return nil
    }
}

class Keychain: ProfileService {

    func loadProfile(with fields: [AccessField], success: Success, failure: Failure) {

        var profile = Profile()

        for item in fields {
            switch item {
            case .basic:
                let info = loadBasicProfile()
                profile.firstName = info[Profile.Keys.firstName.raw]
                profile.lastName = info[Profile.Keys.lastName.raw]
                profile.email = info[Profile.Keys.email.raw]
            case .bankAccount:
                profile.bankAccount = loadBankAccount()
            }
        }

        success(profile)
    }

    private func loadBasicProfile() -> [String : String] {
        /// تحصل على هذه الحقول من ذاكرة آمنة.
        return [Profile.Keys.firstName.raw : "Vasya",
                Profile.Keys.lastName.raw : "Pupkin",
                Profile.Keys.email.raw : "vasya.pupkin@gmail.com"]
    }

    private func loadBankAccount() -> BankAccount {
        /// تحصل على هذه الحقول من ذاكرة آمنة.
        return BankAccount(id: 12345, amount: 999)
    }
}

class BiometricsService {

    enum Access {
        case authorized
        case denied
    }

    static func checkAccess() -> Access {
        /// تستخدم الخدمة بصمة الوجه أو الإصبع أو كلمة مرور عادية لتحديد ما إن كان
        /// المستخدم الحالي هو مالك الجهاز.

        /// دعنا نفترض أنه في مثالنا أن المستخدم نسي كلمة المرور.
    }
}

struct Profile {

    enum Keys: String {
        case firstName
        case lastName
        case email
    }

    var firstName: String?
    var lastName: String?
    var email: String?

    var bankAccount: BankAccount?
}

struct BankAccount {

    var id: Int
    var amount: Double
}

enum ProfileError: LocalizedError {

    case accessDenied

    var errorDescription: String? {
        switch self {
        case .accessDenied:
            return "Access is denied. Please enter a valid password"
        }
    }
}

extension RawRepresentable {

    var raw: Self.RawValue {
        return rawValue
    }
}

extension LocalizedError {

    var localizedSummary: String {
        return errorDescription ?? ""
    }
}

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

Client: Loading a profile WITHOUT proxy
Client: Basic profile is loaded
Client: Basic profile with a bank account is loaded

Client: Let's load a profile WITH proxy
Client: Basic profile is loaded
Client: Cannot load a profile with a bank account
Client: Error: Access is denied. Please enter a valid password

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

المستوى: ★ ★ ☆

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

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

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

مثال تصوري

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

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

 index.ts: مثال تصوري

/**
 * The Subject interface declares common operations for both RealSubject and the
 * Proxy. As long as the client works with RealSubject using this interface,
 * you'll be able to pass it a proxy instead of a real subject.
 */
interface Subject {
    request(): void;
}

/**
 * The RealSubject contains some core business logic. Usually, RealSubjects are
 * capable of doing some useful work which may also be very slow or sensitive -
 * e.g. correcting input data. A Proxy can solve these issues without any
 * changes to the RealSubject's code.
 */
class RealSubject implements Subject {
    public request(): void {
        console.log('RealSubject: Handling request.');
    }
}

/**
 * The Proxy has an interface identical to the RealSubject.
 */
class Proxy implements Subject {
    private realSubject: RealSubject;

    /**
     * The Proxy maintains a reference to an object of the RealSubject class. It
     * can be either lazy-loaded or passed to the Proxy by the client.
     */
    constructor(realSubject: RealSubject) {
        this.realSubject = realSubject;
    }

    /**
     * The most common applications of the Proxy pattern are lazy loading,
     * caching, controlling the access, logging, etc. A Proxy can perform one of
     * these things and then, depending on the result, pass the execution to the
     * same method in a linked RealSubject object.
     */
    public request(): void {
        if (this.checkAccess()) {
            this.realSubject.request();
            this.logAccess();
        }
    }

    private checkAccess(): boolean {
        // Some real checks should go here.
        console.log('Proxy: Checking access prior to firing a real request.');

        return true;
    }

    private logAccess(): void {
        console.log('Proxy: Logging the time of request.');
    }
}

/**
 * The client code is supposed to work with all objects (both subjects and
 * proxies) via the Subject interface in order to support both real subjects and
 * proxies. In real life, however, clients mostly work with their real subjects
 * directly. In this case, to implement the pattern more easily, you can extend
 * your proxy from the real subject's class.
 */
function clientCode(subject: Subject) {
    // ...

    subject.request();

    // ...
}

console.log('Client: Executing the client code with a real subject:');
const realSubject = new RealSubject();
clientCode(realSubject);

console.log('');

console.log('Client: Executing the same client code with a proxy:');
const proxy = new Proxy(realSubject);
clientCode(proxy);

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

Client: Executing the client code with a real subject:
RealSubject: Handling request.

Client: Executing the same client code with a proxy:
Proxy: Checking access prior to firing a real request.
RealSubject: Handling request.
Proxy: Logging the time of request.

انظر أيضًا

مصادر

توثيق نمط الوكيل في موقع refactoring.guru.