Python设计模式编程中的备忘录模式与对象池模式示例
Memento备忘录模式
备忘录模式一个最好想象的例子:undo!它对对象的一个状态进行了'快照',在你需要的时候恢复原貌。做前端会有一个场景:你设计一个表单,当点击提交会对表单内容验证,这个时候你就要对用户填写的数据复制下来,当用户填写的不正确或者格式不对等问题,就可以使用快照数据恢复用户已经填好的,而不是让用户重新来一遍,不是嘛?
python的例子
这里实现了一个事务提交的例子
importcopy defMemento(obj,deep=False): #对你要做快照的对象做快照 state=(copy.copyifdeepelsecopy.deepcopy)(obj.__dict__) defRestore(): obj.__dict__=state returnRestore classTransaction: deep=False def__init__(self,*targets): self.targets=targets self.Commit() #模拟事务提交,其实就是初始化给每个对象往self.targets赋值 defCommit(self): self.states=[Memento(target,self.deep)fortargetinself.targets] #回滚其实就是调用Memento函数,执行其中的闭包,将__dict__恢复 defRollback(self): forstateinself.states: state() #装饰器的方式给方法添加这个事务的功能 deftransactional(method): #这里的self其实就是要保存的那个对象,和类的实例无关 defwrappedMethod(self,*args,**kwargs): state=Memento(self) try: returnmethod(self,*args,**kwargs) except: #和上面的回滚一样,异常就恢复 state() raise returnwrappedMethod classNumObj(object): def__init__(self,value): self.value=value def__repr__(self): return'<%s:%r>'%(self.__class__.__name__,self.value) defIncrement(self): self.value+=1 @transactional defDoStuff(self): #赋值成字符串,再自增长肯定会报错的 self.value='1111' self.Increment() if__name__=='__main__': n=NumObj(-1) printn t=Transaction(n) try: foriinrange(3): n.Increment() printn #这里事务提交会保存状态从第一次的-1到2 t.Commit() print'--commited' foriinrange(3): n.Increment() printn n.value+='x'#willfail printn except: #回滚只会回顾到上一次comit成功的2而不是-1 t.Rollback() print'--rolledback' printn print'--nowdoingstuff...' try: n.DoStuff() except: print'->doingstufffailed!' importtraceback traceback.print_exc(0) pass #第二次的异常回滚n还是2,整个过程都是修改NumObj的实例对象 printn
注意
当你要保存的状态很大,可能会浪费大量内存
对象池模式
在开发中,我们总是用到一些和'池'相关的东西,比如内存池,连接池,对象池,线程池..这里说的对象池其实也就是一定数量已经创建好的对象的集合。为什么要用对象池?创建对象是要付出代价的(我暂时还没有研究过底层,只说我工作中体会的),比如pymongo就自带线程池,这样用完就放回到池里再被重用,岂不是节省了创建的花费?
python的例子
我这里实现了个线程安全的简单的对象池
importQueue
importtypes
importthreading
fromcontextlibimportcontextmanager
classObjectPool(object):
def__init__(self,fn_cls,*args,**kwargs):
super(ObjectPool,self).__init__()
self.fn_cls=fn_cls
self._myinit(*args,**kwargs)
def_myinit(self,*args,**kwargs):
self.args=args
self.maxSize=int(kwargs.get("maxSize",1))
self.queue=Queue.Queue()
def_get_obj(self):
#因为传进来的可能是函数,还可能是类
iftype(self.fn_cls)==types.FunctionType:
returnself.fn_cls(self.args)
#判断是经典或者新类
eliftype(self.fn_cls)==types.ClassTypeortype(self.fn_cls)==types.TypeType:
returnapply(self.fn_cls,self.args)
else:
raise"Wrongtype"
defborrow_obj(self):
#这个print没用,只是在你执行的时候告诉你目前的队列数,让你发现对象池的作用
printself.queue._qsize()
#要是对象池大小还没有超过设置的最大数,可以继续放进去新对象
ifself.queue.qsize()<self.maxSizeandself.queue.empty():
self.queue.put(self._get_obj())
#都会返回一个对象给相关去用
returnself.queue.get()
#回收
defrecover_obj(self,obj):
self.queue.put(obj)
#测试用函数和类
defecho_func(num):
returnnum
classecho_cls(object):
pass
#不用构造含有__enter__,__exit__的类就可以使用with,当然你可以直接把代码放到函数去用
@contextmanager
defpoolobj(pool):
obj=pool.borrow_obj()
try:
yieldobj
exceptException,e:
yieldNone
finally:
pool.recover_obj(obj)
obj=ObjectPool(echo_func,23,maxSize=4)
obj2=ObjectPool(echo_cls,maxSize=4)
classMyThread(threading.Thread):
defrun(self):
#为了实现效果,我搞了个简单的多线程,2个with放在一个地方了,只为测试用
withpoolobj(obj)ast:
printt
withpoolobj(obj2)ast:
printt
if__name__=='__main__':
threads=[]
foriinrange(200):
t=MyThread()
t.start()
threads.append(t)
fortinthreads:
t.join(True)