python设计模式之代理模式

在某些应用中,我们想要在访问某个对象之前执行一个或多个重要的操作,例如,访问敏感信息——在允许用户访问敏感信息之前,我们希望确保用户具备足够的权限。操作系统中也存在类似的情况,用户必须具有管理员权限才能在系统中安装新程序。

上面提到的重要操作不一定与安全问题相关。延迟初始化是另一个案例:我们想要把一个计算成本较高的对象的创建过程延迟到用户首次真正使用它时才进行。

这类操作通常使用代理设计模式( Proxy design pattern)来实现。该模式因使用代理(又名替代, surrogate)对象在访问实际对象之前执行重要操作而得其名。以下是四种不同的知名代理类型:

  • [ ] 远程代理:实际存在于不同地址空间(例如,某个网络服务器)的对象在本地的代理者。
  • [ ] 虚拟代理:用于懒初始化,将一个大计算量对象的创建延迟到真正需要的时候进行。
  • [ ] 保护/防护代理:控制对敏感对象的访问。
  • [ ] 智能(引用)代理:在对象被访问时执行额外的动作。此类代理的例子包括引用计数和

    线程安全检查。

本文例子先是一个虚拟代理然后就是保护代理。

我们先创建一个LazyProperty类,用作一个修饰器。当它修饰某个特性时, LazyProperty惰性(首次使用时)加载特性,而不是立即进行。 __init__方法创建两个变量,用作初始化待修饰特性的方法的别名。 method变量是一个实际方法的别名,method_name变量则是该方法名称的别名。为更好理解如何使用这两个别名,可以将其值输出到标准输出(取消注释下面代码中的两个注释行)。

class LazyProperty:
def __init__(self, method):
self.method = method
self.method_name = method.__name__
# print('function overriden: {}'.format(self.fget))
# print("function's name: {}".format(self.func_name))

LazyProperty类实际上是一个描述符。 描述符( descriptor)是Python中重写类属性访问方法( get()、 set()和__delete__())的默认行为要使用的一种推荐机制。 LazyProperty类仅重写了__set__(),因为这是其需要重写的唯一访问方法。换句话说,我们无需重写所有访问方法。 get()方法所访问的特性值,正是下层方法想要赋的值,并使用setattr()来手动赋值。 get()实际做的事情非常简单,就是使用值来替代方法!这意味着不仅特性是惰性加载的,而且仅可以设置一次。我们马上就能看到这意味着什么。同样,取消注释以下代码的的注释行,以得到一些额外信息。

    def __get__(self, obj, cls):
if not obj:
return None
value = self.method(obj)
# print('value {}'.format(value))
setattr(obj, self.method_name, value)
return value

Test类演示了我们可以如何使用LazyProperty类。其中有三个属性, x、 y和resource。我们想懒加载resource变量,因此将其初始化为None,如以下代码所示。

class Test:
def __init__(self):
self.x = 'foo'
self.y = 'bar'
self._resource = None

resource()方法是使用LazyProperty类修饰的。因演示目的, LazyProperty类将_resource属性初始化为一个tuple,如以下代码所示。通常来说这是一个缓慢/代价大的初始化过程(初始化数据库、图形等)。

@LazyProperty
def resource(self):
print('initializing self._resource which is: {}'.format(self._resource))
self._resource = tuple(range(5)) # 假设这一行的计算成本比较大
return self._resource

main()函数展示了懒初始化是如何进行的。注意, get()访问方法的重写使得可以将resource()方法当作一个变量(可以使用t.resource替代t.resource())。

def main():
t = Test()
print(t.x)
print(t.y)
# 做更多的事情……
print(t.resource)
print(t.resource)

从该例子的输出我们可以得到如下结论:

  • [ ] _resource变量实际不是在t实例创建时初始化的,而是在我们首次使用t.resource时。
  • [ ] 第二次使用t.resource之时,并没有再次初始化变量。这就是为什么初始化字符串

    initializing self._resource which is:仅出现一次的原因。
  • [ ] 执行结果如下:
