CodeGym /وبلاگ جاوا /Random-FA /الگوی طراحی پروکسی
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 "";
   }
}
هر بار که جدول زمانی قطار را دریافت می کنید، برنامه یک فایل را از روی دیسک می خواند. اما این تنها شروع مشکلات ماست. هر بار که جدول زمانی حتی یک قطار را دریافت می کنید، کل فایل خوانده می شود! خوب است که چنین کدهایی فقط در نمونه هایی از کارهایی که نباید انجام دهید وجود دارد :) کلاس Client:
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