設計模式–代理模式(Proxy Pattern)
前言:
- 這篇筆記主要是參考自 Udemy 線上學習平台上 Dmitri Nesteruk 老師開的 Design Patterns in Python (付費)課程 中的 Proxy Pattern 享元模式的課程部分以及 對岸圖靈星球論壇發表在 Youtube 頻道上的設計模式–代理模式(免費)教學視頻的重點摘要。
※ 什麼是 Proxy Pattern?
- 創建一個類,這個類的功能是作為另一個資源的代理接口(Interface),而需要接口的資源,通常是初始化吃資源或是需要因應角色有不同行為抑或是使用時需要特別的附加行為,例如記錄(logging)
- 而 使用這樣一個代理的類別物件來控制另一個(資源)物件,就是代理模式的主要精神。
- 代理的行為不只一種,可以是 虛擬代理、緩存代理、保護代理、遠程代理等等。
※ UML Diagram
※ Example - 1
# 保護代理範例
class Car:
def __init__(self, driver):
self.driver = driver
def drive(self):
print(f'Car being driven by {self.driver.name}')
class CarProxy:
def __init__(self, driver):
self.driver = driver
self.car = Car(driver)
def drive(self):
if self.driver.age >= 16:
self.car.drive()
else:
print('Driver too young')
class Driver:
def __init__(self, name, age):
self.name = name
self.age = age
if __name__ == '__main__':
car = CarProxy(Driver('John', 12))
car2 = CarProxy(Driver('Tom', 20))
car.drive()
car2.drive()
# 執行結果:
Driver too young
Car being driven by Tom
※ Example - 2
# 虛擬代理範例(處理需要 Lazy Loding Object)
class Bitmap:
def __init__(self, filename):
self.filename = filename
print(f'Loading image from {filename}')
def draw(self):
print(f'Drawing image {self.filename}')
class LazyBitmap:
def __init__(self, filename):
self.filename = filename
self.bitmap = None
def draw(self):
if not self.bitmap:
self.bitmap = Bitmap(self.filename)
self.bitmap.draw()
def draw_image(image):
print('About to draw image')
image.draw()
print('Done drawing image')
if __name__ == '__main__':
bmp = LazyBitmap('facepalm.jpg') # Bitmap
draw_image(bmp)
# 執行結果
About to draw image
Loading image from facepalm.jpg
Drawing image facepalm.jpg
Done drawing image
※ Example - 3
# 這個末尾章節的習作部分應該算是虛擬與保護代理的複合範例
from unittest import main, TestCase
class Person:
def __init__(self, age):
self.age = age
def drink(self):
return 'drinking'
def drive(self):
return 'driving'
def drink_and_drive(self):
return 'driving while drunk'
class ResponsiblePerson:
def __init__(self, person):
self.person = person
@property
def age(self):
return self.person.age
@age.setter
def age(self, value):
self.person.age = value
def drink(self):
if self.age >= 18:
return self.person.drink()
return 'too young'
def drive(self):
if self.age >= 16:
return self.person.drive()
return 'too young'
def drink_and_drive(self):
return 'dead'
class Evaluate(TestCase):
def test_exercise(self):
p = Person(10)
rp = ResponsiblePerson(p)
self.assertEqual('too young', rp.drive())
self.assertEqual('too young', rp.drink())
self.assertEqual('dead', rp.drink_and_drive())
rp.age = 20
self.assertEqual('driving', rp.drive())
self.assertEqual('drinking', rp.drink())
self.assertEqual('dead', rp.drink_and_drive())
main(argv=[''], verbosity=2, exit=False)
# 執行結果:
test_exercise (__main__.Evaluate.test_exercise) ... C:\Users\chriswei\AppData\Roaming\Python\Python312\site-packages\jupyter_client\session.py:200: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
return datetime.utcnow().replace(tzinfo=utc) # noqa
ok
----------------------------------------------------------------------
Ran 1 test in 0.002s
OK
※ Proxy Pattern 與 Decorator 的差異
好的,以下是 Python Proxy Pattern 與 Decorator Pattern 的差異:
特徵 | Proxy Pattern | Decorator Pattern |
---|---|---|
目的 | 控制對原始物件的存取 | 增強原始物件的功能 |
實現方式 | 建立代理物件來代表原始物件 | 將原始物件包裝在裝飾器物件中 |
對原始物件的影響 | 不會改變原始物件的接口 | 不會改變原始物件的狀態 |
適用場景 | 需要控制對原始物件的存取 | 需要增強原始物件的功能 |
優點 | 可以隱藏原始物件,增加安全性 | 可以動態地添加新功能 |
缺點 | 會增加額外的程式碼 | 可能會降低效能 |
具體來說,Proxy Pattern 的目的是在不改變原始物件接口的情況下,控制對原始物件的存取,例如:
- 提供緩存或記錄功能,避免直接訪問原始物件
- 提供安全性檢查,防止未經授權的存取
- 提供負載平衡,將請求分散到多個原始物件上
Decorator Pattern 的目的是在不改變原始物件的狀態的情況下,增強原始物件的功能,例如:
- 添加日誌記錄
- 添加性能監控
- 添加安全檢查
總而言之,Proxy Pattern 和 Decorator Pattern 都是結構型模式,可以用來擴展原始物件的功能,但兩者在目的、實現方式、對原始物件的影響等方面都有所不同。
※ Proxy Pattern 的優缺點及應用場景
(1). 優點
- 關注點分離: 將資源的使用從資源本身轉移到代理身上
- 訪問控制: 藉由代理來進行資源的使用控制
- 延遲實例化(Lazy Loading): 對於初始化吃資源的物件,可以使用代理來延遲該物件的實例化
- 緩存優化: 基於代理來管理資源物件的實例化 → 將同類的物件使用原先已初始化的實例,只有新的物件才進行初始化
- 附加行為: 基於代理對資源物件的管制,來附加一些額外的行為,例如記錄
(2). 缺點
- 每多一層控制層,就會增加程式的複雜性
- 增加複雜性,等於增加程式的負載,增加性能開銷
(3). 應用場景
- 訪問代理
- 保護代理
- 遠程代理
- 附加管理