foo
bar
initializing self._resource which is: None
(0, 1, 2, 3, 4)
(0, 1, 2, 3, 4)

可能对于该例子的输出有些读者还不能理解,这里做一补充。

  1. 对于@LazyProperty修饰的函数程序执行时只预处理一次。
  2. 对于resource()函数,经过修饰器后,resource = LazyProperty(resource()),是一个实例。
  3. 对于obj.get()函数,当调用xx.obj时才执行。
  4. 当第一次调用t.resource时,就等价于t.LazyProperty(resource()),执行__get__()函数,然后就是,value赋值那句,此时,就执行Test的resource()函数,把返回值给value,并通过设置setattr()函数给t对象增加属性:resource = value,并返回value。
  5. 当第二次调用时,t中已经具有resource属性,故,优先调用,直接返回值。这就是为什么上面的输出第二次元组时直接输出元组,它已经不是调用resouce函数,而是recource属性。

在OOP中有两种基本的、不同类型的懒初始化,如下所示。

  • [ ] 在实例级:这意味着会一个对象的特性进行懒初始化,但该特性有一个对象作用域。同一个类的每个实例(对象)都有自己的(不同的)特性副本。
  • [ ] 在类级或模块级:在这种情况下,我们不希望每个实例都有一个不同的特性副本,而是所有实例共享同一个特性,而特性是懒初始化的。这一情况在本章不涉及。如果你觉得有意思,可以将其作为练习。
1. 现实生活的例子

芯片(又名芯片密码)卡是现实生活中使用防护代理的一个好例子。借记/信用卡包含一个芯片, ATM机或读卡器需要先读取芯片;在芯片通过验证后,需要一个密码( PIN)才能完成交易。这意味着只有在物理地提供芯片卡并且知道密码时才能进行交易。

使用银行支票替代现金进行购买和交易是远程代理的一个例子。支票准许了对一个银行账户的访问。下图展示了支票如何用作一个远程代理。

2. 软件的例子

Python的weakref模块包含一个proxy()方法,该方法接受一个输入对象并将一个智能代理返回给该对象。弱引用是为对象添加引用计数支持的一种推荐方式。

3. 应用案例

因为存在至少四种常见的代理类型,所以代理设计模式有很多应用案例,如下所示。

  • [ ] 在使用私有网络或云搭建一个分布式系统时。在分布式系统中,一些对象存在于本地内存中,一些对象存在于远程计算机的内存中。如果我们不想本地代码关心两者之间的区别,那么可以创建一个远程代理来隐藏/封装,使得应用的分布式性质透明化。
  • [ ] 因过早创建计算成本较高的对象导致应用遭受性能问题之时。使用虚拟代理引入懒初始化,仅在真正需要对象之时才创建,能够明显提高性能。
  • [ ] 用于检查一个用户是否有足够权限来访问某个信息片段。如果应用要处理敏感信息(例如,医疗数据),我们会希望确保用户在被准许之后才能访问/修改数据。一个保护/防护代理可以处理所有安全相关的行为。
  • [ ] 应用(或库、工具集、框架等)使用多线程,而我们希望把线程安全的重任从客户端代码转移到应用。这种情况下,可以创建一个智能代理,对客户端隐藏线程安全的复杂性。
  • [ ] 对象关系映射( Object-Relational Mapping, ORM)API也是一个如何使用远程代理的例子。

    包括Django在内的许多流行Web框架使用一个ORM来提供类OOP的关系型数据库访问。ORM是关系型数据库的代理,数据库可以部署在任意地方,本地或远程服务器都可以。
4. 实现

为演示代理模式,我们将实现一个简单的保护代理来查看和添加用户。该服务提供以下两个选项。

  • [ ] 查看用户列表:这一操作不要求特殊权限。
  • [ ] 添加新用户:这一操作要求客户端提供一个特殊的密码。

SensitiveInfo类包含我们希望保护的信息。 users变量是已有用户的列表。 read()方法输出用户列表。 add()方法将一个新用户添加到列表中。考虑一下下面的代码。

