##language:zh ''' The Evolution of Finger: moving to a component based architecture Finger的演化:步入基于组件的结构 ''' -- dreamingk [<<DateTime(2004-08-09T02:10:32Z)>>] <<TableOfContents>> == 简介 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。但是使用接口则可以提供和继承一样的灵活性:将来子类可以重载适配器。 {{{ #!python # Do everything properly, and componentize from twisted.application import internet, service from twisted.internet import protocol, reactor, defer from twisted.protocols import basic, irc from twisted.python import components from twisted.web import resource, server, static, xmlrpc import cgi class IFingerService(components.Interface): def getUser(self, user): """Return a deferred returning a string""" def getUsers(self): """Return a deferred returning a list of strings""" class IFingerSetterService(components.Interface): def setUser(self, user, status): """Set the user's status to something""" def catchError(err): return "Internal error in server" class FingerProtocol(basic.LineReceiver): def lineReceived(self, user): d = self.factory.getUser(user) d.addErrback(catchError) def writeValue(value): self.transport.write(value+'\n') self.transport.loseConnection() d.addCallback(writeValue) class IFingerFactory(components.Interface): def getUser(self, user): """Return a deferred returning a string""" def buildProtocol(self, addr): """Return a protocol returning a string""" class FingerFactoryFromService(protocol.ServerFactory): __implements__ = IFingerFactory, protocol = FingerProtocol def __init__(self, service): self.service = service def getUser(self, user): return self.service.getUser(user) components.registerAdapter(FingerFactoryFromService, IFingerService, IFingerFactory) class FingerSetterProtocol(basic.LineReceiver): def connectionMade(self): self.lines = [] def lineReceived(self, line): self.lines.append(line) def connectionLost(self, reason): if len(self.lines) == 2: self.factory.setUser(*self.lines) class IFingerSetterFactory(components.Interface): def setUser(self, user, status): """Return a deferred returning a string""" def buildProtocol(self, addr): """Return a protocol returning a string""" class FingerSetterFactoryFromService(protocol.ServerFactory): __implements__ = IFingerSetterFactory, protocol = FingerSetterProtocol def __init__(self, service): self.service = service def setUser(self, user, status): self.service.setUser(user, status) components.registerAdapter(FingerSetterFactoryFromService, IFingerSetterService, IFingerSetterFactory) class IRCReplyBot(irc.IRCClient): def connectionMade(self): self.nickname = self.factory.nickname irc.IRCClient.connectionMade(self) def privmsg(self, user, channel, msg): user = user.split('!')[0] if self.nickname.lower() == channel.lower(): d = self.factory.getUser(msg) d.addErrback(catchError) d.addCallback(lambda m: "Status of %s: %s" % (msg, m)) d.addCallback(lambda m: self.msg(user, m)) class IIRCClientFactory(components.Interface): """ @ivar nickname """ def getUser(self, user): """Return a deferred returning a string""" def buildProtocol(self, addr): """Return a protocol""" class IRCClientFactoryFromService(protocol.ClientFactory): __implements__ = IIRCClientFactory, protocol = IRCReplyBot nickname = None def __init__(self, service): self.service = service def getUser(self, user): return self.service.getUser(user) components.registerAdapter(IRCClientFactoryFromService, IFingerService, IIRCClientFactory) class UserStatusTree(resource.Resource): __implements__ = resource.IResource, def __init__(self, service): resource.Resource.__init__(self) self.service = service self.putChild('RPC2', UserStatusXR(self.service)) def render_GET(self, request): d = self.service.getUsers() def formatUsers(users): l = ['<li><a href="%s">%s</a></li>' % (user, user) for user in users] return '<ul>'+''.join(l)+'</ul>' d.addCallback(formatUsers) d.addCallback(request.write) d.addCallback(lambda _: request.finish()) return server.NOT_DONE_YET def getChild(self, path, request): if path=="": return UserStatusTree(self.service) else: return UserStatus(path, self.service) components.registerAdapter(UserStatusTree, IFingerService, resource.IResource) class UserStatus(resource.Resource): def __init__(self, user, service): resource.Resource.__init__(self) self.user = user self.service = service def render_GET(self, request): d = self.service.getUser(self.user) d.addCallback(cgi.escape) d.addCallback(lambda m: '<h1>%s</h1>'%self.user+'<p>%s</p>'%m) d.addCallback(request.write) d.addCallback(lambda _: request.finish()) return server.NOT_DONE_YET class UserStatusXR(xmlrpc.XMLRPC): def __init__(self, service): xmlrpc.XMLRPC.__init__(self) self.service = service def xmlrpc_getUser(self, user): return self.service.getUser(user) class FingerService(service.Service): __implements__ = IFingerService, def __init__(self, filename): self.filename = filename self._read() def _read(self): self.users = {} for line in file(self.filename): user, status = line.split(':', 1) user = user.strip() status = status.strip() self.users[user] = status self.call = reactor.callLater(30, self._read) def getUser(self, user): return defer.succeed(self.users.get(user, "No such user")) def getUsers(self): return defer.succeed(self.users.keys()) application = service.Application('finger', uid=1, gid=1) f = FingerService('/etc/users') serviceCollection = service.IServiceCollection(application) internet.TCPServer(79, IFingerFactory(f) ).setServiceParent(serviceCollection) internet.TCPServer(8000, server.Site(resource.IResource(f)) ).setServiceParent(serviceCollection) i = IIRCClientFactory(f) i.nickname = 'fingerbot' internet.TCPClient('irc.freenode.org', 6667, i ).setServiceParent(serviceCollection) Source listing - listings/finger/finger19.py Advantages of Latest Version Readable -- each class is short Maintainable -- each class knows only about interfaces Dependencies between code parts are minimized Example: writing a new IFingerService is easy class IFingerSetterService(components.Interface): def setUser(self, user, status): """Set the user's status to something""" # Advantages of latest version class MemoryFingerService(service.Service): __implements__ = IFingerService, IFingerSetterService def __init__(self, **kwargs): self.users = kwargs def getUser(self, user): return defer.succeed(self.users.get(user, "No such user")) def getUsers(self): return defer.succeed(self.users.keys()) def setUser(self, user, status): self.users[user] = status f = MemoryFingerService(moshez='Happy and well') serviceCollection = service.IServiceCollection(application) internet.TCPServer(1079, IFingerSetterFactory(f), interface='127.0.0.1' ).setServiceParent(serviceCollection) Source listing - listings/finger/finger19a_changes.py Full source code here: # Do everything properly, and componentize from twisted.application import internet, service from twisted.internet import protocol, reactor, defer from twisted.protocols import basic, irc from twisted.python import components from twisted.web import resource, server, static, xmlrpc import cgi class IFingerService(components.Interface): def getUser(self, user): """Return a deferred returning a string""" def getUsers(self): """Return a deferred returning a list of strings""" class IFingerSetterService(components.Interface): def setUser(self, user, status): """Set the user's status to something""" def catchError(err): return "Internal error in server" class FingerProtocol(basic.LineReceiver): def lineReceived(self, user): d = self.factory.getUser(user) d.addErrback(catchError) def writeValue(value): self.transport.write(value+'\n') self.transport.loseConnection() d.addCallback(writeValue) class IFingerFactory(components.Interface): def getUser(self, user): """Return a deferred returning a string""" def buildProtocol(self, addr): """Return a protocol returning a string""" class FingerFactoryFromService(protocol.ServerFactory): __implements__ = IFingerFactory, protocol = FingerProtocol def __init__(self, service): self.service = service def getUser(self, user): return self.service.getUser(user) components.registerAdapter(FingerFactoryFromService, IFingerService, IFingerFactory) class FingerSetterProtocol(basic.LineReceiver): def connectionMade(self): self.lines = [] def lineReceived(self, line): self.lines.append(line) def connectionLost(self, reason): if len(self.lines) == 2: self.factory.setUser(*self.lines) class IFingerSetterFactory(components.Interface): def setUser(self, user, status): """Return a deferred returning a string""" def buildProtocol(self, addr): """Return a protocol returning a string""" class FingerSetterFactoryFromService(protocol.ServerFactory): __implements__ = IFingerSetterFactory, protocol = FingerSetterProtocol def __init__(self, service): self.service = service def setUser(self, user, status): self.service.setUser(user, status) components.registerAdapter(FingerSetterFactoryFromService, IFingerSetterService, IFingerSetterFactory) class IRCReplyBot(irc.IRCClient): def connectionMade(self): self.nickname = self.factory.nickname irc.IRCClient.connectionMade(self) def privmsg(self, user, channel, msg): user = user.split('!')[0] if self.nickname.lower() == channel.lower(): d = self.factory.getUser(msg) d.addErrback(catchError) d.addCallback(lambda m: "Status of %s: %s" % (msg, m)) d.addCallback(lambda m: self.msg(user, m)) class IIRCClientFactory(components.Interface): """ @ivar nickname """ def getUser(self, user): """Return a deferred returning a string""" def buildProtocol(self, addr): """Return a protocol""" class IRCClientFactoryFromService(protocol.ClientFactory): __implements__ = IIRCClientFactory, protocol = IRCReplyBot nickname = None def __init__(self, service): self.service = service def getUser(self, user): return self.service.getUser(user) components.registerAdapter(IRCClientFactoryFromService, IFingerService, IIRCClientFactory) class UserStatusTree(resource.Resource): __implements__ = resource.IResource, def __init__(self, service): resource.Resource.__init__(self) self.service = service self.putChild('RPC2', UserStatusXR(self.service)) def render_GET(self, request): d = self.service.getUsers() def formatUsers(users): l = ['<li><a href="%s">%s</a></li>' % (user, user) for user in users] return '<ul>'+''.join(l)+'</ul>' d.addCallback(formatUsers) d.addCallback(request.write) d.addCallback(lambda _: request.finish()) return server.NOT_DONE_YET def getChild(self, path, request): if path=="": return UserStatusTree(self.service) else: return UserStatus(path, self.service) components.registerAdapter(UserStatusTree, IFingerService, resource.IResource) class UserStatus(resource.Resource): def __init__(self, user, service): resource.Resource.__init__(self) self.user = user self.service = service def render_GET(self, request): d = self.service.getUser(self.user) d.addCallback(cgi.escape) d.addCallback(lambda m: '<h1>%s</h1>'%self.user+'<p>%s</p>'%m) d.addCallback(request.write) d.addCallback(lambda _: request.finish()) return server.NOT_DONE_YET class UserStatusXR(xmlrpc.XMLRPC): def __init__(self, service): xmlrpc.XMLRPC.__init__(self) self.service = service def xmlrpc_getUser(self, user): return self.service.getUser(user) class MemoryFingerService(service.Service): __implements__ = IFingerService, IFingerSetterService def __init__(self, **kwargs): self.users = kwargs def getUser(self, user): return defer.succeed(self.users.get(user, "No such user")) def getUsers(self): return defer.succeed(self.users.keys()) def setUser(self, user, status): self.users[user] = status application = service.Application('finger', uid=1, gid=1) f = MemoryFingerService(moshez='Happy and well') serviceCollection = service.IServiceCollection(application) internet.TCPServer(79, IFingerFactory(f) ).setServiceParent(serviceCollection) internet.TCPServer(8000, server.Site(resource.IResource(f)) ).setServiceParent(serviceCollection) i = IIRCClientFactory(f) i.nickname = 'fingerbot' internet.TCPClient('irc.freenode.org', 6667, i ).setServiceParent(serviceCollection) internet.TCPServer(1079, IFingerSetterFactory(f), interface='127.0.0.1' ).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(whoonline@msn.com) return index-->[[TwistedTUT]] Version: 1.3.0