CodeGym /مدونة جافا /Random-AR /نمط تصميم الوكيل
John Squirrels
مستوى
San Francisco

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

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

لماذا تحتاج إلى وكيل؟

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

مثال 1

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

مثال 2

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

المبدأ الكامن وراء نمط التصميم

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

ما هي المهام الأفضل للوكيل؟

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

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

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

نمط الوكيل في الممارسة العملية

لننفذ نظامًا يقرأ جداول مواعيد القطارات من القرص الصلب:

public interface TrainTimetable {
   String[] getTimetable();
   String getTrainDepartureTime();
}
هذا هو الفصل الذي ينفذ الواجهة الرئيسية:

public class ElectricTrainTimetable implements TrainTimetable {

   @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 TimetableDisplay {
   private TrainTimetable trainTimetable = new ElectricTrainTimetable();

   public void printTimetable() {
       String[] timetable = trainTimetable.getTimetable();
       String[] tmpArr;
       System.out.println("Train\\tFrom\\tTo\\t\\tDeparture time\\tArrival time\\tTravel time");
       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;London;Prague;13:43;21:15;07:32
BA-1404;Paris;Graz;14:25;21:25;07:00
9B-8710;Prague;Vienna;04:48;08:49;04:01;
9B-8122;Prague;Graz;04:48;08:49;04:01
دعونا اختبار ذلك:

public static void main(String[] args) {
   TimetableDisplay timetableDisplay = new timetableDisplay();
   timetableDisplay.printTimetable();
}
انتاج:

Train  From  To  Departure time  Arrival time  Travel time
9B-6854  London  Prague  13:43  21:15  07:32
BA-1404  Paris  Graz  14:25  21:25  07:00
9B-8710  Prague  Vienna  04:48  08:49  04:01
9B-8122  Prague  Graz  04:48  08:49  04:01
الآن دعونا نتعرف على الخطوات المطلوبة لتقديم نمطنا:
  1. حدد واجهة تسمح باستخدام الوكيل بدلاً من الكائن الأصلي. في مثالنا هذا TrainTimetable.

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

    إليك فئة الوكيل الخاصة بنا:

    
    public class ElectricTrainTimetableProxy implements TrainTimetable {
       // Reference to the original object
       private TrainTimetable trainTimetable = new ElectricTrainTimetable();
      
       private String[] timetableCache = null
    
       @Override
       public String[] getTimetable() {
           return trainTimetable.getTimetable();
       }
    
       @Override
       public String getTrainDepartureTime(String trainId) {
           return trainTimetable.getTrainDepartureTime(trainId);
       }
      
       public void clearCache() {
           trainTimetable = null;
       }
    }

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

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

    
    public class ElectricTrainTimetableProxy implements TrainTimetable {
       // Reference to the original object
       private TrainTimetable trainTimetable = new ElectricTrainTimetable();
    
       private String[] timetableCache = null
    
       @Override
       public String[] getTimetable() {
           if (timetableCache == null) {
               timetableCache = trainTimetable.getTimetable();
           }
           return timetableCache;
       }
    
       @Override
       public String getTrainDepartureTime(String trainId) {
           if (timetableCache == null) {
               timetableCache = trainTimetable.getTimetable();
           }
           for (int i = 0; i < timetableCache.length; i++) {
               if (timetableCache[i].startsWith(trainId+";")) return timetableCache[i];
           }
           return "";
       }
    
       public void clearCache() {
           trainTimetable = null;
       }
    }

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

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

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

  4. في تعليمات العميل البرمجية، قم بإنشاء كائن وكيل بدلاً من الكائن الأصلي:

    
    public class TimetableDisplay {
       // Changed reference
       private TrainTimetable trainTimetable = new ElectricTrainTimetableProxy();
    
       public void printTimetable() {
           String[] timetable = trainTimetable.getTimetable();
           String[] tmpArr;
           System.out.println("Train\\tFrom\\tTo\\t\\tDeparture time\\tArrival time\\tTravel time");
           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]);
           }
       }
    }

    يفحص

    
    Train  From  To  Departure time  Arrival time  Travel time
    9B-6854  London  Prague  13:43  21:15  07:32
    BA-1404  Paris  Graz  14:25  21:25  07:00
    9B-8710  Prague  Vienna  04:48  08:49  04:01
    9B-8122  Prague  Graz  04:48  08:49  04:01

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

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

قبل أن نقول وداعا، هنا رابط مفيد

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