Memento備忘錄模式
備忘錄模式一個最好想象的例子:undo! 它對對象的一個狀態進行了'快照', 在你需要的時候恢復原貌。做前端會有一個場景:你設計一個表單,當點擊提交會對表單內容 驗證,這個時候你就要對用戶填寫的數據復制下來,當用戶填寫的不正確或者格式不對等問題, 就可以使用快照數據恢復用戶已經填好的,而不是讓用戶重新來一遍,不是嘛?
Python的例子
這裡實現了一個事務提交的例子
import
copy
def
Memento(obj, deep
=
False
):
# 對你要做快照的對象做快照
state
=
(copy.copy
if
deep
else
copy.deepcopy)(obj.__dict__)
def
Restore():
obj.__dict__
=
state
return
Restore
class
Transaction:
deep
=
False
def
__init__(
self
,
*
targets):
self
.targets
=
targets
self
.Commit()
# 模擬事務提交,其實就是初始化給每個對象往self.targets賦值
def
Commit(
self
):
self
.states
=
[Memento(target,
self
.deep)
for
target
in
self
.targets]
# 回滾其實就是調用Memento函數,執行其中的閉包,將__dict__恢復
def
Rollback(
self
):
for
state
in
self
.states:
state()
# 裝飾器的方式給方法添加這個事務的功能
def
transactional(method):
# 這裡的self其實就是要保存的那個對象,和類的實例無關
def
wrappedMethod(
self
,
*
args,
*
*
kwargs):
state
=
Memento(
self
)
try
:
return
method(
self
,
*
args,
*
*
kwargs)
except
:
# 和上面的回滾一樣,異常就恢復
state()
raise
return
wrappedMethod
class
NumObj(
object
):
def
__init__(
self
, value):
self
.value
=
value
def
__repr__(
self
):
return
'<%s: %r>'
%
(
self
.__class__.__name__,
self
.value)
def
Increment(
self
):
self
.value
+
=
1
@transactional
def
DOStuff(
self
):
# 賦值成字符串,再自增長肯定會報錯的
self
.value
=
'1111'
self
.Increment()
if
__name__
=
=
'__main__'
:
n
=
NumObj(
-
1
)
print
n
t
=
Transaction(n)
try
:
for
i
in
range
(
3
):
n.Increment()
print
n
# 這裡事務提交會保存狀態從第一次的-1到2
t.Commit()
print
'-- commited'
for
i
in
range
(
3
):
n.Increment()
print
n
n.value
+
=
'x'
# will fail
print
n
except
:
# 回滾只會回顧到上一次comit成功的2 而不是-1
t.Rollback()
print
'-- rolled back'
print
n
print
'-- now doing stuff ...'
try
:
n.DOStuff()
except
:
print
'-> doing stuff failed!'
import
traceback
traceback.print_exc(
0
)
pass
# 第二次的異常回滾n還是2, 整個過程都是修改NumObj的實例對象
print
n
注意
當你要保存的狀態很大,可能會浪費大量內存
對象池模式
在開發中,我們總是用到一些和'池'相關的東西,比如 內存池,連接池,對象池,線程池.. 這裡說的對象池其實也就是一定數量已經創建好的對象的集合。為什麼要用對象池? 創建對象是要付出代價的(我暫時還沒有研究過底層,只說我工作中體會的), 比如pymongo就自帶線程池,這樣用完就放回到池裡再被重用,豈不是節省了創建的花費?
Python的例子
我這裡實現了個線程安全的簡單的對象池
import
Queue
import
types
import
threading
from
contextlib
import
contextmanager
class
ObjectPool(
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
):
# 因為傳進來的可能是函數,還可能是類
if
type
(
self
.fn_cls)
=
=
types.FunctionType:
return
self
.fn_cls(
self
.args)
# 判斷是經典或者新類
elif
type
(
self
.fn_cls)
=
=
types.ClassType
or
type
(
self
.fn_cls)
=
=
types.TypeType:
return
apply
(
self
.fn_cls,
self
.args)
else
:
raise
"Wrong type"
def
borrow_obj(
self
):
# 這個print 沒用,只是在你執行的時候告訴你目前的隊列數,讓你發現對象池的作用
print
self
.queue._qsize()
# 要是對象池大小還沒有超過設置的最大數,可以繼續放進去新對象
if
self
.queue.qsize()<
self
.maxSize
and
self
.queue.empty():
self
.queue.put(
self
._get_obj())
# 都會返回一個對象給相關去用
return
self
.queue.get()
# 回收
def
recover_obj(
self
,obj):
self
.queue.put(obj)
# 測試用函數和類
def
echo_func(num):
return
num
class
echo_cls(
object
):
pass
# 不用構造含有__enter__, __exit__的類就可以使用with,當然你可以直接把代碼放到函數去用
@contextmanager
def
poolobj(pool):
obj
=
pool.borrow_obj()
try
:
yIEld
obj
except
Exception, e:
yIEld
None
finally
:
pool.recover_obj(obj)
obj
=
ObjectPool(echo_func,
23
, maxSize
=
4
)
obj2
=
ObjectPool(echo_cls, maxSize
=
4
)
class
MyThread(threading.Thread):
def
run(
self
):
# 為了實現效果,我搞了個簡單的多線程,2個with放在一個地方了,只為測試用
with poolobj(obj) as t:
print
t
with poolobj(obj2) as t:
print
t
if
__name__
=
=
'__main__'
:
threads
=
[]
for
i
in
range
(
200
):
t
=
MyThread()
t.start()
threads.append(t)
for
t
in
threads:
t.join(
True
)