چرا به پروکسی نیاز دارید؟
این الگو به حل مشکلات مرتبط با دسترسی کنترل شده به یک شی کمک می کند. ممکن است بپرسید "چرا به دسترسی کنترل شده نیاز داریم؟" بیایید به چند موقعیت نگاه کنیم که به شما کمک می کند بفهمید چیست.مثال 1
تصور کنید که ما یک پروژه بزرگ با یک دسته کد قدیمی داریم که در آن یک کلاس مسئول صادرات گزارش ها از یک پایگاه داده وجود دارد. کلاس به صورت همزمان کار می کند. یعنی کل سیستم بیکار است در حالی که پایگاه داده درخواست را پردازش می کند. تولید یک گزارش به طور متوسط 30 دقیقه طول می کشد. بر این اساس فرآیند صادرات از ساعت 12:30 صبح آغاز می شود و مدیریت گزارش را صبح دریافت می کند. یک ممیزی نشان داد که بهتر است بتوانیم بلافاصله گزارش را در ساعات کاری عادی دریافت کنیم. زمان شروع را نمی توان به تعویق انداخت، و سیستم نمی تواند در حالی که منتظر پاسخ از پایگاه داده است، مسدود شود. راه حل این است که نحوه عملکرد سیستم را تغییر دهید، گزارش را در یک موضوع جداگانه تولید و صادر کنید. این راه حل به سیستم اجازه می دهد تا به طور معمول کار کند و مدیریت گزارش های تازه ای دریافت خواهد کرد. با این حال، یک مشکل وجود دارد: کد فعلی نمی تواند بازنویسی شود، زیرا سایر بخش های سیستم از عملکرد آن استفاده می کنند. در این مورد، میتوانیم از الگوی پروکسی برای معرفی یک کلاس پروکسی میانی استفاده کنیم که درخواستهایی برای صادرات گزارشها دریافت میکند، زمان شروع را ثبت میکند و یک موضوع جداگانه راهاندازی میکند. پس از ایجاد گزارش، موضوع پایان می یابد و همه خوشحال می شوند.مثال 2
یک تیم توسعه در حال ایجاد یک وب سایت رویدادها هستند. برای دریافت اطلاعات در مورد رویدادهای جدید، تیم یک سرویس شخص ثالث را درخواست می کند. یک کتابخانه خصوصی ویژه تعامل با خدمات را تسهیل می کند. در طول توسعه، یک مشکل کشف می شود: سیستم شخص ثالث اطلاعات خود را یک بار در روز به روز می کند، اما هر بار که کاربر صفحه ای را به روز می کند، درخواستی برای آن ارسال می شود. این باعث ایجاد تعداد زیادی درخواست می شود و سرویس دیگر پاسخ نمی دهد. راه حل این است که پاسخ سرویس را در حافظه پنهان نگه دارید و نتیجه ذخیره شده را در هنگام بارگذاری مجدد صفحات به بازدیدکنندگان برگردانید و در صورت نیاز حافظه پنهان را به روز کنید. در این مورد، الگوی طراحی پروکسی یک راه حل عالی است که عملکرد موجود را تغییر نمی دهد.اصل پشت الگوی طراحی
برای پیاده سازی این الگو، باید یک کلاس پروکسی ایجاد کنید. این رابط کلاس سرویس را پیاده سازی می کند و رفتار آن را برای کد مشتری تقلید می کند. به این ترتیب، مشتری به جای شی واقعی، با یک پروکسی تعامل می کند. به عنوان یک قاعده، همه درخواست ها به کلاس سرویس منتقل می شوند، اما با اقدامات اضافی قبل یا بعد. به زبان ساده، یک پروکسی یک لایه بین کد مشتری و شی هدف است. مثالی از کش کردن نتایج جستجو از یک هارد دیسک قدیمی و بسیار کند را در نظر بگیرید. فرض کنید ما در مورد یک جدول زمانی برای قطارهای الکتریکی در یک برنامه قدیمی صحبت می کنیم که منطق آن قابل تغییر نیست. یک دیسک با جدول زمانی به روز شده هر روز در یک زمان ثابت درج می شود. بنابراین، ما داریم:TrainTimetable
رابط.ElectricTrainTimetable
، که این رابط را پیاده سازی می کند.- کد مشتری از طریق این کلاس با سیستم فایل تعامل دارد.
TimetableDisplay
کلاس مشتری متد آنprintTimetable()
از متدهای کلاس استفاده می کندElectricTrainTimetable
.

