JavaRush /وبلاگ جاوا /Random-FA /الگوی طراحی پروکسی

الگوی طراحی پروکسی

در گروه منتشر شد
در برنامه نویسی، برنامه ریزی صحیح معماری برنامه مهم است. یک ابزار ضروری برای این الگوهای طراحی است. امروز در مورد Proxy یا به عبارت دیگر معاون صحبت خواهیم کرد.

چرا به معاون نیاز دارید؟

این الگو به حل مشکلات مرتبط با دسترسی کنترل شده به یک شی کمک می کند. ممکن است سوالی داشته باشید: "چرا به چنین دسترسی کنترل شده ای نیاز داریم؟" بیایید به چند موقعیت نگاه کنیم که به شما کمک می کند بفهمید چیست.

مثال 1

بیایید تصور کنیم که یک پروژه بزرگ با یک دسته کد قدیمی داریم که در آن یک کلاس مسئول دانلود گزارش ها از پایگاه داده است. کلاس به صورت همزمان کار می کند، یعنی کل سیستم بیکار است در حالی که پایگاه داده درخواست را پردازش می کند. به طور متوسط، یک گزارش در 30 دقیقه تولید می شود. به دلیل این ویژگی، آپلود آن از ساعت 00:30 شروع می شود و مدیریت این گزارش را صبح دریافت می کند. در طول تجزیه و تحلیل، مشخص شد که دریافت گزارش بلافاصله پس از تولید، یعنی ظرف یک روز ضروری است. برنامه ریزی مجدد زمان شروع غیرممکن است، زیرا سیستم منتظر پاسخ از پایگاه داده خواهد بود. راه حل این است که اصل عملیات را با شروع آپلود و تولید گزارش در یک موضوع جداگانه تغییر دهید. این راه حل به سیستم اجازه می دهد تا به طور معمول کار کند و مدیریت گزارش های تازه ای دریافت خواهد کرد. با این حال، یک مشکل وجود دارد: کد فعلی نمی تواند بازنویسی شود، زیرا توابع آن توسط سایر بخش های سیستم استفاده می شود. در این حالت، می‌توانید با استفاده از الگوی معاون، یک کلاس پروکسی متوسط ​​را معرفی کنید که درخواست آپلود گزارش، ثبت زمان شروع و راه‌اندازی یک موضوع جداگانه را دریافت می‌کند. وقتی گزارش تولید شد، تاپیک کار خود را کامل می کند و همه خوشحال خواهند شد.

مثال 2

تیم توسعه یک وب سایت پوستر ایجاد می کند. برای به دست آوردن اطلاعات در مورد رویدادهای جدید، آنها به یک سرویس شخص ثالث مراجعه می کنند که تعامل با آن از طریق یک کتابخانه بسته ویژه اجرا می شود. در طول توسعه، یک مشکل به وجود آمد: یک سیستم شخص ثالث یک بار در روز داده ها را به روز می کند و هر بار که کاربر صفحه را به روز می کند، درخواستی برای آن رخ می دهد. این تعداد زیادی درخواست ایجاد می کند و سرویس دیگر پاسخ نمی دهد. راه حل این است که پاسخ سرویس را ذخیره کنید و نتیجه ذخیره شده را در هر راه اندازی مجدد به بازدیدکنندگان ارائه دهید و در صورت نیاز این کش را به روز کنید. در این مورد، استفاده از الگوی معاون یک راه حل عالی بدون تغییر عملکرد تمام شده است.

نحوه کار الگو

برای پیاده سازی این الگو، باید یک کلاس پروکسی ایجاد کنید. این رابط کلاس سرویس را پیاده سازی می کند و رفتار خود را برای کد مشتری شبیه سازی می کند. بنابراین، به جای شی واقعی، مشتری با پروکسی خود تعامل می کند. به طور معمول، تمام درخواست‌ها به کلاس سرویس ارسال می‌شوند، اما با اقدامات اضافی قبل یا بعد از فراخوانی آن. به عبارت ساده، این شی پراکسی یک لایه بین کد مشتری و شی هدف است. بیایید به نمونه ای از کش کردن یک درخواست از یک دیسک قدیمی بسیار کند نگاه کنیم. بگذارید یک برنامه قطار برقی در برخی از برنامه های باستانی باشد که اصل عملکرد آن قابل تغییر نیست. دیسک با برنامه به روز شده هر روز در یک زمان ثابت درج می شود. بنابراین ما داریم:
  1. رابط TimetableTrains.
  2. کلاسی TimetableElectricTrainsکه این رابط را پیاده سازی می کند.
  3. از طریق این کلاس است که کد مشتری با سیستم فایل دیسک تعامل می کند.
  4. کلاس مشتری DisplayTimetable. روش آن printTimetable()از روش های TimetableElectricTrains.
این طرح ساده است: الگوی طراحی پروکسی - 2در حال حاضر، هر بار که یک متد فراخوانی می شود، printTimetable()کلاس TimetableElectricTrainsبه دیسک دسترسی پیدا می کند، داده ها را تخلیه می کند و در اختیار مشتری قرار می دهد. این سیستم به خوبی کار می کند، اما بسیار کند است. بنابراین تصمیم گرفته شد با افزودن مکانیزم ذخیره سازی، عملکرد سیستم را افزایش دهیم. این کار را می توان با استفاده از الگوی Proxy انجام داد: الگوی طراحی پروکسی - 3به این ترتیب کلاس DisplayTimetableحتی متوجه نمی شود که با کلاس تعامل دارد TimetableElectricTrainsProxyنه با کلاس قبلی. پیاده سازی جدید برنامه را یک بار در روز بارگذاری می کند و در صورت درخواست های مکرر، شیء بارگذاری شده قبلی را از حافظه برمی گرداند.

برای چه کارهایی بهتر است از Proxy استفاده کنیم؟

در اینجا چند موقعیت وجود دارد که در آنها این الگو قطعا مفید خواهد بود:
  1. ذخیره سازی.
  2. اجرای تنبل به اجرای تنبل نیز معروف است. چرا یک شی را به یکباره بارگذاری کنید در حالی که می توانید آن را در صورت نیاز بارگذاری کنید؟
  3. ثبت درخواست ها
  4. داده های موقت و بررسی های دسترسی.
  5. راه اندازی رشته های پردازش موازی.
  6. ضبط یا شمارش سابقه تماس.
موارد استفاده دیگری نیز وجود دارد. با درک اصل عملکرد این الگو، خودتان می توانید یک برنامه موفق برای آن پیدا کنید. در نگاه اول معاون همان کاری را که نما انجام می دهد انجام می دهد ، اما اینطور نیست. پروکسی رابطی مشابه با شیء سرویس دارد . همچنین، الگو را با دکوراتور یا آداپتور اشتباه نگیرید . 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