python 與設計模式 ——工廠與裝飾者

python 與設計模式第二篇

添加了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真心慢(生產力)。

巨大的ps:但願能一塊兒寫issue

[開源項目]skill_issues——開發經驗,要的就是乾貨

相關文章
相關標籤/搜索