ConstructiCat Logo
CodeBust.
Browse section ▾

الوكيل.

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

##Intent

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

نمط تصميم الوكيل

##Problem

لماذا تريد التحكم في الوصول إلى كائن ما؟ إليك مثال: لديك كائن ضخم يستهلك قدراً هائلاً من موارد النظام. تحتاجه من وقت لآخر، لكن ليس دائماً.

المشكلة التي يحلها نمط الوكيل

استعلامات قاعدة البيانات يمكن أن تكون بطيئة جداً.

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

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

##Solution

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

الحل باستخدام نمط الوكيل

يتنكر الوكيل كأنه كائن قاعدة بيانات. يمكنه التعامل مع التهيئة الكسولة وتخزين النتائج مؤقتاً دون أن يعلم العميل أو كائن قاعدة البيانات الحقيقي بذلك.

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

##Structure

Client «interface» ServiceInterface +operation() Proxy -realService: Service+Proxy(s: Service)+checkAccess()+operation() Service ...+operation() if (checkAccess()) { realService.operation()} realService = s
Client «interface» ServiceInterface +operation() Proxy -realService: Service+Proxy(s: Service)+checkAccess()+operation() Service ...+operation() if (checkAccess()) { realService.operation()} realService = s 1 2 3 4
  1. تُعلن واجهة الخدمة عن واجهة الخدمة. يجب على الوكيل اتباع هذه الواجهة ليتمكن من التنكر كـكائن خدمة.

  2. الخدمة هي فئة تُقدم بعض منطق الأعمال المفيد.

  3. تحتوي فئة الوكيل على حقل مرجعي يشير إلى كائن الخدمة. بعد أن ينهي الوكيل معالجته (مثل: التهيئة الكسولة، التسجيل، التحكم في الوصول، التخزين المؤقت، إلخ.)، يمرر الطلب إلى كائن الخدمة.

    عادةً ما يدير الوكلاء دورة الحياة الكاملة لكائنات الخدمة الخاصة بهم.

  4. يجب على العميل التعامل مع كل من الخدمات والوكلاء عبر نفس الواجهة. بهذه الطريقة يمكنك تمرير وكيل إلى أي كود يتوقع كائن خدمة.

##Pseudocode

يوضح هذا المثال كيف يمكن لنمط الوكيل المساعدة في تقديم التهيئة الكسولة والتخزين المؤقت لمكتبة تكامل YouTube من طرف ثالث.

هيكل مثال نمط الوكيل

تخزين نتائج الخدمة مؤقتاً باستخدام الوكيل.

The library provides us with the video downloading class. However, it’s very inefficient. If the client application requests the same video multiple times, the library just downloads it over and over, instead of caching and reusing the first downloaded file.

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

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

// التطبيق الملموس لموصّل الخدمة. يمكن لطرق
// هذه الفئة طلب معلومات من YouTube. تعتمد سرعة
// الطلب على اتصال المستخدم بالإنترنت
// وكذلك على YouTube. سيبطؤ التطبيق إذا أُرسلت كثير من
// الطلبات في نفس الوقت، حتى وإن طلبت جميعها
// نفس المعلومات.
class ThirdPartyYouTubeClass implements ThirdPartyYouTubeLib is
    method listVideos() is
        // إرسال طلب API إلى YouTube.

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

    method downloadVideo(id) is
        // تنزيل ملف فيديو من YouTube.

// لتوفير عرض النطاق الترددي، يمكننا تخزين نتائج الطلبات مؤقتاً والاحتفاظ
// بها لبعض الوقت. لكن قد يكون من المستحيل وضع هذا الكود
// مباشرةً في فئة الخدمة. على سبيل المثال، ربما تم
// تقديمه كجزء من مكتبة طرف ثالث و/أو تعريفه
// بـ `final`. لهذا السبب نضع كود التخزين المؤقت في
// فئة وكيل جديدة تُنفذ نفس الواجهة التي تُنفذها
// فئة الخدمة. تفوض إلى كائن الخدمة فقط عندما
// يجب إرسال الطلبات الحقيقية.
class CachedYouTubeClass implements ThirdPartyYouTubeLib is
    private field service: ThirdPartyYouTubeLib
    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)

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

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

    method renderVideoPage(id) is
        info = service.getVideoInfo(id)
        // عرض صفحة الفيديو.

    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()

##Applicability

هناك عشرات الطرق لاستخدام نمط الوكيل. دعنا نستعرض أكثر الاستخدامات شيوعاً.

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

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

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

يمكن للوكيل تمرير الطلب إلى كائن الخدمة فقط إذا تطابقت بيانات اعتماد العميل مع معايير معينة.

التنفيذ المحلي لخدمة بعيدة (الوكيل البعيد). يحدث هذا عندما يكون كائن الخدمة موجوداً على خادم بعيد.

في هذه الحالة، يُمرر الوكيل طلب العميل عبر الشبكة، متولياً معالجة جميع التفاصيل المعقدة للعمل مع الشبكة.

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

يمكن للوكيل تسجيل كل طلب قبل تمريره إلى الخدمة.

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

يمكن للوكيل تنفيذ التخزين المؤقت للطلبات المتكررة التي تُنتج دائماً نفس النتائج. قد يستخدم الوكيل معاملات الطلبات كمفاتيح للتخزين المؤقت.

المرجع الذكي. يحدث هذا عندما تحتاج إلى إمكانية إلغاء كائن ثقيل الوزن بمجرد عدم وجود عملاء يستخدمونه.

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

يمكن للوكيل أيضاً تتبع ما إذا كان العميل قد عدّل كائن الخدمة. عندها يمكن إعادة استخدام الكائنات غير المعدلة من قِبل عملاء آخرين.

##How to Implement

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

  2. أنشئ فئة الوكيل. يجب أن تحتوي على حقل لتخزين مرجع للخدمة. عادةً ما ينشئ الوكلاء دورة حياة خدماتهم بالكامل ويديرونها. في حالات نادرة، يتم تمرير الخدمة إلى الوكيل عبر المُنشئ من قِبل العميل.

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

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

  5. فكّر في تطبيق التهيئة الكسولة لكائن الخدمة.

##Pros & Cons

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

##Relations with Other Patterns

  • مع المحوِّل (Adapter) تصل إلى كائن موجود عبر واجهة مختلفة. مع الوكيل (Proxy)، تبقى الواجهة كما هي. مع المزيِّن (Decorator) تصل إلى الكائن عبر واجهة معزّزة.

  • الواجهة الخارجية (Facade) مشابهة لـ الوكيل (Proxy) في أن كليهما يُخزّن كياناً معقداً ويُهيئه بمفرده. على عكس الواجهة الخارجية، يمتلك الوكيل نفس واجهة كائن خدمته، مما يجعلهما قابلَين للتبادل.

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