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)