代理模式是通过一层间接保护层实现更安全的接口访问,例如:访问敏感信息——在允许用户访问敏感信息之前,我们希望确保用户具备足够的权限。
有四种常用的代理模式:
- 远程代理:实际存在于不同地址空间(例如,某个网络服务器)的对象在本地的代理者。使得访问远程对象就像访问本地一样,隐藏了复杂性,如:ORM。
- 虚拟代理:用来实现延迟访问,比如一些需要复杂计算的对象,Python 里可以实现 lazy_property,改善性能。
- 保护/防护代理:用于控制敏感对象的访问。
- 智能(引用)代理:在对象被访问时执行额外的动作。例如引用计数和线程安全检查。
延迟初始化
我们先创建一个LazyProperty
类,用作一个装饰器。当它修饰某个特性时,LazyProperty
惰性地(首次使用时)加载特性,而不是立即进行。
class LazyProperty:
def __init__(self, method):
self.method = method
self.method_name = method.__name__
def __get__(self, instance, owner):
if not instance:
return None
value = self.method(instance)
setattr(owner, self.method_name, value)
return value
class Test:
def __init__(self):
self.x = 'foo'
self.y = 'bar'
self._resource = None
@LazyProperty
def resource(self):
# 构造函数里没有初始化,第一次访问才会被调用
print('initializing self._resource which is: {}'.format(self._resource))
self._resource = tuple(range(5)) # 模拟一次耗时计算
return self._resource
def main():
t = Test()
print(t.x)
print(t.y)
# 访问LazyProperty, resource里的print语句只执行一次,实现了延迟加载和一次执行
print(t.resource)
print(t.resource)
if __name__ == '__main__':
main()
LazyProperty类实际上是一个描述符。 描述符(descriptor)是Python中重写类属性访问方法(__get__()
、 __set__()
和__delete__()
)的默认行为要使用的一种推荐机制。
Test类演示了我们可以如何使用LazyProperty类。其中有三个属性, x、 y 和 _resource。我们想懒加载 _resource 变量,因此将其初始化为None。
在OOP中有两种基本的、不同类型的懒加载,如下所示:
- 在实例级:这意味着会一个对象的特性进行懒初始化,但该特性有一个对象作用域。同一个类的每个实例(对象)都有自己的(不同的)特性副本。
- 在类级或模块级:在这种情况下,我们不希望每个实例都有一个不同的特性副本,而是所有实例共享同一个特性,而特性是懒初始化的。
保护代理
这里实现一个简单的保护代理来查看和添加用户,该服务提供以下两个选项:
- 查看用户列表:这一操作不要求特殊权限。
- 添加新用户:这一操作要求客户端提供一个特殊的密码。
class SensitiveInfo:
def __init__(self):
self.users = ['nick', 'tom', 'ben', 'mike']
def read(self):
result = 'There are {} users: {}'
print(result.format(len(self.users), ' '.join(self.users)))
def add(self, user):
self.users.append(user)
print('Added user {}'.format(user))
class Info:
def __init__(self):
self.protected = SensitiveInfo()
self.secret = '0xdeadbeef'
def read(self):
self.protected.read()
def add(self, user):
sec = input('what is the secret? ')
self.protected.add(user) if sec == self.secret else print("That's wrong!")
def main():
info = Info()
while True:
print('1. read list |==| 2. add user |==| 3. quit')
key = input('choose option: ')
if key == '1':
info.read()
elif key == '2':
name = input('choose username: ')
info.add(name)
elif key == '3':
exit()
else:
print('unknown option: {}'.format(key))
if __name__ == '__main__':
main()
该示例有几个重大的安全缺陷:
- 没有什么能阻止客户端代码通过直接创建一个 SensitiveInfo 实例来绕过应用的安全设置,疑使用 abc 模块来禁止直接实例化 SensitiveInfo。
- 密钥直接写死在代码里,应该用安全性较高密钥写到配置或者环境变量里。
这里使用抽象基类来修复第一个问题,只需要修改类代码而不用修改 main() 函数里面的使用代码:
from abc import ABCMeta, abstractmethod
class SensitiveInfo(metaclass=ABCMeta):
def __init__(self):
self.users = ['nick', 'tom', 'ben', 'mike']
@abstractmethod
def read(self):
pass
@abstractmethod
def add(self, user):
pass
class Info(SensitiveInfo):
'''protection proxy to SensitiveInfo'''
def __init__(self):
# self.protected = SensitiveInfo()
super().__init__()
self.secret = '0xdeadbeef' # 为了方便示例这里直接写死在代码里
def read(self):
print('There are {} users: {}'.format(len(self.users), ' '.join(self.users)))
def add(self, user):
""" 给add操作加上密钥验证,保护add操作"""
sec = input('what is the secret? ')
self.users.append(user) if sec == self.secret else print("That's wrong!")