پروکسی (Proxy Pattern)
در دنیای برنامهنویسی شیءگرا، الگوی طراحی پروکسی (Proxy Pattern) یک روش برای کنترل دسترسی به یک شیء است. پروکسی در واقع یک نماینده یا جایگزین برای یک شیء اصلی (که به آن شیء هدف گفته میشود) است. این نماینده به جای اینکه مستقیماً شیء اصلی را در معرض دید قرار دهد، خودش واسطی برای ارتباط با آن میشود. این الگو به ما امکان میدهد تا قبل یا بعد از انجام عملیات روی شیء اصلی، کارهای دیگری مانند کنترل دسترسی، لاگینگ (Logging)، یا رهگیری (Tracking) انجام دهیم، بدون اینکه کد اصلی شیء هدف را تغییر دهیم.
کلاس Proxy
از شما میخواهیم کلاس Proxy را تکمیل کنید. این کلاس باید بهعنوان نمایندهای برای هر شیء دلخواه عمل کند و دسترسی به ویژگیها و متدهای آن شیء را از طریق خودش فراهم کند، در حالی که اطلاعات مربوط به این دسترسیها را نیز ثبت و ارائه میدهد.
این متد سازندهی کلاس
Proxyاست و وظیفهی آن مقداردهی اولیهی یک شیءProxyاست. این متد یک پارامتر ورودی به نامobjدریافت میکند که همان شیء هدف است؛ شیئی که این نمونهیProxyنمایندهی آن خواهد بود.در این متد باید شیء هدف (
obj) را در یک ویژگی نمونه (مثلاًself._obj) ذخیره کنید. همچنین، باید ویژگیهای دیگری که نیاز دارید را نیز در اینجا آماده کنید.💡 راهنمایی ۱: در متد
__init__، علاوه بر ذخیرهی شیء اصلی (self._obj)، دو ویژگی کمکی برای رهگیری اضافه کنید:_last_accessed: یک متغیر (رشته) برای ذخیرهی نام آخرین عضوی که به آن دسترسی پیدا کردهایم. مقدار اولیهی آن راNoneقرار دهید._access_counts: یک دیکشنری برای ذخیرهی تعداد دسترسیها به هر عضو. کلیدهای آن نام اعضا (رشته) و مقادیر آن تعداد دفعات دسترسی (عدد صحیح) خواهد بود. مقدار اولیهی آن باید یک دیکشنری خالی ({}) باشد. هر بار که در ادامه (در متد__getattr__) دسترسی به یک عضو معتبر در شیء هدف را تشخیص داده و آن عضو را برمیگردانید، باید این دو ویژگی را بهروزرسانی کنید.
def __init__(self, obj):
self._obj = obj
self._last_accessed = None
self._access_counts = {}
این متد نام آخرین متد یا ویژگی معتبری که با موفقیت از طریق این نمونهی
Proxyروی شیء هدف دسترسی یا فراخوانی شدهاست را برمیگرداند. خروجی این متد یک رشته است که نام آخرین عضوی است که به آن دسترسی پیدا کردیم.اگر تا لحظهی فراخوانی این متد، به هیچ عضو معتبری روی شیء هدف از طریق این پروکسی دسترسی پیدا نکرده بودیم، این متد باید یک استثناء با پیام
"No attribute was accessed."پرتاب کند.
def last_accessed_attribute(self):
if self._last_accessed is None:
raise Exception("No attribute was accessed.")
return self._last_accessed
این متد تعداد دفعاتی که یک عضو خاص (متد یا ویژگی) با نام
attribute_nameروی شیء هدف از طریق این نمونهProxyبه آن دسترسی پیدا کردیم را گزارش میدهد. این متد یک پارامتر ورودی به نامattribute_nameدریافت میکند که نام متد یا ویژگی موردنظر است (از نوع رشته). خروجی آن یک عدد صحیح است که نشاندهندهی تعداد دفعات دسترسی یا فراخوانی است.اگر
attribute_nameورودی، نام یک عضو معتبر در شیء هدف نباشد، یا اگر عضو معتبری باشد اما هنوز از طریق این پروکسی دسترسی یا فراخوانی نشده باشد، متد باید مقدار0را برگرداند.
def count_of_accesses(self, attribute_name):
return self._access_counts.get(attribute_name, 0)
این متد بررسی میکند که آیا به یک عضو خاص (متد یا ویژگی) با نام
attribute_nameروی شیء هدف از طریق این نمونهیProxyحداقل یک بار دسترسی پیدا کردهایم یا خیر. این متد یک پارامتر ورودی به نامattribute_nameدریافت میکند که نام متد یا ویژگی مورد نظر است (از نوع رشته). خروجی آن یک مقدار منطقی است:Trueاگر عضو با موفقیت حداقل یک بار دسترسی یا فراخوانی شده باشد وFalseدر غیر این صورت.اگر
attribute_nameورودی، نام یک عضو معتبر در شیء هدف نباشد، متد باید مقدارFalseرا برگرداند.
def was_accessed(self, attribute_name):
return self._access_counts.get(attribute_name, 0) > 0
این متد قلب کلاس
Proxyاست و مسئول رهگیری دسترسی به اعضای شیء هدف میباشد. زمانی که پایتون ویژگی خواسته شده را در خودِ کلاسProxyپیدا نمیکند، این متد فراخوانی میشود.الگوریتم پیشنهادی برای پیادهسازی
__getattr__:
- بررسی وجود: با استفاده از تابع
hasattr(self._obj, name)بررسی کنید که آیا ویژگی درخواست شده در شیء هدف (self._obj) وجود دارد یا خیر.- اگر وجود نداشت: استثناء
Exception("No such attribute.") را پرتاب کنید (raise).- اگر وجود داشت (موفقیت): نام ویژگی (
name) را درself._last_accessedذخیره کنید. تعداد دسترسی به این نام را در دیکشنریself._access_countsاضافه کنید (اگر قبلاً بوده یکی اضافه کنید، اگر نبوده مقدار ۱ بگذارید). در نهایت با استفاده ازgetattr(self._obj, name)مقدار واقعی را از شیء هدف گرفته وreturnکنید.
def __getattr__(self, name):
if not hasattr(self._obj, name):
raise Exception("No such attribute.")
self._last_accessed = name
self._access_counts[name] = self._access_counts.get(name, 0) + 1
return getattr(self._obj, name)
🎯 مثال
فرض کنید کلاس سادهای به نام Radio داریم.
class Radio():
def __init__(self):
self._channel = None
self.is_on = False
self.volume = 0
def get_channel(self):
return self._channel
def set_channel(self, value):
self._channel = value
def power(self):
self.is_on = not self.is_on
def __str__(self):
return f"Radio(channel={self._channel}, is_on={self.is_on}, volume={self.volume})"
حالا، با استفاده از کلاس Proxyای که شما پیادهسازی میکنید، میتوانیم یک نمونه از Radio ایجاد کرده و سپس یک پروکسی برای آن بسازیم.
radio = Radio()
radio_proxy = Proxy(radio)
با استفاده از radio_proxy، باید بتوانیم به تمام متدها و ویژگیهای شیء اصلی radio دسترسی داشته باشیم و همزمان کلاس Proxy این دسترسیها و فراخوانیها را رهگیری کند.
radio_proxy.set_channel(95)
radio_proxy.power()
radio_proxy.set_channel(100)
print(radio_proxy.is_on) # True
print(radio_proxy.last_accessed_attribute()) # is_on
print(radio_proxy.count_of_accesses('set_channel')) # 2
print(radio_proxy.was_accessed('power')) # True
print(radio_proxy.count_of_accesses('non_existent_method')) # 0
print(radio_proxy.was_accessed('volume')) # False
خروجی:
True
is_on
2
True
0
False
💡 چالشهای حل تمرین
⚠️ چگونه به ویژگیهای موجود در شیء هدف دسترسی پیدا کنم و آنها را رهگیری کنم و تعداد دفعات دسترسی را بشمارم؟
برای رهگیری دسترسی به ویژگیها و متدهایی که بهصورت مستقیم در کلاس
Proxyتعریف نشدهاند، باید متد__getattr__(self, name)را در کلاسProxyپیادهسازی (override) کنید. این متد زمانی فراخوانی میشود که پایتون ویژگی خواسته شده را در خودِ کلاسProxyپیدا نمیکند.الگوریتم پیشنهادی برای پیادهسازی
__getattr__:
- بررسی وجود: با استفاده از تابع
hasattr(self._obj, name)بررسی کنید که آیا ویژگی درخواست شده در شیء هدف (self._obj) وجود دارد یا خیر.- اگر وجود نداشت: استثناء
Exception("No such attribute.") را پرتاب کنید (raise).- اگر وجود داشت (موفقیت): نام ویژگی (
name) را درself._last_accessedذخیره کنید. تعداد دسترسی به این نام را در دیکشنریself._access_countsاضافه کنید (اگر قبلاً بوده یکی اضافه کنید، اگر نبوده مقدار ۱ بگذارید). در نهایت با استفاده ازgetattr(self._obj, name)مقدار واقعی را از شیء هدف گرفته وreturnکنید.
✅ این تمرین را در ویرایشگر خود پیادهسازی کنید. به یاد داشته باشید که کلاس
Proxyباید تمام ویژگیها و متدهای شیء هدف را رهگیری کند.
نمونهها
radio = Radio()
radio_proxy = Proxy(radio)
radio_proxy.set_channel(95)
radio_proxy.power()
radio_proxy.set_channel(100)
print(radio_proxy.is_on)
print(radio_proxy.last_accessed_attribute())
print(radio_proxy.count_of_accesses('set_channel'))
print(radio_proxy.was_accessed('power'))
print(radio_proxy.count_of_accesses('non_existent_method'))
print(radio_proxy.was_accessed('volume'))True is_on 2 True 0 False
p = Proxy(Radio())
try:
p.last_accessed_attribute()
except Exception as error:
print(error)No attribute was accessed.
p = Proxy(Radio())
p.volume
try:
p.nope
except Exception as error:
print(error)
p.last_accessed_attribute()
p.count_of_accesses("nope")
p.was_accessed("nope")0 No such attribute. 'volume' 0 False
p = Proxy(Radio())
p.volume
p.volume
p.count_of_accesses("volume")
p.was_accessed("volume")
p.last_accessed_attribute()0 0 2 True 'volume'
radio = Radio()
p = Proxy(radio)
p.set_channel(7)
p.get_channel()
radio.get_channel()
p.count_of_accesses("get_channel")7 7 1
برای نوشتن پاسخ و دریافت امتیاز، ابتدا وارد حساب شوید.
ورود