添加了test.py,里面的单元测试有使用的方法。
源码地址:http://git.oschina.net/duoduo3_69/python_design_pattern
git checkout v002(这个版本与此篇博客相符)
承接上文python 与设计模式 ——工厂与单例
modeldecorator/*
装饰者模式是一个非常棒的设计模式,他能在不改变基类代码的情况下
动态的添加一些功能,与之前的工厂模式结合,就会实现一行代码添加
一个功能的效果,这也是zarkpy里面装饰者的设计。
装饰者模式本身会有一个被装饰者对象的引用,保持原来接口的情况下
动态添加一些功能,然后再重新调用被装饰者原来的方法,达到一种添
加效果的功能。
装饰者并不是本文的亮点,亮点是工厂与装饰者结合之后,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——开发经验,要的就是干货