设计模式网上的版本各种各样,但是有两个问题:
1、因为设计模式最早的理论是基于JAVA,所以很多实现和讲解都以JAVA作为源码;
2、大量的代码都是依葫芦画瓢,基本上都比较鸡肋,在工程代码中没有参考价值。
所以决心在遇到实际问题的时候自己总结一下,以上算是给这一系列做个简单的序。
一、起因
最近因为工程代码中的历史遗留问题,让一些API用起来非常不顺手,跟同事出现了一些分歧,工程中大致代码是这样的:
# -*- coding: utf-8 -*-
import redis
class RedisClient(object):
def __init__(self, host, port, password, db):
self._client = redis.Redis(host=host, port=port, password=password, db=db)
def _make_key(self, key):
return 'prefix_{0}'.format(key)
def get(self, key):
return self._client.get(self._make_key(key))
'''省略一堆需要封装self._client中的方法'''
mc = RedisClient()
代码逻辑非常简单,对redis库进行二次封装(即RedisClient类为Redis类的代理类),并在操作每个redis的key时,给key再加上prefix_的前缀。
同事认为这样的写法有两个好处:
1、能够隐藏redis库中的细节,外部调用的时候更加便捷
2、统一封装,修改简单,如果以后nosql数据库更换时,不需要去外部修改,只需要修改RedisClient类中的方法就可以满足需求
而作者却认为这样的想法有待商榷,首先GOF提出代理模式的目的(下一节会展开描述)并不涵盖对外部库的二次封装,其次以上所提及的两个优点其实均是伪命题,redis本身提供了种类丰富的数据结构,使用者几乎不可能在不理解的情况下调用函数,由于数据结构多,使得函数传参也变得多样,统一封装基本是痴人说梦,基于以上几个原因,想要换nosql而仅仅修改代理类,也是吃人说明。重中之重是,每当你需要用到Redis类中的方法时,你都需要手动向RedisClient中添加一个同名函数,平添很多工作量。这样的代理类,显然是弊大于利的。
二、代理模式
代理模式的目的:
1、远程代理:假如一个对象有同的地址空间,则需要一个代理类来提供局部代表
假如需要的数据在远端或者数据库中,使用代理类隐藏数据传输过程逻辑,直接获取数据本身
2、虚代理:延迟实例化代价很高的类
原著中以图片对象为例子,实例化图片对象的开销很大,代理类中只会预先存储一些实例化图片需要的参数,只有在确定要显示图片的时候,再根据代理类中的参数实例化图片类
3、保护代理:需要对访问对象进行权限控制,代理类作为权限控制层存在
这个比较好理解,就不再展开了
4、智能指引:取代指针
其实1、4在工程代码中是最常见的存在,代码中经常会出现诸如‘a.b.c.d’这样的引用,倘若这个指针要被反复使用,那就会出现多次,繁琐而又不好修改。
“反向”代理(智能指针):
因为上一段代码在工程中引用非常多,不能直接修改代码逻辑,而我们又希望不重新实例化redis对象的情况下调用使用redis,于是代码的管理层出现了这样的代理模式:
class UserManager(object):
@property
def redis_client(self):
return mc._client
def getUserInfo(self):
return self.redis_client.hget('User', 1001)
以上代码其实就是目的4的体现,为了能够直接使用redis中的方法,我们在自己的类中设置了一个代理函数redis_client,为了避免函数调用打上@property标签,这样就可以像在getUserInfo中那样流畅的使用被代理对象的API了,这也是为什么标题上会加上“反向”的原因。
延伸-代理的结合:
# -*- coding: utf-8 -*-
import redis
class RedisClient(object):
REDIS_CONFIG = {'host': '127.0.0.1', 'port': 6381,
'password': 'password', 'db':0}
def __init__(self):
self._client = None
def __getattribute__(self, *args, **kwargs):
'''优先使用代理类中的函数,如果没有则使用被代理类的函数'''
if self._client is None:
'''这里假设Redis是一个开销比较大的类,预先储存配置,
需要的时候再实例化常驻内存'''
self._client = redis.Redis(**self.REDIS_CONFIG)
try:
return object.__getattribute__(self, *args, **kwargs)
except AttributeError:
return object.__getattribute__(self._client, *args, **kwargs)
def get(self, key):
return self._client.get(self._make_key(key))
mc = RedisClient()
class UserManager(object):
def getUserInfo(self):
return mc.hget('User', 1001)
示例中假设RedisClient是一个需要频繁实例化、但开销很大的类。这样去使用代理类,就免去了没有重写过的方法需要逐一去重写的麻烦,其次又使用了延后的代理运用,在第一次查找的时候才实例化redis连接。
注意:虽然相比第一节中的做法已经改进很多,但这样使用代理仍有一个问题,即当代码中大量使用被代理类的函数的时候,该函数就不适合再被代理,而需要通过在代理类中新增函数来实现,以免修改过多;但归根揭底就是,含有数量庞杂的API时,不适合使用代理模式。