The Evolution of Finger: moving to a component based architecture Finger的演化:步入基于组件的结构
简介 Introduction
This is the fourth part of the Twisted tutorial Twisted from Scratch, or The Evolution of Finger.
这是Twisted教程,Finger的演化的第四部分。
In this section of the tutorial, we'll move our code to a component architecture so that adding new features is trivial.
在这一部分,我们将把代码放到一个组件结构中,这样加入新的特性就很简单了。
写可维护的代码 Write Maintainable Code
In the last version, the service class was three times longer than any other class, and was hard to understand. This was because it turned out to have multiple responsibilities. It had to know how to access user information, by rereading the file every half minute, but also how to display itself in a myriad of protocols. Here, we used the component-based architecture that Twisted provides to achieve a separation of concerns. All the service is responsible for, now, is supporting getUser/getUsers. It declares its support via the implements keyword. Then, adapters are used to make this service look like an appropriate class for various things: for supplying a finger factory to TCPServer, for supplying a resource to site's constructor, and to provide an IRC client factory for TCPClient. All the adapters use are the methods in FingerService they are declared to use: getUser/getUsers. We could, of course, skip the interfaces and let the configuration code use things like FingerFactoryFromService(f) directly. However, using interfaces provides the same flexibility inheritance gives: future subclasses can override the adapters.
在上个版本中,服务类是其它类的三倍长,而且也难懂。这是因为它具有多重职责。 必须要知道如何取得用户信息,通过每半分钟重新读取一次文件,还要知道如何通过不同的协议来显示信息。 这里,我们使用Twisted提供的基于组件的结构,由此分而治之。所有的服务都提供getUser/getUsers。 通过implement关键字。然后适配器就能让这个服务成为一个可以用于多种用途的类:向TCPServer提供finger factory, 提供资源给站点的构造器,提供IRC客户端factory给TCPClient。适配器使用的是FingerService声明可用的方法:getUser/getUsers。 我们可以,当然了,忽略接口让配置代码直接使用FingerFactoryFromService。但是使用接口则可以提供和继承一样的灵活性:将来子类可以重载适配器。
1 # Do everything properly, and componentize
2 from twisted.application import internet, service
3 from twisted.internet import protocol, reactor, defer
4 from twisted.protocols import basic, irc
5 from twisted.python import components
6 from twisted.web import resource, server, static, xmlrpc
7 import cgi
8
9 class IFingerService(components.Interface):
10
11 def getUser(self, user):
12 """Return a deferred returning a string"""
13
14 def getUsers(self):
15 """Return a deferred returning a list of strings"""
16
17 class IFingerSetterService(components.Interface):
18
19 def setUser(self, user, status):
20 """Set the user's status to something"""
21
22 def catchError(err):
23 return "Internal error in server"
24
25 class FingerProtocol(basic.LineReceiver):
26
27 def lineReceived(self, user):
28 d = self.factory.getUser(user)
29 d.addErrback(catchError)
30 def writeValue(value):
31 self.transport.write(value+'\n')
32 self.transport.loseConnection()
33 d.addCallback(writeValue)
34
35
36 class IFingerFactory(components.Interface):
37
38 def getUser(self, user):
39 """Return a deferred returning a string"""
40
41 def buildProtocol(self, addr):
42 """Return a protocol returning a string"""
43
44
45 class FingerFactoryFromService(protocol.ServerFactory):
46
47 __implements__ = IFingerFactory,
48
49 protocol = FingerProtocol
50
51 def __init__(self, service):
52 self.service = service
53
54 def getUser(self, user):
55 return self.service.getUser(user)
56
57 components.registerAdapter(FingerFactoryFromService,
58 IFingerService,
59 IFingerFactory)
60
61 class FingerSetterProtocol(basic.LineReceiver):
62
63 def connectionMade(self):
64 self.lines = []
65
66 def lineReceived(self, line):
67 self.lines.append(line)
68
69 def connectionLost(self, reason):
70 if len(self.lines) == 2:
71 self.factory.setUser(*self.lines)
72
73
74 class IFingerSetterFactory(components.Interface):
75
76 def setUser(self, user, status):
77 """Return a deferred returning a string"""
78
79 def buildProtocol(self, addr):
80 """Return a protocol returning a string"""
81
82
83 class FingerSetterFactoryFromService(protocol.ServerFactory):
84
85 __implements__ = IFingerSetterFactory,
86
87 protocol = FingerSetterProtocol
88
89 def __init__(self, service):
90 self.service = service
91
92 def setUser(self, user, status):
93 self.service.setUser(user, status)
94
95
96 components.registerAdapter(FingerSetterFactoryFromService,
97 IFingerSetterService,
98 IFingerSetterFactory)
99
100 class IRCReplyBot(irc.IRCClient):
101
102 def connectionMade(self):
103 self.nickname = self.factory.nickname
104 irc.IRCClient.connectionMade(self)
105
106 def privmsg(self, user, channel, msg):
107 user = user.split('!')[0]
108 if self.nickname.lower() == channel.lower():
109 d = self.factory.getUser(msg)
110 d.addErrback(catchError)
111 d.addCallback(lambda m: "Status of %s: %s" % (msg, m))
112 d.addCallback(lambda m: self.msg(user, m))
113
114
115 class IIRCClientFactory(components.Interface):
116
117 """
118 @ivar nickname
119 """
120
121 def getUser(self, user):
122 """Return a deferred returning a string"""
123
124 def buildProtocol(self, addr):
125 """Return a protocol"""
126
127
128 class IRCClientFactoryFromService(protocol.ClientFactory):
129
130 __implements__ = IIRCClientFactory,
131
132 protocol = IRCReplyBot
133 nickname = None
134
135 def __init__(self, service):
136 self.service = service
137
138 def getUser(self, user):
139 return self.service.getUser(user)
140
141 components.registerAdapter(IRCClientFactoryFromService,
142 IFingerService,
143 IIRCClientFactory)
144
145 class UserStatusTree(resource.Resource):
146
147 __implements__ = resource.IResource,
148
149 def __init__(self, service):
150 resource.Resource.__init__(self)
151 self.service = service
152 self.putChild('RPC2', UserStatusXR(self.service))
153
154 def render_GET(self, request):
155 d = self.service.getUsers()
156 def formatUsers(users):
157 l = ['<li><a href="%s">%s</a></li>' % (user, user)
158 for user in users]
159 return '<ul>'+''.join(l)+'</ul>'
160 d.addCallback(formatUsers)
161 d.addCallback(request.write)
162 d.addCallback(lambda _: request.finish())
163 return server.NOT_DONE_YET
164
165 def getChild(self, path, request):
166 if path=="":
167 return UserStatusTree(self.service)
168 else:
169 return UserStatus(path, self.service)
170
171 components.registerAdapter(UserStatusTree, IFingerService,
172 resource.IResource)
173
174 class UserStatus(resource.Resource):
175
176 def __init__(self, user, service):
177 resource.Resource.__init__(self)
178 self.user = user
179 self.service = service
180
181 def render_GET(self, request):
182 d = self.service.getUser(self.user)
183 d.addCallback(cgi.escape)
184 d.addCallback(lambda m:
185 '<h1>%s</h1>'%self.user+'<p>%s</p>'%m)
186 d.addCallback(request.write)
187 d.addCallback(lambda _: request.finish())
188 return server.NOT_DONE_YET
189
190
191 class UserStatusXR(xmlrpc.XMLRPC):
192
193 def __init__(self, service):
194 xmlrpc.XMLRPC.__init__(self)
195 self.service = service
196
197 def xmlrpc_getUser(self, user):
198 return self.service.getUser(user)
199
200
201 class FingerService(service.Service):
202
203 __implements__ = IFingerService,
204
205 def __init__(self, filename):
206 self.filename = filename
207 self._read()
208
209 def _read(self):
210 self.users = {}
211 for line in file(self.filename):
212 user, status = line.split(':', 1)
213 user = user.strip()
214 status = status.strip()
215 self.users[user] = status
216 self.call = reactor.callLater(30, self._read)
217
218 def getUser(self, user):
219 return defer.succeed(self.users.get(user, "No such user"))
220
221 def getUsers(self):
222 return defer.succeed(self.users.keys())
223
224
225 application = service.Application('finger', uid=1, gid=1)
226 f = FingerService('/etc/users')
227 serviceCollection = service.IServiceCollection(application)
228 internet.TCPServer(79, IFingerFactory(f)
229 ).setServiceParent(serviceCollection)
230 internet.TCPServer(8000, server.Site(resource.IResource(f))
231 ).setServiceParent(serviceCollection)
232 i = IIRCClientFactory(f)
233 i.nickname = 'fingerbot'
234 internet.TCPClient('irc.freenode.org', 6667, i
235 ).setServiceParent(serviceCollection)
236
237 Source listing - listings/finger/finger19.py
238 Advantages of Latest Version
239 Readable -- each class is short
240 Maintainable -- each class knows only about interfaces
241 Dependencies between code parts are minimized
242 Example: writing a new IFingerService is easy
243 class IFingerSetterService(components.Interface):
244
245 def setUser(self, user, status):
246 """Set the user's status to something"""
247
248 # Advantages of latest version
249
250 class MemoryFingerService(service.Service):
251
252 __implements__ = IFingerService, IFingerSetterService
253
254 def __init__(self, **kwargs):
255 self.users = kwargs
256
257 def getUser(self, user):
258 return defer.succeed(self.users.get(user, "No such user"))
259
260 def getUsers(self):
261 return defer.succeed(self.users.keys())
262
263 def setUser(self, user, status):
264 self.users[user] = status
265
266
267 f = MemoryFingerService(moshez='Happy and well')
268 serviceCollection = service.IServiceCollection(application)
269 internet.TCPServer(1079, IFingerSetterFactory(f), interface='127.0.0.1'
270 ).setServiceParent(serviceCollection)
271
272 Source listing - listings/finger/finger19a_changes.py
273 Full source code here:
274
275 # Do everything properly, and componentize
276 from twisted.application import internet, service
277 from twisted.internet import protocol, reactor, defer
278 from twisted.protocols import basic, irc
279 from twisted.python import components
280 from twisted.web import resource, server, static, xmlrpc
281 import cgi
282
283 class IFingerService(components.Interface):
284
285 def getUser(self, user):
286 """Return a deferred returning a string"""
287
288 def getUsers(self):
289 """Return a deferred returning a list of strings"""
290
291 class IFingerSetterService(components.Interface):
292
293 def setUser(self, user, status):
294 """Set the user's status to something"""
295
296 def catchError(err):
297 return "Internal error in server"
298
299 class FingerProtocol(basic.LineReceiver):
300
301 def lineReceived(self, user):
302 d = self.factory.getUser(user)
303 d.addErrback(catchError)
304 def writeValue(value):
305 self.transport.write(value+'\n')
306 self.transport.loseConnection()
307 d.addCallback(writeValue)
308
309
310 class IFingerFactory(components.Interface):
311
312 def getUser(self, user):
313 """Return a deferred returning a string"""
314
315 def buildProtocol(self, addr):
316 """Return a protocol returning a string"""
317
318
319 class FingerFactoryFromService(protocol.ServerFactory):
320
321 __implements__ = IFingerFactory,
322
323 protocol = FingerProtocol
324
325 def __init__(self, service):
326 self.service = service
327
328 def getUser(self, user):
329 return self.service.getUser(user)
330
331 components.registerAdapter(FingerFactoryFromService,
332 IFingerService,
333 IFingerFactory)
334
335 class FingerSetterProtocol(basic.LineReceiver):
336
337 def connectionMade(self):
338 self.lines = []
339
340 def lineReceived(self, line):
341 self.lines.append(line)
342
343 def connectionLost(self, reason):
344 if len(self.lines) == 2:
345 self.factory.setUser(*self.lines)
346
347
348 class IFingerSetterFactory(components.Interface):
349
350 def setUser(self, user, status):
351 """Return a deferred returning a string"""
352
353 def buildProtocol(self, addr):
354 """Return a protocol returning a string"""
355
356
357 class FingerSetterFactoryFromService(protocol.ServerFactory):
358
359 __implements__ = IFingerSetterFactory,
360
361 protocol = FingerSetterProtocol
362
363 def __init__(self, service):
364 self.service = service
365
366 def setUser(self, user, status):
367 self.service.setUser(user, status)
368
369
370 components.registerAdapter(FingerSetterFactoryFromService,
371 IFingerSetterService,
372 IFingerSetterFactory)
373
374 class IRCReplyBot(irc.IRCClient):
375
376 def connectionMade(self):
377 self.nickname = self.factory.nickname
378 irc.IRCClient.connectionMade(self)
379
380 def privmsg(self, user, channel, msg):
381 user = user.split('!')[0]
382 if self.nickname.lower() == channel.lower():
383 d = self.factory.getUser(msg)
384 d.addErrback(catchError)
385 d.addCallback(lambda m: "Status of %s: %s" % (msg, m))
386 d.addCallback(lambda m: self.msg(user, m))
387
388
389 class IIRCClientFactory(components.Interface):
390
391 """
392 @ivar nickname
393 """
394
395 def getUser(self, user):
396 """Return a deferred returning a string"""
397
398 def buildProtocol(self, addr):
399 """Return a protocol"""
400
401
402 class IRCClientFactoryFromService(protocol.ClientFactory):
403
404 __implements__ = IIRCClientFactory,
405
406 protocol = IRCReplyBot
407 nickname = None
408
409 def __init__(self, service):
410 self.service = service
411
412 def getUser(self, user):
413 return self.service.getUser(user)
414
415 components.registerAdapter(IRCClientFactoryFromService,
416 IFingerService,
417 IIRCClientFactory)
418
419 class UserStatusTree(resource.Resource):
420
421 __implements__ = resource.IResource,
422
423 def __init__(self, service):
424 resource.Resource.__init__(self)
425 self.service = service
426 self.putChild('RPC2', UserStatusXR(self.service))
427
428 def render_GET(self, request):
429 d = self.service.getUsers()
430 def formatUsers(users):
431 l = ['<li><a href="%s">%s</a></li>' % (user, user)
432 for user in users]
433 return '<ul>'+''.join(l)+'</ul>'
434 d.addCallback(formatUsers)
435 d.addCallback(request.write)
436 d.addCallback(lambda _: request.finish())
437 return server.NOT_DONE_YET
438
439 def getChild(self, path, request):
440 if path=="":
441 return UserStatusTree(self.service)
442 else:
443 return UserStatus(path, self.service)
444
445 components.registerAdapter(UserStatusTree, IFingerService,
446 resource.IResource)
447
448 class UserStatus(resource.Resource):
449
450 def __init__(self, user, service):
451 resource.Resource.__init__(self)
452 self.user = user
453 self.service = service
454
455 def render_GET(self, request):
456 d = self.service.getUser(self.user)
457 d.addCallback(cgi.escape)
458 d.addCallback(lambda m:
459 '<h1>%s</h1>'%self.user+'<p>%s</p>'%m)
460 d.addCallback(request.write)
461 d.addCallback(lambda _: request.finish())
462 return server.NOT_DONE_YET
463
464
465 class UserStatusXR(xmlrpc.XMLRPC):
466
467 def __init__(self, service):
468 xmlrpc.XMLRPC.__init__(self)
469 self.service = service
470
471 def xmlrpc_getUser(self, user):
472 return self.service.getUser(user)
473
474 class MemoryFingerService(service.Service):
475
476 __implements__ = IFingerService, IFingerSetterService
477
478 def __init__(self, **kwargs):
479 self.users = kwargs
480
481 def getUser(self, user):
482 return defer.succeed(self.users.get(user, "No such user"))
483
484 def getUsers(self):
485 return defer.succeed(self.users.keys())
486
487 def setUser(self, user, status):
488 self.users[user] = status
489
490
491 application = service.Application('finger', uid=1, gid=1)
492 f = MemoryFingerService(moshez='Happy and well')
493 serviceCollection = service.IServiceCollection(application)
494 internet.TCPServer(79, IFingerFactory(f)
495 ).setServiceParent(serviceCollection)
496 internet.TCPServer(8000, server.Site(resource.IResource(f))
497 ).setServiceParent(serviceCollection)
498 i = IIRCClientFactory(f)
499 i.nickname = 'fingerbot'
500 internet.TCPClient('irc.freenode.org', 6667, i
501 ).setServiceParent(serviceCollection)
502 internet.TCPServer(1079, IFingerSetterFactory(f), interface='127.0.0.1'
503 ).setServiceParent(serviceCollection)
Source listing - listings/finger/finger19a.py
Aspect-Oriented Programming At last, an example of aspect-oriented programming that isn't about logging or timing. This code is actually useful! Watch how aspect-oriented programming helps you write less code and have fewer dependencies!
至少可以算作是面向方面编程的一个和logging以及timing无关的例子。这段代码很有用!要知道面向方面编程让你写更少的代码,还减小依赖!
翻译:Bruce Who([email protected])
return index-->TwistedTUT
Version: 1.3.0