添加了test.py,裏面的單元測試有使用的方法。java
源碼地址:http://git.oschina.net/duoduo3_69/python_design_patternpython
git checkout v002(這個版本與此篇博客相符)c++
承接上文python 與設計模式 ——工廠與單例git
modeldecorator/*
裝飾者模式是一個很是棒的設計模式,他能在不改變基類代碼的狀況下 動態的添加一些功能,與以前的工廠模式結合,就會實現一行代碼添加 一個功能的效果,這也是zarkpy裏面裝飾者的設計。程序員
裝飾者模式自己會有一個被裝飾者對象的引用,保持原來接口的狀況下 動態添加一些功能,而後再從新調用被裝飾者原來的方法,達到一種添 加效果的功能。web
裝飾者並非本文的亮點,亮點是工廠與裝飾者結合以後,amazing!真 正作到一行代碼添加一個功能。請先耐心看前半部分。設計模式
Decorator.py,裝飾者的基類,將請求的方法轉交給model,接受兩個 參數,model是須要裝飾的對象,arguments是裝飾器裏面可能用到的 參數。框架
# -*- coding: utf-8 -*- class Decorator(object): """docstring for Decorator""" def __init__(self, model, arguments): super(Decorator, self).__init__() assert(isinstance(arguments,dict)) self.model = model self.arguments = arguments def __getattr__(self, attr): """docstring for __getattr__""" return getattr(self.model, attr)
依然以以前的Dao爲例,對於Dao來講,常常會用到的功能可能有分頁,排序, 如何,裝飾者在這裏顯得很是到位(代碼中有一個驗證的例子,實際上應該 寫成模板方法,之後再行修改)。函數
若是要對Dao中原有的功能添加一些功能的話,裝飾者要求與原接口必須相同, 由於python中沒有強制的編譯,因此這也是一種約定。單元測試
爲了方便閱讀,先把dao貼過來。
# -*- coding: utf-8 -*- class Dao(object): """docstring for BaseDao""" decorator = [] def __init__(self): super(Dao, self).__init__() def get(self,item_id): """docstring for get""" print 'Dao', 'get', item_id def insert(self,data): """docstring for get""" print 'Dao', 'insert', data def delete(self,item_id): """docstring for get""" print 'Dao', 'delete', item_id def update(self,item_id,data): """docstring for get""" print 'Dao', 'update', item_id, data def all(self,env=None): print 'Dao','all',env
一個驗證的裝飾者,能夠看到,在處理一些事件以後,最終調用 被裝飾者原來的方法self.model.get(item_id)
,以到達裝飾 的效果。
# -*- coding: utf-8 -*- from Decorator import Decorator class Invaildate(Decorator): """docstring for Invaildate""" def get(self,item_id): """docstring for get""" print 'invalidate', 'get',self.arguments self.model.get(item_id) def insert(self,data): """docstring for get""" print 'invalidate', 'insert',self.arguments self.model.insert(data) def delete(self,item_id): """docstring for get""" print 'invalidate', 'delete',self.arguments self.model.delete(item_id) def update(self,item_id,data): """docstring for get""" print 'invalidate', 'update',self.arguments self.model.update(item_id,data)
一個分頁裝飾者:
# -*- coding: utf-8 -*- import copy as _copy from Decorator import Decorator class Pagination(Decorator): """docstring for Pagination""" def all(self,env=None): env = _copy.deepcopy(self.arguments) if self.arguments is not None else {} # 一些分頁有關的代碼 if env.has_key('per_page_num'): print 'Pagination',env['per_page_num'] return self.model.all(env)
裝飾者的使用方法(不使用工廠):
def test_invaild_decorator(self): print 'test_invaild_decorator' print '============================' import DaoFactory import modeldecorator u1 = DaoFactory.dao_factory("User") print 'before decorator' print '========' u1.get(1) u1.update(1,1) u1.insert(1) u1.delete(1) u1 = modeldecorator.Invaildate(u1,dict(arg1="test1")) print 'after decorator' print '========' u1.get(1) u1.update(1,1) u1.insert(1) u1.delete(1) print 'after two decorator' u1 = modeldecorator.Pagination(u1,dict(per_page_num ="10")) u1.all() u1 = modeldecorator.Invaildate(u1,dict(arg2="test2")) print 'after three decorator' print '========' u1.get(1) u1.update(1,1) u1.insert(1) u1.delete(1)
單元測試結果
.test_invaild_decorator ============================ before decorator ======== Dao get 1 Dao update 1 1 Dao insert 1 Dao delete 1 after decorator ======== invalidate get {'arg1': 'test1'} Dao get 1 invalidate update {'arg1': 'test1'} Dao update 1 1 invalidate insert {'arg1': 'test1'} Dao insert 1 invalidate delete {'arg1': 'test1'} Dao delete 1 after two decorator Pagination 10 Dao all {'per_page_num': '10'} after three decorator ======== invalidate get {'arg2': 'test2'} invalidate get {'arg1': 'test1'} Dao get 1 invalidate update {'arg2': 'test2'} invalidate update {'arg1': 'test1'} Dao update 1 1 invalidate insert {'arg2': 'test2'} invalidate insert {'arg1': 'test1'} Dao insert 1 invalidate delete {'arg2': 'test2'} invalidate delete {'arg1': 'test1'} Dao delete 1
細心的讀者可能會看到Dao.py這個文件中多了與上個版本相比多了 decorator = []
。
有了上面裝飾者調用的例子,不難寫出下面的工廠方法:
CACHED_DECORATOR_DAO = {} def dao_decorator_factory(dao_name): assert isinstance(dao_name,(str,unicode)) cache_key = dao_name if CACHED_DECORATOR_DAO.has_key(cache_key): return CACHED_DECORATOR_DAO[cache_key] else: import dao import modeldecorator try: assert(hasattr(sys.modules["dao"],cache_key)) dao = getattr(sys.modules["dao"],cache_key) dao = dao() except: print 'dao name is',cache_key raise decorator = dao.decorator if dao.decorator else [] try: for d,args in decorator: assert(hasattr(sys.modules["modeldecorator"],d)) dao = getattr(sys.modules["modeldecorator"],d)(dao,args) except: raise CACHED_DECORATOR_DAO[cache_key] = dao return dao
主要添加了這幾行,for不斷的添加裝飾。
decorator = dao.decorator if dao.decorator else [] try: for d,args in decorator: assert(hasattr(sys.modules["modeldecorator"],d)) dao = getattr(sys.modules["modeldecorator"],d)(dao,args) except: raise CACHED_DECORATOR_DAO[cache_key] = dao
爲你的dao添加新的功能,例如爲一個Todo類添加分頁,和驗證的功能(驗 證這個裝飾者的例子確實很差,應該是模板方法),只需像下面的例子添加 兩行代碼,分頁和驗證的功能就ok了,調用的時候須要使用以前寫好的工廠, 代碼在後面。
# -*- coding: utf-8 -*- from Dao import Dao class Todo(Dao): """docstring for Todo""" decorator = [ ("Invaildate",dict(arg1 = "invaildate")), ("Pagination",dict(per_page_num = "10")), ] def __init__(self): super(Todo, self).__init__()
單元測試裏面的演示代碼:
def test_decorator_factory(self): print 'test_decorator_factory' print '======================================' import DaoFactory u1 = DaoFactory.dao_decorator_factory('Todo') u1.get(1) u1.update(1,1) u1.insert(1) u1.delete(1) u1.all()
結果
test_decorator_factory ====================================== invalidate get {'arg1': 'invaildate'} Dao get 1 invalidate update {'arg1': 'invaildate'} Dao update 1 1 invalidate insert {'arg1': 'invaildate'} Dao insert 1 invalidate delete {'arg1': 'invaildate'} Dao delete 1 Pagination 10 Dao all {'per_page_num': '10'}
python雖然不想java,c++這些語言有明確的接口(interface,純虛函數)從編譯 和語法的角度強制實現接口,可是正如以前說過的:
設計模式僅僅是參考,重要的是寫一些東西
參考設計模式,遵照一些約定(rail的名言,約定優於配置),本身也能夠寫框架 (不過最好不要重複發明輪子,可是若是你以爲有必要的話,能夠爲本身的團隊 寫適合本身團隊的東西)。
最重要的是能解放生產力,寧花機器一分,不花程序員一秒。
若是是寫中小型的web的話,能夠看看python或rail,java真心慢(生產力)。
[開源項目]skill_issues——開發經驗,要的就是乾貨