چرا به معاون نیاز دارید؟
این الگو به حل مشکلات مرتبط با دسترسی کنترل شده به یک شی کمک می کند. ممکن است سوالی داشته باشید: "چرا به چنین دسترسی کنترل شده ای نیاز داریم؟" بیایید به چند موقعیت نگاه کنیم که به شما کمک می کند بفهمید چیست.مثال 1
بیایید تصور کنیم که یک پروژه بزرگ با یک دسته کد قدیمی داریم که در آن یک کلاس مسئول دانلود گزارش ها از پایگاه داده است. کلاس به صورت همزمان کار می کند، یعنی کل سیستم بیکار است در حالی که پایگاه داده درخواست را پردازش می کند. به طور متوسط، یک گزارش در 30 دقیقه تولید می شود. به دلیل این ویژگی، آپلود آن از ساعت 00:30 شروع می شود و مدیریت این گزارش را صبح دریافت می کند. در طول تجزیه و تحلیل، مشخص شد که دریافت گزارش بلافاصله پس از تولید، یعنی ظرف یک روز ضروری است. برنامه ریزی مجدد زمان شروع غیرممکن است، زیرا سیستم منتظر پاسخ از پایگاه داده خواهد بود. راه حل این است که اصل عملیات را با شروع آپلود و تولید گزارش در یک موضوع جداگانه تغییر دهید. این راه حل به سیستم اجازه می دهد تا به طور معمول کار کند و مدیریت گزارش های تازه ای دریافت خواهد کرد. با این حال، یک مشکل وجود دارد: کد فعلی نمی تواند بازنویسی شود، زیرا توابع آن توسط سایر بخش های سیستم استفاده می شود. در این حالت، میتوانید با استفاده از الگوی معاون، یک کلاس پروکسی متوسط را معرفی کنید که درخواست آپلود گزارش، ثبت زمان شروع و راهاندازی یک موضوع جداگانه را دریافت میکند. وقتی گزارش تولید شد، تاپیک کار خود را کامل می کند و همه خوشحال خواهند شد.مثال 2
تیم توسعه یک وب سایت پوستر ایجاد می کند. برای به دست آوردن اطلاعات در مورد رویدادهای جدید، آنها به یک سرویس شخص ثالث مراجعه می کنند که تعامل با آن از طریق یک کتابخانه بسته ویژه اجرا می شود. در طول توسعه، یک مشکل به وجود آمد: یک سیستم شخص ثالث یک بار در روز داده ها را به روز می کند و هر بار که کاربر صفحه را به روز می کند، درخواستی برای آن رخ می دهد. این تعداد زیادی درخواست ایجاد می کند و سرویس دیگر پاسخ نمی دهد. راه حل این است که پاسخ سرویس را ذخیره کنید و نتیجه ذخیره شده را در هر راه اندازی مجدد به بازدیدکنندگان ارائه دهید و در صورت نیاز این کش را به روز کنید. در این مورد، استفاده از الگوی معاون یک راه حل عالی بدون تغییر عملکرد تمام شده است.نحوه کار الگو
برای پیاده سازی این الگو، باید یک کلاس پروکسی ایجاد کنید. این رابط کلاس سرویس را پیاده سازی می کند و رفتار خود را برای کد مشتری شبیه سازی می کند. بنابراین، به جای شی واقعی، مشتری با پروکسی خود تعامل می کند. به طور معمول، تمام درخواستها به کلاس سرویس ارسال میشوند، اما با اقدامات اضافی قبل یا بعد از فراخوانی آن. به عبارت ساده، این شی پراکسی یک لایه بین کد مشتری و شی هدف است. بیایید به نمونه ای از کش کردن یک درخواست از یک دیسک قدیمی بسیار کند نگاه کنیم. بگذارید یک برنامه قطار برقی در برخی از برنامه های باستانی باشد که اصل عملکرد آن قابل تغییر نیست. دیسک با برنامه به روز شده هر روز در یک زمان ثابت درج می شود. بنابراین ما داریم:- رابط
TimetableTrains
. - کلاسی
TimetableElectricTrains
که این رابط را پیاده سازی می کند. - از طریق این کلاس است که کد مشتری با سیستم فایل دیسک تعامل می کند.
- کلاس مشتری
DisplayTimetable
. روش آنprintTimetable()
از روش هایTimetableElectricTrains
.
printTimetable()
کلاس TimetableElectricTrains
به دیسک دسترسی پیدا می کند، داده ها را تخلیه می کند و در اختیار مشتری قرار می دهد. این سیستم به خوبی کار می کند، اما بسیار کند است. بنابراین تصمیم گرفته شد با افزودن مکانیزم ذخیره سازی، عملکرد سیستم را افزایش دهیم. این کار را می توان با استفاده از الگوی Proxy انجام داد: به این ترتیب کلاس DisplayTimetable
حتی متوجه نمی شود که با کلاس تعامل دارد TimetableElectricTrainsProxy
نه با کلاس قبلی. پیاده سازی جدید برنامه را یک بار در روز بارگذاری می کند و در صورت درخواست های مکرر، شیء بارگذاری شده قبلی را از حافظه برمی گرداند.
برای چه کارهایی بهتر است از Proxy استفاده کنیم؟
در اینجا چند موقعیت وجود دارد که در آنها این الگو قطعا مفید خواهد بود:- ذخیره سازی.
- اجرای تنبل به اجرای تنبل نیز معروف است. چرا یک شی را به یکباره بارگذاری کنید در حالی که می توانید آن را در صورت نیاز بارگذاری کنید؟
- ثبت درخواست ها
- داده های موقت و بررسی های دسترسی.
- راه اندازی رشته های پردازش موازی.
- ضبط یا شمارش سابقه تماس.
مزایا و معایب
- + شما می توانید دسترسی به شیء سرویس را همانطور که می خواهید کنترل کنید.
- + قابلیت های اضافی برای مدیریت چرخه زندگی یک شی خدمات.
- + بدون شیء سرویس کار می کند.
- + عملکرد و امنیت کد را بهبود می بخشد.
- - خطر بدتر شدن عملکرد به دلیل درمان های اضافی وجود دارد.
- - ساختار کلاس های برنامه را پیچیده می کند.
جایگزین الگو در عمل
بیایید سیستمی را با شما پیاده سازی کنیم که برنامه های قطار را از روی دیسک می خواند: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
حال بیایید مراحل پیاده سازی الگوی خود را طی کنیم:
-
رابطی را تعریف کنید که به شما امکان می دهد از یک پروکسی جدید به جای شی اصلی استفاده کنید. در مثال ما این است
TimetableTrains
. -
یک کلاس پروکسی ایجاد کنید. باید حاوی ارجاع به یک شیء سرویس باشد (ایجاد در یک کلاس یا عبور در سازنده).
کلاس پروکسی ما اینجاست:
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; } }
در این مرحله به سادگی یک کلاس با ارجاع به شی اصلی ایجاد می کنیم و همه فراخوانی ها را به آن ارسال می کنیم.
-
ما منطق کلاس پروکسی را پیاده سازی می کنیم. اساساً تماس همیشه به شی اصلی هدایت می شود.
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() لازم نیست به شی اصلی هدایت شود. ما به سادگی عملکرد آن را در یک روش جدید کپی کردیم.
شما نمی توانید این کار را انجام دهید. اگر مجبور شدید کد را کپی کنید یا دستکاری های مشابه انجام دهید، به این معنی است که مشکلی پیش آمده است و باید از زاویه دیگری به مشکل نگاه کنید. در مثال ساده ما راه دیگری وجود ندارد، اما در پروژه های واقعی، به احتمال زیاد، کد به درستی نوشته می شود.
-
ایجاد شی اصلی در کد مشتری را با یک شی جایگزین جایگزین کنید:
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
عالیه درست کار میکنه
همچنین می توانید کارخانه ای را در نظر بگیرید که بسته به شرایط خاص، هم شی اصلی و هم شی جایگزین را ایجاد می کند.
لینک مفید به جای نقطه
-
مقاله عالی در مورد الگوها و کمی در مورد "معاون"
GO TO FULL VERSION