JavaRush /مدونة جافا /Random-AR /نمط تصميم الوكيل

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

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

لماذا تحتاج إلى نائب؟

يساعد هذا النمط في حل المشكلات المرتبطة بالوصول المتحكم فيه إلى كائن ما. قد يكون لديك سؤال: "لماذا نحتاج إلى مثل هذا الوصول الخاضع للرقابة؟" دعونا نلقي نظرة على بعض المواقف التي ستساعدك على معرفة ما هو.

مثال 1

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

مثال 2

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

كيف يعمل النمط

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

ما هي المهام الأفضل لاستخدام الوكيل؟

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

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

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

نمط بديل في الممارسة العملية

لننفذ معك نظامًا يقرأ جداول القطارات من القرص:
public interface TimetableTrains {
   String[] getTimetable();
   String getTrainDepartureTime();
}
فئة تنفذ الواجهة الرئيسية:
public class TimetableElectricTrains implements TimetableTrains {

   @Override
   public String[] getTimetable() {
       ArrayList<String> list = new ArrayList<>();
       try {
           Scanner scanner = new Scanner(new FileReader(new File("/tmp/electric_trains.csv")));
           while (scanner.hasNextLine()) {
               String line = scanner.nextLine();
               list.add(line);
           }
       } catch (IOException e) {
           System.err.println("Error:  " + e);
       }
       return list.toArray(new String[list.size()]);
   }

   @Override
   public String getTrainDepartureTime(String trainId) {
       String[] timetable = getTimetable();
       for(int i = 0; i<timetable.length; i++) {
           if(timetable[i].startsWith(trainId+";")) return timetable[i];
       }
       return "";
   }
}
في كل مرة تحاول فيها الحصول على الجدول الزمني لجميع القطارات، يقرأ البرنامج الملف من القرص. ولكن هذه لا تزال الزهور. تتم قراءة الملف أيضًا في كل مرة تحتاج فيها إلى الحصول على الجدول الزمني لقطار واحد فقط! من الجيد أن هذا الرمز موجود فقط في الأمثلة السيئة :) فئة العميل:
public class DisplayTimetable {
   private TimetableTrains timetableTrains = new TimetableElectricTrains();

   public void printTimetable() {
       String[] timetable = timetableTrains.getTimetable();
       String[] tmpArr;
       System.out.println("Поезд\tОткуда\tКуда\t\tВремя отправления\tВремя прибытия\tВремя в пути");
       for(int i = 0; i < timetable.length; i++) {
           tmpArr = timetable[i].split(";");
           System.out.printf("%s\t%s\t%s\t\t%s\t\t\t\t%s\t\t\t%s\n", tmpArr[0], tmpArr[1], tmpArr[2], tmpArr[3], tmpArr[4], tmpArr[5]);
       }
   }
}
ملف المثال:

9B-6854;Лондон;Прага;13:43;21:15;07:32
BA-1404;Париж;Грац;14:25;21:25;07:00
9B-8710;Прага;Вена;04:48;08:49;04:01;
9B-8122;Прага;Грац;04:48;08:49;04:01
دعونا اختبار:
public static void main(String[] args) {
   DisplayTimetable displayTimetable = new DisplayTimetable();
   displayTimetable.printTimetable();
}
خاتمة:

Поезд  Откуда  Куда   Время отправления Время прибытия    Время в пути
9B-6854  Лондон  Прага    13:43         21:15         07:32
BA-1404  Париж   Грац   14:25         21:25         07:00
9B-8710  Прага   Вена   04:48         08:49         04:01
9B-8122  Прага   Грац   04:48         08:49         04:01
الآن دعونا نستعرض خطوات تنفيذ النمط الخاص بنا:
  1. حدد واجهة تسمح لك باستخدام وكيل جديد بدلاً من الكائن الأصلي. في مثالنا هو TimetableTrains.

  2. إنشاء فئة الوكيل. يجب أن يحتوي على مرجع لكائن خدمة (إنشاء في فئة أو تمرير في مُنشئ)؛

    هنا فئة الوكيل لدينا:

    public class TimetableElectricTrainsProxy implements TimetableTrains {
       // Ссылка на оригинальный an object
       private TimetableTrains timetableTrains = new TimetableElectricTrains();
    
       private String[] timetableCache = null
    
       @Override
       public String[] getTimetable() {
           return timetableTrains.getTimetable();
       }
    
       @Override
       public String getTrainDepartureTime(String trainId) {
           return timetableTrains.getTrainDepartureTime(trainId);
       }
    
       public void clearCache() {
           timetableTrains = null;
       }
    }

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

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

    public class TimetableElectricTrainsProxy implements TimetableTrains {
       // Ссылка на оригинальный an object
       private TimetableTrains timetableTrains = new TimetableElectricTrains();
    
       private String[] timetableCache = null
    
       @Override
       public String[] getTimetable() {
           if(timetableCache == null) {
               timetableCache = timetableTrains.getTimetable();
           }
           return timetableCache;
       }
    
       @Override
       public String getTrainDepartureTime(String trainId) {
           if(timetableCache == null) {
               timetableCache = timetableTrains.getTimetable();
           }
           for(int i = 0; i < timetableCache.length; i++) {
               if(timetableCache[i].startsWith(trainId+";")) return timetableCache[i];
           }
           return "";
       }
    
       public void clearCache() {
           timetableTrains = null;
       }
    }

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

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

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

  4. استبدل إنشاء الكائن الأصلي في كود العميل بكائن بديل:

    public class DisplayTimetable {
       // Измененная link
       private TimetableTrains timetableTrains = new TimetableElectricTrainsProxy();
    
       public void printTimetable() {
           String[] timetable = timetableTrains.getTimetable();
           String[] tmpArr;
           System.out.println("Поезд\tОткуда\tКуда\t\tВремя отправления\tВремя прибытия\tВремя в пути");
           for(int i = 0; i<timetable.length; i++) {
               tmpArr = timetable[i].split(";");
               System.out.printf("%s\t%s\t%s\t\t%s\t\t\t\t%s\t\t\t%s\n", tmpArr[0], tmpArr[1], tmpArr[2], tmpArr[3], tmpArr[4], tmpArr[5]);
           }
       }
    }

    فحص

    
    Поезд  Откуда  Куда   Время отправления Время прибытия    Время в пути
    9B-6854  Лондон  Прага    13:43         21:15         07:32
    BA-1404  Париж   Грац   14:25         21:25         07:00
    9B-8710  Прага   Вена   04:48         08:49         04:01
    9B-8122  Прага   Грац   04:48         08:49         04:01

    عظيم، أنه يعمل بشكل صحيح.

    يمكنك أيضًا التفكير في مصنع يقوم بإنشاء الكائن الأصلي والكائن البديل وفقًا لشروط معينة.

رابط مفيد بدلا من النقطة

  1. مقال ممتاز عن الأنماط والقليل عن "النائب"

هذا كل شيء لهذا اليوم! سيكون من الرائع العودة إلى التعلم واختبار معرفتك الجديدة عمليًا :)
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION