The Evolution of Finger: moving to a component based architecture

Finger的演化:步入基于组件的结构

-- dreamingk [2004-08-09 02:10:32]

1. 简介 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.

在这一部分,我们将把代码放到一个组件结构中,这样加入新的特性就很简单了。

2. 写可维护的代码 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