سلام. میتونید برای آشنایی با الگوهای طراحی (یا همون دیزاین پترن های) زبان جاوا و دیدن لیست کامل مقالاتِ آموزشیشون، به مقاله ی یک فنجان جاوا - دیزاین پترن ها Design Patterns مراجعه کنید.
(همونطور که گفته شده این الگو زیرمجموعه ی الگوهای ساختاری (Structural) هست)
از وقتی که یادمون میاد، یا مارو تحریم کردن، یا خودمون فیلتر کردیم، یا فیلترمون کردن، یا تحریمشون کردیم، یا هرطور دیگه ای که میشه نگاهش کرد، ماهیتِ اصلیش طوری بوده که مداوم مجبوریم برای دسترسی به یسری مطالب و محتواها و سایت ها، متوسل بشیم به استفاده از پروکسی و وی-پی-ان. الگوی طراحیِ پروکسی توی برنامه نویسی شئ-گرا هم تشابه زیادی داره به اتفاقی که موقع پروکسی زدن رخ میده. همونطور که از عکس بالا هم مشخصه، ما برای ارتباط با کلاس نهاییمون میایم یه واسطه یا همون پروکسی تعریف میکنیم، اگه کاری با کلاس داشتیم کارمون رو به پروکسی میگیم و از پروکسی هم جواب میگیریم.
باید اعتراف کنم این الگو جزءِ جالبترین الگوهایی بوده که من باهاشون آشنا شدم، چون هم درکِ مفهومش راحته، هم مثالهاش مختصر هستن، هم کاربردیه و انعطاف زیادی داره، هم بعد از خوندنِ این مقاله احتمال زیاد کاربردهای زیادی (چه از کدهای قبلیتون چه در آینده و حین کد زدن) به ذهنتون خواهد رسید.
این الگو مثل دست انداز میمونه، اگه جایی که لازمه استفاده بشه، میتونه حتی جونِ بعضیارو نجات بده، و اگه جای بدی بکار گرفته بشه میتونه موجب تصادف شه و هزینه های الکیِ دیگه ای مثلِ کندیِ ارتباط یا موارد مشابه ایجاد کنه. بصورت کلی الگوی پروکسی میتونه مزایا و کاربردهای زیر رو داشته باشه:
بریم سراغ یه کار لذت بخش، یعنی تمرین و کد زدن. ما که با اینترنت و فیلتر و تحریم شروع کردیم، میخوایم به مثالی در ارتباط با همین مفهوم ببینیم (میخوایم برای دسترسیِ کارمندای یه شرکت به اینترنت برنامه بنویسیم). یه چیزی که توی این الگو مهمه، استفاده از یه اینترفیسه، پس کلاس زیر رو تعریف میکنیم:
public interface OfficeInternetAccess { public void grantInternetAccess(); }
کلاس بالا فقط قراره یه دسترسی برای ارتباط با اینترنت برقرار کنه، حالا دو تا نکته مطرحه، اینکه ما واقعاً به اینترنت دسترسی داریم یا نه، یا اینکه میخوایم به کسی که درخواست کرده دسترسی بدیم یا نه (به هر دلیلی، مثل پر بودن ظرفیت شبکمون یا هر مورد دیگه ای). پس دو تا کلاس لازم داریم، کلاس اول رو بصورت زیر پیاده سازی میکنیم:
public class RealInternetAccess implements OfficeInternetAccess { private String employeeName; public RealInternetAccess(String empName) { this.employeeName = empName; } @Override public void grantInternetAccess() { System.out.println("Internet Access granted for employee: "+ employeeName); } }
کلاس بالا اینترفیس OfficeInternetAccess رو implements کرده پس باید تابع grantInternetAccess رو پیاده سازی بکنه، و اینجا ما فقط داریم یه لاگ میندازیم که بگیم برای فلان کارمند داریم دسترسی به اینترنت میدیم. حالا کلاس دوم رو ببینیم:
public class ProxyInternetAccess implements OfficeInternetAccess { private String employeeName; private RealInternetAccess realaccess; public ProxyInternetAccess(String employeeName) { this.employeeName = employeeName; } @Override public void grantInternetAccess() { if (getRole(employeeName) > 2) { realaccess = new RealInternetAccess(employeeName); realaccess.grantInternetAccess(); } else { System.out.println("No Internet access granted."); } } public int getRole(String emplName) { // Check role from the database based on Name and designation // return job level or job designation. return 3; } }
کلاس بالا که اسمش رو گذاشتیم ProxyInternetAccess (همون کلاسی که قرار به عنوان نقش واسط یا پروکسی عمل کنه)، برای دسترسی دادن به اینترنت (تابع grantInternetAccess) اول به کمک تابع getRole (که فعلا استاتیک برمیگردونه ولی میتونه توی ارتباط با دیتابیس یه به هر نوع دیگه ای تشخیص بده چه عددی برگردونه) چک میکنه نقش کارمندی که درخواست داده چیه، با فرض اینکه نقش ها (یا همون سطح دسترسی ها مثل مهمان، کارمند معمولی، مدیر و غیره) بصورت عدد توی دیتابیس ذخیره شده باشن (فارق از اینکه خوبه یا بد، صرفاً مثاله دیگه)، با خط زیر
if (getRole(employeeName) > 2)
در صورتی که سطح دسترسی قابل قبول نباشه که هیچی، ولی اگه اوکی باشه، داخل بدنه ی شرط بالا، میاد یه شئ RealInternetAccess جدید برای کارمند فعلی میسازه و تابع grantInternetAccessش رو صدا میزنه (دلیل هم میتونه این باشه که ما توی مثالمون، داخل کلاس RealInternetAccess برای دسترسی دادن فقط لاگ انداختیم، ولی میتونیم خود دسترسی به اینترنت یا مسائل دیگه رو هم در صورت نیاز بررسی کنیم).
به همین راحتی ما یه مثال از الگوی پروکسی رو بررسی کردیم، و بصورت زیر میشه امتحانش کرد:
public class ProxyPatternClient { public static void main(String[] args) { OfficeInternetAccess access = new ProxyInternetAccess("Ghodsian"); access.grantInternetAccess(); } }
توی مثال بالا حالتی رو دیدیم که میتونستیم سطح دسترسیِ درخواست دهنده به چیزی رو بررسی و در صورت نیاز محدود کنیم، میشه شاخ و برگش هم داد، مثلا برای هر کاربر تعداد محدودی دسترسی در نظر گرفت.
بریم سراغ یه مثال دیگه، میخوایم بریم فضا، ولی برای فضا رفتن بصورت فیزیکی و بدون استفاده از هر گونه مواد روان گردان، به یه سفینه فضایی نیاز داریم، و با فرض اینکه با این همه پیشرفت علم، بازم لازمه حدأقل یه نفر نیروی انسانی به عنوان مسئول داخل سفینمون باشه، خلبان هم میخوایم. برای پیاده سازیِ این مورد، از اینترفیس زیر شروع میکنیم:
interface Spaceship { public void fly(); }
و کلاسِ سفینه:
public class MillenniumFalcon implements Spaceship { @Override public void fly() { System.out.println("Welcome. The Millennium Falcon is starting up its engines!"); } }
و کلاسِ خلبان:
public class Pilot { private String name; // Constructor, Getters, and Setters }
توضیح خاصی فکر نکنم نیاز داشته باشن دو تا کلاش بالا، فقط خیلی خلاصه، تابع fly از کلاس MillenniumFalcon میگه که سفینه میخواد حرکت کنه، و کلاس Pilot هم که فقط یه اسم خلبان داره و توابع سازنده و گِتِر و سِتِرش رو دیگه خودتون در نظر بگیرید.
حالا میرسیم به اصلِ کار، یعنی پیاده سازیِ کلاسی که برامون کارِ پروکسی رو انجام بده:
public class MillenniumFalconProxy implements Spaceship { private Pilot pilot; private Spaceship falcon; public MillenniumFalconProxy(Pilot pilot) { this.pilot = pilot; this.falcon = new MillenniumFalcon(); } @Override public void fly() { String pilotName = pilot.getName(); if (pilotName .equals("Mohammad Ghodsian")) { falcon.fly(); } else { System.out.printf("Sorry %s, only Mohammad Ghodsian can fly the Falcon!\n", pilotName); } } }
کلاس بالا، از یه طرف چون اینترفیس Spaceship رو implement کرده، باید تابع fly رو پیاده سازی کنه، و داخلش چک میکنه که این خلبانی که بهش داده شده، میتونه پرواز کنه یا نه، یه چیزی تو مایه های سطح دسترسی یا اجازه ی انجام عملیات، و اینجا بصورت استاتیک خلبان بررسی میشه ولی مشخصه که به طرق مختلف میشه این عملیات رو انجام داد. برای بررسی کدمون، تستِ زیر رو مینویسیم:
public static void main(String[] args) { Spaceship falcon1 = new MillenniumFalconProxy(new Pilot("Mohammad Ghodsian")); falcon1.fly(); Spaceship falcon2 = new MillenniumFalconProxy(new Pilot("Naghi Mamouli")); falcon2.fly(); }
خب، اول به سفینه گفتیم خلبانت Mohammad Ghodsian هست، و چون من فقط به خودم و بصورتِ کاملاً استاتیک داخل کد اجازه ی پرواز دادم، با صدا زدنِ falcon1.fly پرواز انجام میشه، اما هر کسِ دیگه ای مثلاً Naghi Mamouli اجازه ی پرواز نداره، پس خروجی کد بصورت زیر میشه:
Welcome. The Millennium Falcon is starting up its engines! Sorry Naghi Mamouli, only Mohammad Ghodsian can fly the Falcon!
ما با مفهوم الگوی پروکسی آشنا شدیم، دو تا مثال هم از دیدیم. توی هر دو مثال قطعاً حالات بهینه تری هم برای پیاده سازی وجود داشته، ولی خب هدف، آشنایی با الگوی پروکسی بود و اگه میخواستیم زیاد وارد پیاده سازی بهینه تر بشیم، مثال هامون طولانی تر میشدن و به این راحتی شاید قابل درک نمیشدن شاید.
اگه بخوایم یه جمع بندی هم داشته باشیم، با پیاده سازی این الگو میتونیم امنیت (با هر تعریفی) رو بالاتر ببریم و کلاس ها و اشیائی که ممکنه برامون خطرساز بشن رو بیشتری بتونیم کنترل کنیم. از طرفی برای استفاده از منابع هم میتونه کاربردی باشه. همچنین از نظر پرفورمنس هم مثلاً گاهی اوقات که بعضی اشیاء از نظر حافظه و زمان اجرا ممکنه اذیت کننده باشن، میتونیم اونها یا دسترسی بهشون رو محدوتر کنیم تا بتونیم فقط در صورت نیاز باهاشون ارتباط برقرار کنیم و از دسترسی های غیر ضروری هم جلوگیری کنیم. البته پرفورمنس میتونه نقطه ضعف هم باشه، مثلِ مشکلاتی که ممکنه توی حافظه و نگهداری بی موردِ اشیاء رخ بده، یا اوقاتی که کلاینت از عملیاتِ داخلِ پروکسی که قراره سربارِ زیادی داشته باشه، اطلاعِ خاصی نداره، یا اطلاع هم داره ولی مهم نیست براش و هی پردازش بندازه سمت پروکسی. خلاصه بسته به جایی که استفاده میشه میتونه هم کار-راه-بنداز باشه هم در-چاه-بنداز.
منتشر شده در ویرگول توسط محمد قدسیان https://virgool.io/@mohammad.ghodsian