class SensitiveInfo:
def __init__(self):
self.users = ['nick', 'tom', 'ben', 'mike']
def read(self):
print('There are {} users: {}'.format(len(self.users), ' '.join(self.users)))
def add(self, user):
self.users.append(user)
print('Added user {}'.format(user))

Info类是SensitiveInfo的一个保护代理。 secret变量值是客户端代码在添加新用户时被要求告知/提供的密码。注意,这只是一个例子。现实中,永远不要执行以下操作。

  • [ ] 在源代码中存储密码
  • [ ] 以明文形式存储密码
  • [ ] 使用一种弱(例如, MD5)或自定义加密形式

read()方法是SensetiveInfo.read()的一个包装。 add()方法确保仅当客户端代码知道密码时才能添加新用户。考虑一下下面的代码。

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!")

main()函数展示了客户端代码可以如何使用代理模式。客户端代码创建一个Info类的实例,并使用菜单让用户选择来读取列表、添加新用户或退出应用。考虑一下下面的代码。

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))

完整代码:

class SensitiveInfo:
def __init__(self):
self.users = ['nick', 'tom', 'ben', 'mike']
def read(self):
print('There are {} users: {}'.format(len(self.users), ' '.join(self.users)))
def add(self, user):
self.users.append(user)
print('Added user {}'.format(user))
class Info:
'''SensitiveInfo的保护代理'''
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()

输出如下:

1. read list |==| 2. add user |==| 3. quit
choose option: a
1. read list |==| 2. add user |==| 3. quit
choose option: 4
1. read list |==| 2. add user |==| 3. quit
choose option: 1
There are 4 users: nick tom ben mike
1. read list |==| 2. add user |==| 3. quit
choose option: 2
choose username: pet
what is the secret? blah
That's wrong!
1. read list |==| 2. add user |==| 3. quit
choose option: 2
choose username: bill
what is the secret? 0xdeadbeef
Added user bill
1. read list |==| 2. add user |==| 3. quit
choose option: 1
There are 5 users: nick tom ben mike bill
1. read list |==| 2. add user |==| 3. quit
choose option: 3
5. 小结

存在四种不同的代理类型,如下所示。

  • [ ] 远程代理,代表一个活跃于远程位置(例如,我们自己的远程服务器或云服务)的对象。
  • [ ] 虚拟代理,将一个对象的初始化延迟到真正需要使用时进行。
  • [ ] 保护/防护代理,用于对处理敏感信息的对象进行访问控制。
  • [ ] 当我们希望通过添加帮助信息(比如,引用计数)来扩展一个对象的行为时,可以使用智能(引用)代理。

在第一个代码示例中,我们使用修饰器和描述符以地道的Python风格创建一个虚拟代理。这个代理允许我们以惰性方式初始化对象属性。

芯片卡和银行支票是人们每天都在使用的两个不同代理的例子。芯片卡是一个防护代理,而银行支票是一个远程代理。另外,一些流行软件中也使用代理。 Python有一个weakref.proxy()方法,使得创建一个智能代理非常简单。 ZeroMQ的Python实现则使用了远程代理。

