زمان تخمینی مطالعه: 10 دقیقه
الگوی Proxy یک الگوی طراحی ساختاری است که به برنامه نویس امکان میدهد یک جایگزین یا مکان نگهدار(Placeholder) برای یک شی دیگر تهیه کنید. یک پروکسی دسترسی به شی اصلی را کنترل کرده و به شما امکان میدهد قبل یا بعد از ارسال درخواست به شی اصلی، کاری را انجام دهید.
بیان مسئله: خوب اطلا چرا باید دسترسی به یک شی را کنترل کنیم؟ اجازه دهید با مثالی شرح دهیم: شما یک شی عظیم دارید که مقدار زیادی از منابع سیستم را مصرف میکند. در طول برنامه خود شما هر از گاهی به آن نیاز دارید، اما نه همیشه.
میتوانید مقداردهی اولیه تنبل(lazy) را پیادهسازی کنید: شی را فقط زمانی ایجاد کنید که واقعاً مورد نیاز است. همه کلاینتهای شی باید مقداری کد اولیه معوق را اجرا کنند. متأسفانه، این احتمالاً باعث تکرار کدهای زیادی میشود. در یک دنیای ایده آل، ما میخواهیم این کد را مستقیماً در کلاس شیء خود قرار دهیم، اما این همیشه ممکن نیست. به عنوان مثال، کلاس ممکن است بخشی از یک کتابخانه شخص ثالث بسته باشد.
الگوی Proxy پیشنهاد میکند که یک کلاس Proxy جدید با اینترفیس مشابه یک شیء سرویس اصلی ایجاد کنید. سپس برنامه خود را بهروزرسانی میکنید تا شیء پراکسی را به همه کلاینتهای شی اصلی ارسال کند. به محض دریافت درخواست از مشتری، پروکسی یک شیء سرویس واقعی ایجاد میکند و تمام کارها را به آن محول میکند.
اما این تکنیک چه سودی خواهد داشت؟ اگر لازم است چیزی را قبل یا بعد از منطق اولیه کلاس اجرا کنید، پروکسی به شما اجازه میدهد این کار را بدون تغییر آن کلاس انجام دهید. از آنجایی که پروکسی همان اینترفیس کلاس اصلی را پیادهسازی میکند، میتوان آن را به هر کلاینتی که انتظار یک شیء سرویس واقعی را دارد، ارسال کرد.
نمونه قابل قیاس در دنیای واقعی
کارت اعتباری یک پروکسی برای یک حساب بانکی است، که پروکسی برای یک بسته پول نقد است. هر دو یک اینترفیس را پیادهسازی میکنند و آن این است که میتوان از آنها برای پرداخت استفاده کرد. یک مصرف کننده احساس خوبی دارد زیرا نیازی به حمل مقدار زیادی پول نقد وجود ندارد. صاحب مغازه نیز خوشحال است زیرا درآمد حاصل از تراکنش به صورت الکترونیکی به حساب بانکی مغازه اضافه میشود بدون اینکه خطر از دست دادن سپرده یا سرقت در راه بانک را تهدید کند.
ساختار الگوی Proxy
در این بخش به بررسی ساختار الگوی پروکسی(Proxy) میپردازیم و پیادهسازی آن را به صورت UML خواهیم دید.
- گام 1: Service Interface، اینترفیس سرویس را تعریف میکند. پروکسی باید این اینترفیس را دنبال کند تا بتواند خود را به عنوان یک شیء سرویس پنهان کند.
- گام 2: Service کلاسی است که منطق تجاری مفیدی را ارائه میدهد.
- گام 3: کلاس Proxy دارای یک فیلد مرجع است که به یک شیء سرویس اشاره میکند. پس از اینکه پروکسی پردازش خود را به پایان رساند (به عنوان مثال، تنظیم اولیه تنبل، ورود به سیستم، کنترل دسترسی، ذخیرهسازی و غیره)، درخواست را به شی سرویس ارسال میکند. معمولاً پراکسیها چرخه حیات کامل اشیاء سرویس خود را مدیریت میکنند.
- گام 4: Client باید با هر دو سرویس و پروکسی از طریق یک اینترفیس کار کند. به این ترتیب میتوانید یک پروکسی را به هر کدی که انتظار یک شیء سرویس را دارد ارسال کنید.
شبه کد(Pseudocode)
این مثال نشان میدهد که چگونه الگوی Proxy میتواند به معرفی تنظیم اولیه تنبل(lazy initialization) و ذخیرهسازی پنهان به یک کتابخانه یکپارچه YouTube شخص ثالث کمک کند.
این کتابخانه، کلاس دانلود ویدیو را در اختیار ما قرار میدهد. با این حال، بسیار ناکارآمد است. اگر برنامه سرویس گیرنده چندین بار یک ویدیو را درخواست کند، کتابخانه به جای ذخیره کردن و استفاده مجدد از اولین فایل دانلود شده، آن را بارها و بارها دانلود میکند. کلاس پروکسی همان اینترفیس دانلود کننده اصلی را پیادهسازی میکند و تمام کار را به آن واگذار میکند. با این حال، فایلهای دانلود شده را ردیابی کرده و زمانی که برنامه چندین بار یک ویدیو را درخواست میکند، نتیجه ذخیرهشده را برمیگرداند.
// The interface of a remote service.
interface ThirdPartyYouTubeLib is
method listVideos()
method getVideoInfo(id)
method downloadVideo(id)
// The concrete implementation of a service connector. Methods
// of this class can request information from YouTube. The speed
// of the request depends on a user's internet connection as
// well as YouTube's. The application will slow down if a lot of
// requests are fired at the same time, even if they all request
// the same information.
class ThirdPartyYouTubeClass implements ThirdPartyYouTubeLib is
method listVideos() is
// Send an API request to YouTube.
method getVideoInfo(id) is
// Get metadata about some video.
method downloadVideo(id) is
// Download a video file from YouTube.
// To save some bandwidth, we can cache request results and keep
// them for some time. But it may be impossible to put such code
// directly into the service class. For example, it could have
// been provided as part of a third party library and/or defined
// as `final`. That's why we put the caching code into a new
// proxy class which implements the same interface as the
// service class. It delegates to the service object only when
// the real requests have to be sent.
class CachedYouTubeClass implements ThirdPartyYouTubeLib is
private field service: ThirdPartyYouTubeLib
private field listCache, videoCache
field needReset
constructor CachedYouTubeClass(service: ThirdPartyYouTubeLib) is
this.service = service
method listVideos() is
if (listCache == null || needReset)
listCache = service.listVideos()
return listCache
method getVideoInfo(id) is
if (videoCache == null || needReset)
videoCache = service.getVideoInfo(id)
return videoCache
method downloadVideo(id) is
if (!downloadExists(id) || needReset)
service.downloadVideo(id)
// The GUI class, which used to work directly with a service
// object, stays unchanged as long as it works with the service
// object through an interface. We can safely pass a proxy
// object instead of a real service object since they both
// implement the same interface.
class YouTubeManager is
protected field service: ThirdPartyYouTubeLib
constructor YouTubeManager(service: ThirdPartyYouTubeLib) is
this.service = service
method renderVideoPage(id) is
info = service.getVideoInfo(id)
// Render the video page.
method renderListPanel() is
list = service.listVideos()
// Render the list of video thumbnails.
method reactOnUserInput() is
renderVideoPage()
renderListPanel()
// The application can configure proxies on the fly.
class Application is
method init() is
aYouTubeService = new ThirdPartyYouTubeClass()
aYouTubeProxy = new CachedYouTubeClass(aYouTubeService)
manager = new YouTubeManager(aYouTubeProxy)
manager.reactOnUserInput()
قابلیتها و کاربردها
راههای زیادی برای استفاده از الگوی Proxy وجود دارد. در این مجال بیایید به محبوبترین کاربردهای آن بپردازیم:
- مقداردهی اولیه تنبل (پراکسی مجازی). این زمانی است که شما یک شیء سرویس سنگین دارید که منابع سیستم را با فعال بودن همیشه هدر میدهد، حتی اگر هر از گاهی به آن نیاز داشته باشید. به جای ایجاد شی هنگام راهاندازی برنامه، میتوانید مقدار دهی اولیه شی را تا زمانی که واقعاً مورد نیاز است به تعویق بیندازید.
- کنترل دسترسی (پراکسی حفاظت). این زمانی است که شما میخواهید فقط مشتریان خاصی بتوانند از شی سرویس استفاده کنند. برای مثال، زمانی که اشیاء شما بخشهای حیاتی یک سیستم عامل هستند و کلاینتها برنامههای راهاندازی مختلفی هستند (از جمله برنامههای مخرب). پروکسی تنها در صورتی میتواند درخواست را به شیء سرویس ارسال کند که اعتبار مشتری با برخی از معیارها مطابقت داشته باشد.
- اجرای محلی یک سرویس راه دور (پراکسی از راه دور). این زمانی است که شیء سرویس در یک سرور راه دور قرار دارد. در این حالت، پروکسی درخواست مشتری را از طریق شبکه ارسال میکند و تمام جزئیات ناخوشایند کار با شبکه را مدیریت میکند.
- ثبت درخواست ها (پراکسی ورود به سیستم). این زمانی است که میخواهید تاریخچهای از درخواستها را برای شیء سرویس نگه دارید. پروکسی میتواند هر درخواست را قبل از ارسال آن به سرویس ثبت کند.
- ذخیره نتایج درخواست (پراکسی کش). این زمانی است که شما باید نتایج درخواستهای مشتری را کش کنید و چرخه عمر این کش را مدیریت کنید، به خصوص اگر نتایج بسیار بزرگ باشند. پروکسی میتواند برای درخواستهای تکرار شوندهای که همیشه نتایج یکسانی دارند، کش را پیادهسازی کند. پروکسی ممکن است از پارامترهای درخواستها به عنوان کلیدهای کش استفاده کند.
- مرجع هوشمند این زمانی است که شما باید بتوانید یک شی سنگین وزن را زمانی که هیچ مشتری از آن استفاده نمیکند، رد کنید. پروکسی میتواند کلاینتهایی را که به شیء سرویس یا نتایج آن ارجاع دادهاند، پیگیری کند. هر از گاهی، پروکسی ممکن است به مشتریان مراجعه کند و بررسی کند که آیا آنها هنوز فعال هستند یا خیر. اگر لیست مشتری خالی شود، پروکسی ممکن است شی سرویس را رد کند و منابع سیستم زیربنایی را آزاد کند.
پروکسی همچنین میتواند ردیابی کند که آیا مشتری شیء سرویس را تغییر داده است یا خیر. سپس اشیاء بدون تغییر ممکن است توسط مشتریان دیگر مورد استفاده مجدد قرار گیرند.
نحوه پیادهسازی
- اگر هیچ اینترفیس سرویسی از قبل موجود نیست، یک اینترفیس ایجاد کنید تا اشیاء پراکسی و سرویس قابل تعویض باشند. استخراج اینترفیس از کلاس سرویس همیشه امکان پذیر نیست، زیرا برای استفاده از آن اینترفیس ، باید همه مشتریان سرویس را تغییر دهید. پلن B این است که پروکسی را به یک زیر کلاس از کلاس سرویس تبدیل کند و به این ترتیب اینترفیس سرویس را به ارث میبرد.
- کلاس پروکسی را ایجاد کنید. باید یک فیلد برای ذخیره ارجاع به سرویس داشته باشد. معمولاً پروکسیها کل چرخه عمر سرویسهای خود را ایجاد و مدیریت میکنند. در موارد نادر، یک سرویس از طریق یک سازنده توسط مشتری به پروکسی ارسال میشود.
- متدهای پروکسی را با توجه به اهداف آنها پیادهسازی کنید. در بیشتر موارد، پس از انجام برخی کارها، پروکسی باید کار را به شیء سرویس واگذار کند.
- یک متد ایجاد را در نظر بگیرید که تصمیم میگیرد مشتری یک پروکسی یا یک سرویس واقعی دریافت کند. این میتواند یک روش استاتیک ساده در کلاس پروکسی یا یک روش Factory تمام عیار باشد.
- پیادهسازی lazy initialization را برای شی سرویس در نظر بگیرید.
مزایا و معایب الگوی Decorator
این الگوی طراحی دارای مزایا و معایبی به شرح زیر است:
– مزایا
- شما میتوانید شیء سرویس را بدون اطلاع مشتریان از آن کنترل کنید.
- شما میتوانید چرخه عمر شیء سرویس را زمانی که مشتریان به آن اهمیت نمیدهند، مدیریت کنید.
- پروکسی حتی اگر شیء سرویس آماده نباشد یا در دسترس نباشد کار میکند.
- اصل باز/بسته: شما میتوانید بدون تغییر سرویس یا مشتریان، پروکسیهای جدید تعریف کنید.
– معایب
- ممکن است کد پیچیدهتر شود زیرا شما نیاز به معرفی کلاسهای جدید دارید.
- ممکن است پاسخ سرویس به تأخیر بیفتد.
ارتباط با الگوهای دیگر
- با الگوی Adapter شما از طریق اینترفیسهای مختلف به یک شی موجود دسترسی پیدا میکنید. با پروکسی، اینترفیس ثابت میماند. با الگوی Decorator شما از طریق یک اینترفیس پیشرفته به شی دسترسی دارید.
- الگوی Facade شبیه به Proxy است که هم یک موجودیت پیچیده را بافر میکند و هم آن را به تنهایی مقداردهی اولیه میکند. بر خلاف Facade، Proxy دارای اینترفیس یکسانی با شی سرویس خود است که باعث میشود آنها قابل تعویض باشند.
- Decorator و Proxy ساختارهای مشابهی دارند، اما اهداف بسیار متفاوتی دارند. هر دو الگو بر اساس اصل ترکیببندی(composition) ساخته شدهاند، جایی که یک شی قرار است بخشی از کار را به دیگری واگذار کند. تفاوت این است که یک Proxy معمولاً چرخه عمر شیء سرویس خود را به تنهایی مدیریت میکند، در حالی که ترکیب Decorators همیشه توسط مشتری کنترل میشود.