代码
完成了一般的委托功能,一个委托上挂多个函数,可以设置函数列表为空时,是否抛出异常。返回值是函数列表中最后一个函数调用的返回,使用方法可参见test部分。
修改:call忘了加字典参数。--LiJie
1 class delegate:
2 def __init__(self, *calls, **opts):
3 for call in calls:
4 if not callable(call):
5 raise RuntimeError, str(call) + ' not a callable object'
6 self.calls = () + calls
7 self.emptyExcept = opts.get('emptyExcept', False)
8 def __call__(self, *args, **kwargs):
9 if self.emptyExcept and not self.calls:
10 raise RuntimeError, 'No callable objects'
11
12 try:
13 result = None
14 for call in self.calls:
15 result = call (*args, **kwargs)
16 return result
17 except TypeError:
18 raise RuntimeError, 'Invalid callable type: ' + str(call)
19
20 def __add__(self, call):
21 if not callable(call):
22 raise RuntimeError, str(call) + ' not a callable object'
23 return delegate(*(self.calls + (call,)))
24 def __iadd__(self, *calls):
25 self.calls += calls
26 return self
27
28
29 if __name__ == '__main__':
30 def a(v1, v2):
31 print 'a:', v1, v2
32 def b(v1, v2):
33 print 'b:', v1, v2
34 def c(v1, v2):
35 print 'c:', v1, v2
36
37 class Test:
38 def hello(self, v1, v2):
39 print 'Test.hello:', v1, v2
40
41 class Test1:
42 def __call__(self, v1, v2):
43 print 'Test1.__call__:', v1, v2
44
45 print '======== test delegate.__init__ and delegate.__call__'
46 f = delegate(a, b, c, Test().hello, Test1())
47 f (3, 4)
48 print '======== test delegate.__add__'
49 f1 = f + a
50 f1 (5, 6)
51 print '======== test delegate.__iadd__'
52 f2 = delegate(a, b)
53 f2 += c
54 f2 (9, 10)
55 print '======== test delegate.__call__ return value'
56 f3 = delegate(lambda x, y: x*y)
57 assert f3(10, 11) == 10*11
58 f3 = delegate(lambda x, y: x*y, lambda x, y: x**y)
59 assert f3(10, 11) == 10**11
60 print '======== test delegate.__call__ with empty exception (empty)'
61 f4 = delegate (emptyExcept=True)
62 try:
63 f4 (3, 5)
64 except RuntimeError, ex:
65 print 'Exception:', ex
66 print '======== test delegate.__call__ with empty exception (not empty)'
67 f4 += a
68 f4 (3, 5)
讨论
不太清楚这个功能的具体实用性。在NewEdit中有类似的处理,它用来处理代码的插入点。不过有两种处理方式:一种是链式执行,不需要考虑返回值的,可以执行多个函数。一种是中断式执行,即要判断返回值,如果返回值为真,则不再继续执行,并且这个返回值要返回给调用者。因此前面的方式有些象过程,后面则是一个函数式的调用。 -- ["limodou"]
功用1--扩展接口设计
- 这个是模拟其它语言中的委托功能,委托实际上是个非常重要的功能,为模块接口设计提供了更广阔的选择,举个例子:
1 class NetConnection:
2 def onConnected (self):
3 pass
4 def onDisconnected (self):
5 pass
6 def onReceived (self, data):
7 pass
8 def connect (self, address):
9 pass
10 def disconnect (self):
11 pass
12 def send (self, data):
13 pass
14
15 class AutoReconnectConnection(NetConnection):
16 def onConnected (self):
17 pass
18 def onDisconnected (self):
19 pass
20
21 class AutoReconnectCommander(AutoReconnectConnection):
22 def onReceived (self, data):
23 pass
NetConnection类完成基本的网络连接,AutoReconnectConnection类有断线后自动重连的功能,AutoReconnectCommander则是把接收到的数据包转为指令或消息格式,这是一个普通的继承结构,没什么问题,使用时只要生成AutoReconnectCommander对象调用相关方法即可,接收到的数据在AutoReconnectCommander类中处理。
不过还有其它选择,这里用刚刚完成的delegate来做,示例如下:
1 class NetConnection:
2 def __init__ (self):
3 self.onConnected = delegate (proto='onConnected()')
4 self.onDisconnected = delegate (proto='onDisconnected()')
5 self.onReceived = delegate(proto='onReceived(data)')
6 def connect (self, address):
7 pass
8 def disconnect (self):
9 pass
10
11 class Commander:
12 def __init__(self):
13 self.send = delegate (proto='send(data)')
14 def onReceived (self, data):
15 pass
16
17 class AutoReconnectProcessor:
18 def __init__ (self):
19 self.connect = delegate (proto='connect(address)')
20 def onConnected (self):
21 pass
22 def onDisconnected (self):
23 pass
注:delegate (proto='onConnected()')括号中的proto='...'其实现在没有使用,只是为了方便随时查阅要挂载的函数的原型。 这个调用过程会麻烦一些:
1 # create objects
2 connection = NetConnection ()
3 commander = Commander ()
4 auto_reconnect_processor = AutoReconnectProcessor()
5
6 # process delegate
7 connection.onConnected += auto_reconnect_processor.onConnected
8 connection.onDisconnected += auto_reconnect_processor.onDisconnected
9 connection.onReceived += commander.onReceived
10 command.send += connection.send
11 auto_reconnect_processor.connect = connection.connect
这个要很多步骤才完成一个初始化。但它带来了很多好处:
- 对象之间的行为绑定修改起来非常容易,比如这里要让某个连接断线后不重连,只要把它们之间的关系去掉即可;
- 插入行为非常容易,比如需要在接收到数据以后,把收到的数据复制一份到某文件,只要实现一个对象,并且具有与onReceived接口相容的调用形式同,并添加委托关系即可;要把发送的数据打印在屏幕,只需要实现一个与send相容的函数,在里面执行输出到屏幕,再添加委托关系即可;
- 每个部分的测试应该会比继承关系更容易,现在只需要为委托接口添加测试、输出或其它方式的测试代码即可。
总述
总之,就像是电路板一样,实现一个个的小元件,用这种方式插到一块。每个元件之间尽量解除了藕合,对测试来说是大有益处的。就算是对整个系统做测试,也会容易很多,测试过程中要做的修改一般只需要修改委托关系即可。就像在已经完成电路板上夹上个万用表进行测试一样方便,如果某个元件工作不正常,通过简单替换成另一个功能类似的元件,重新加上委托关系即可。使用委托,唯一需要确定的是接口,对象之间根本不知道内部情况,这也与主流的软件开发思想一致。你所说的NewEdit中的处理,我想应该是类似任务列表和消息处理吧,任务列就是一起执行一组方法列表,依次执行;一般消息处理只要一组方法中,有一个处理并返回真值,则不再执行后续。如果是我所描述的这样,那么这里的delegate应该和任务列表功能相似。有些语言的delegate只支持绑定一个方法,C#的delegate支持绑定多个方法,我这里是按C#的方式来实现的,因为在前一个C++项目中使用了一个自己做的delegate,效果是好到非常(测试比较好做),所以这次有个python项目,我就直接先实现这个了。
-- LiJie