python设计模式之代理模的更多相关文章

  1. python 设计模式之代理模式

    代理模式在一般形式上是一个类函数接口.代理可以是这些事物的接口:网络连接,存储的对象,文件,或者其他资源(昂贵的或者不容易复制的). 一个众所周知的代理模式的例子就是引用计数的指针对象. 代理模式是结 ...

  2. Python设计模式(5)-代理模式

    # coding=utf-8 # 代理模式:# * 代理类成为实际想调用对象的中间件,可以控制对实际调用对象的访问权限# * 可以维护实际对象的引用 class DbManager: def __in ...

  3. 浅谈Python设计模式 - 代理模式

    声明:本系列文章主要参考<精通Python设计模式>一书,并且参考一些资料,结合自己的一些看法来总结而来. 一.在某些应用中,我们想要在访问某个对象之前执行一个或者多个重要的操作,例如,访 ...

  4. 简介Python设计模式中的代理模式与模板方法模式编程

    简介Python设计模式中的代理模式与模板方法模式编程 这篇文章主要介绍了Python设计模式中的代理模式与模板方法模式编程,文中举了两个简单的代码片段来说明,需要的朋友可以参考下 代理模式 Prox ...

  5. 最全36种python设计模式

    设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用.设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案.这些解决方案是众多软件开发人员经过 ...

  6. python——设计模式

    设计模式是什么? 设计模式是经过总结.优化的,对我们经常会碰到的一些编程问题的可重用解决方案.一个设计模式并不像一个类或一个库那样能够直接作用于我们的代码.反之,设计模式更为高级,它是一种必须在特定情 ...

  7. Python设计模式 - 总览(更新中...)

    最近打算重构部分python项目,有道是"工欲善其事,必先利其器",所以有必要梳理一下相关设计模式.每次回顾基本概念或底层实现时都会有一些新的收获,希望这次也不例外. 本系列打算先 ...

  8. python设计模式之装饰器详解(三)

    python的装饰器使用是python语言一个非常重要的部分,装饰器是程序设计模式中装饰模式的具体化,python提供了特殊的语法糖可以非常方便的实现装饰模式. 系列文章 python设计模式之单例模 ...

  9. python设计模式第2版

    python设计模式第2版 目录 第1章 设计模式简介 1 1.1 理解面向对象编程 1 1.1.1 对象 2 1.1.2 类 2 1.1.3 方法 2 1.2 面向对象编程的主要概念 3 1.2.1 ...

随机推荐

  1. Vue开发者必会的基础知识盘点

    你会Vue吗,你看以下知识点你掌握了多少?实际工作中是否运用的得心应手?如果是,那么恭喜你! Vue中的数据和DOM已经被关联起来,所有的东西都是响应式的.注意我们不再和HTML直接交互.一个Vue应 ...

  2. Python "按位或"和"按位异或"的区别

    首先分别解释一下按位或和按位异或 按位或: 按位或指的是参与运算的两个数分别对应的二进制位进行“或”的操作.只要对应的两个二进制位有一个为1时,结果位就为1.python中运算符为“|” 按位异或: ...

  3. NCRE-Python考点

    NCRE-Python考点 作者:封亚飞本文不含 文件处理.面向对象程序设计.公共基础.计算生态希望各位可以批评指正Qq 64761294 由于图片上传不方便,需要真题的朋友可以加我的qq找我要pdf ...

  4. 深入理解JVM(一)Java内存区域

    运行时数据区 程序计数器 当前线程执行的字节码的行号指示器 每条线程都有独立的程序计数器,各线程之间计数器互不影响,独立存储. 如果执行的是java方法,计数器记录正在执行的虚拟机字节码指令的位置: ...

  5. vue history路由模式 Nginx 生产实践

    nginx(带二级目录的配置) location ~* /A {    alias  /opt/nginx-1.4.7/html/ued/A;     try_files $uri $uri /A/s ...

  6. three.js 数学方法之Matrix3

    今天郭先生来说一说three.js的三维矩阵,这块知识需要结合线性代数的一些知识,毕业时间有点长,线性代数的知识大部分都还给了老师.于是一起简单的复习了一下.所有的计算都是使用列优先顺序进行的.然而, ...

  7. justoj connect(边的处理)

    CONNECT https://oj.ismdeep.com/contest/problem?id=1702&pid=2 Problem Description 有nn个顶点,每个顶点有自己的 ...

  8. Java Web(3)-XML

    一.XML简介 1. 什么是xml? xml 是可扩展的标记性语言 2. xml的作用? 用来保存数据,而且这些数据具有自我描述性 它还可以做为项目或者模块的配置文件 还可以做为网络传输数据的格式(现 ...

  9. DOM事件操作

    DOM事件:对事件做出反应 当事件发生时,可以执行 JavaScript,比如:点击时 onClick="" 例:当用户点击时,会改变 <h1> 元素的内容: < ...

  10. Django学习路32_创建管理员及内容补充+前面内容复习

    创建管理员 python manage.py createsuperuser   数据库属性命名限制 1.不能是python的保留关键字 2.不允许使用连续的下划线,这是由django的查询方式决定的 ...