printTimetable()
، ElectricTrainTimetable
کلاس به دیسک دسترسی پیدا می کند، داده ها را بارگذاری می کند و به مشتری ارائه می دهد. عملکرد سیستم خوب است، اما بسیار کند است. در نتیجه، تصمیم به افزایش عملکرد سیستم با افزودن مکانیزم ذخیره سازی گرفته شد. این را می توان با استفاده از الگوی پراکسی انجام داد: 
TimetableDisplay
با کلاس تعامل دارد . ElectricTrainTimetableProxy
اجرای جدید جدول زمانی را یک بار در روز بارگذاری می کند. برای درخواست های تکراری، شیء بارگذاری شده قبلی را از حافظه برمی گرداند.
چه وظایفی برای یک پروکسی بهتر است؟
در اینجا چند موقعیت وجود دارد که این الگو قطعاً مفید خواهد بود:- ذخیره سازی
- مقداردهی اولیه با تأخیر یا تنبلی چرا اگر بتوانید یک شی را در صورت نیاز بارگذاری کنید، فوراً بارگذاری کنید؟
- ثبت درخواست ها
- تأیید میانی داده ها و دسترسی
- شروع رشته های کارگری
- ضبط دسترسی به یک شی
مزایا و معایب
- + هر طور که بخواهید می توانید دسترسی به شیء سرویس را کنترل کنید
- + توانایی های اضافی مربوط به مدیریت چرخه زندگی شی سرویس
- + بدون شیء سرویس کار می کند
- + عملکرد و امنیت کد را بهبود می بخشد.
- - خطر بدتر شدن عملکرد به دلیل درخواست های اضافی وجود دارد
- - سلسله مراتب کلاس را پیچیده تر می کند
الگوی پروکسی در عمل
بیایید سیستمی را پیاده سازی کنیم که جدول زمانی قطارها را از روی هارد دیسک می خواند: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
حال بیایید مراحل مورد نیاز برای معرفی الگوی خود را طی کنیم:
-
رابطی را تعریف کنید که اجازه استفاده از پروکسی را به جای شی اصلی می دهد. در مثال ما، این است
TrainTimetable
. -
کلاس پروکسی را ایجاد کنید. باید ارجاعی به شیء سرویس داشته باشد (آن را در کلاس ایجاد کنید یا به سازنده ارسال کنید).
کلاس پروکسی ما اینجاست:
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; } }
در این مرحله، ما به سادگی یک کلاس با ارجاع به شی اصلی ایجاد می کنیم و همه تماس ها را به آن فوروارد می کنیم.
-
بیایید منطق کلاس پروکسی را پیاده سازی کنیم. اساساً تماس ها همیشه به شی اصلی هدایت می شوند.
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 () لازم نیست به شی اصلی هدایت شود. ما به سادگی عملکرد آن را در یک روش جدید کپی کردیم.
این کار را نکن اگر باید کد را کپی کنید یا کاری مشابه انجام دهید، مشکلی پیش آمده است و باید دوباره از زاویه دیگری به مشکل نگاه کنید. در مثال ساده خود، هیچ گزینه دیگری نداشتیم. اما در پروژه های واقعی، کد به احتمال زیاد درست تر نوشته می شود.
-
در کد مشتری، به جای شی اصلی، یک شی پراکسی ایجاد کنید:
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
عالیه درست کار میکنه
همچنین می توانید بسته به شرایط خاص، گزینه کارخانه ای را در نظر بگیرید که هم یک شی اصلی و هم یک شی پراکسی ایجاد می کند.
GO TO FULL VERSION