Size: 11206
Comment:
|
Size: 18579
Comment:
|
Deletions are marked like this. | Additions are marked like this. |
Line 93: | Line 93: |
= Twisted Components: Interfaces and Adapters = == twisted.python.components: Twisted's implementation of Interfaces and Components == * '''Components and Inheritance''' Object oriented programming languages allow programmers to reuse portions of existing code by creating new classes of objects which subclass another class. When a class subclasses another, it is said to inherit all of its behaviour. The subclass can then override and extend the behavior provided to it by the superclass. Inheritance is very useful in many situations, but because it is so convenient to use, often becomes abused in large software systems, especially when multiple inheritance is involved. One solution is to use delegation instead of inheritance where appropriate. Delegation is simply the act of asking another object to perform a task for an object. To support this design pattern, which is often referred to as the components pattern because it involves many small interacting components, interfaces and adapters were created by the Zope 3 team. Interfaces are simply markers which objects can use to say I implement this interface. Other objects may then make requests like Please give me an object which implements interface X for object type Y. Objects which implement an interface for another object type are called adapters. The superclass-subclass relationship is said to be an is-a relationship. When designing object hierarchies, object modellers use subclassing when they can say that the subclass is the same class as the superclass. For example: {{{#!python class Shape: sideLength = 0 def getSideLength(self): return self.sideLength def setSideLength(self, sideLength): self.sideLength = sideLength def area(self): raise NotImplementedError, "Subclasses must implement area" class Triangle(Shape): def area(self): return (self.sideLength * self.sideLength) / 2 class Square(Shape): def area(self): return self.sideLength * self.sideLength }}} In the above example, a Triangle ''is-a'' Shape, so it subclasses Shape, and a Square ''is-a'' Shape, so it also subclasses Shape. However, subclassing can get complicated, especially when Multiple Inheritance enters the picture. Multiple Inheritance allows a class to inherit from more than one base class. Software which relies heavily on inheritance often ends up having both very wide and very deep inheritance trees, meaning that one class inherits from many superclasses spread throughout the system. Since subclassing with Multiple Inheritance means implementation inheritance, locating a method's actual implementation and ensuring the correct method is actually being invoked becomes a challenge. For example: {{{#!python class Area: sideLength = 0 def getSideLength(self): return self.sideLength def setSideLength(self, sideLength): self.sideLength = sideLength def area(self): raise NotImplementedError, "Subclasses must implement area" class Color: color = None def setColor(self, color): self.color = color def getColor(self): return self.color class Square(Area, Color): def area(self): return self.sideLength * self.sideLength }}} The reason programmers like using implementation inheritance is because it makes code easier to read since the implementation details of Area are in a separate place than the implementation details of Color. This is nice, because conceivably an object could have a color but not an area, or an area but not a color. The problem, though, is that Square is not really an Area or a Color, but has an area and color. Thus, we should really be using another object oriented technique called composition, which relies on delegation rather than inheritance to break code into small reusable chunks. Let us continue with the Multiple Inheritance example, though, because it is often used in practice. What if both the Color and the Area base class defined the same method, perhaps calculate? Where would the implementation come from? The implementation that is located for Square().calculate() depends on the method resolution order, or MRO, and can change when programmers change seemingly unrelated things by refactoring classes in other parts of the system, causing obscure bugs. Our first thought might be to change the calculate method name to avoid name clashes, to perhaps calculateArea and calculateColor. While explicit, this change could potentially require a large number of changes throughout a system, and is error-prone, especially when attempting to integrate two systems which you didn't write. Let's imagine another example. We have an electric appliance, say a hair dryer. The hair dryer is american voltage. We have two electric sockets, one of them an american 110 Volt socket, and one of them a foreign 220 Volt socket. If we plug the hair dryer into the 220 Volt socket, it is going to expect 110 Volt current and errors will result. Going back and changing the hair dryer to support both plug110Volt and plug220Volt methods would be tedious, and what if we decided we needed to plug the hair dryer into yet another type of socket? For example: {{{#!python class HairDryer: def plug(self, socket): if socket.voltage() == 110: print "I was plugged in properly and am operating." else: print "I was plugged in improperly and " print "now you have no hair dryer any more." class AmericanSocket: def voltage(self): return 110 class ForeignSocket: def voltage(self): return 220 }}} Given these classes, the following operations can be performed: {{{ >>> hd = HairDryer() >>> am = AmericanSocket() >>> hd.plug(am) I was plugged in properly and am operating. >>> fs = ForeignSocket() >>> hd.plug(fs) I was plugged in improperly and now you have no hair dryer any more. }}} We are going to attempt to solve this problem by writing an Adapter for the ForeignSocket which converts the voltage for use with an American hair dryer. An Adapter is a class which is constructed with one and only one argument, the adaptee or original object. There is a simple implementation in twisted.python.components.Adapter which defines the __init__ shown below, so you can subclass it if you desire. In this example, we will show all code involved for clarity: {{{#!python class AdaptToAmericanSocket: def __init__(self, original): self.original = original def voltage(self): return self.original.voltage() / 2 }}} Now, we can use it as so: {{{ >>> hd = HairDryer() >>> fs = ForeignSocket() >>> adapted = AdaptToAmericanSocket(fs) >>> hd.plug(adapted) I was plugged in properly and am operating. }}} So, as you can see, an adapter can 'override' the original implementation. It can also 'extend' the interface of the original object by providing methods the original object did not have. Note that an Adapter must explicitly delegate any method calls it does not wish to modify to the original, otherwise the Adapter cannot be used in places where the original is expected. Usually this is not a problem, as an Adapter is created to conform an object to a particular interface and then discarded. |
高阶Twisted (High-Level Twisted) -- 令狐虫
工具的使用(Using the utilities)
Application
Twisted程序通常和twisted.application.service.Application协作。这个类通常管理着一个运行中的服务器的所有持久化配置信息 —— 需要绑定的端口、必须被保持或者被尝试的连接位置、需要周期性完成的动作,以及几乎所有的一切。它是服务树中的根对象,实现了IService接口。
Twisted programs usually work with twisted.application.service.Application. This class usually holds all persistent configuration of a running server -- ports to bind to, places where connections to must be kept or attempted, periodic actions to do and almost everything else. It is the root object in a tree of services implementing IService.
其他的HOWTO们描述了如何为一个应用编写客户代码,但是本节描述的是如何使用已经写好的代码(这些代码可能是Twisted的一部分,也可能来自一个第三方的Twisted插件开发者)。Twisted的发布中包含了用户建立和维护Application所需的各种各样的工具。
Other HOWTOs describe how to write custom code for Applications, but this one describes how to use already written code (which can be part of Twisted or from a third-party Twisted plugin developer). The Twisted distribution comes with an assortment of tools to create and manipulate ApplicationS.
Application是一种Python对象,可以像其他对象一样被建立和维护。特别的一点是,它们可以被序列化到文件中。Twisted支持几种序列化格式。
ApplicationS are just Python objects, which can be created and manipulated in the same ways as any other object. In particular, they can be serialized to files. Twisted supports several serialization formats.
序列化(Serialization)
TAP Twisted Application Pickle。这种格式由本地的Python pickle包提供支持。这种格式虽然人类看不懂,但是存取是最快的。 A Twisted Application Pickle. This format is supported by the native Python pickle support. While not being human readable, this format is the fastest to load and save.
TAX Twisted 包含 twisted.persisted.marmalade, 一个支持对遵循XML标准的一种格式进行序列化和解序列化的模块。这种格式是人类可读可编辑的。 Twisted contains twisted.persisted.marmalade, a module that supports serializing and deserializing from a format which follows the XML standard. This format is human readable and editable.
TAS Twisted 包含 twisted.persisted.aot,一个支持序列化为Python代码的模块。它的好处在于使用了Python自身的语法分析器,并且以后可以手工将这段代码加入到文件中去。 Twisted contains twisted.persisted.aot, a module that supports serializing into Python source. This has the advantage of using Python's own parser and being able to later manually add Python code to the file.
mktap和tapconvert(mktap and tapconvert)
mktap(1)工具是建立TAP(或者TAX或者TAS)文件的主要途径。它可以用来建立所有主流Twisted服务器类型——比如web、ftp或IRC——的应用。它同时也支持插件,因此当你安装一个Twisted插件(其实就是将它解压到你PYTHONPATH所指定的目录中)它会自动检测并在所有支持该插件的Twisted应用中使用它。它可以生成上述的任何一种应用格式。
The mktap(1) utility is the main way to create a TAP (or TAX or TAS) file. It can be used to create an Application for all of the major Twisted server types like web, ftp or IRC. It also supports plugins, so when you install a Twisted plugin (that is, unpack it into a directory on your PYTHONPATH) it will automatically detect it and use any Twisted Application support in it. It can create any of the above Application formats.
想了解哪些服务器类型是可用的,使用 mktap --help。mktap --help <名称> 显示一个给定服务器的可能的配置选项。mktap支持一些配置应用所需的通用选项——要了解完整的细节,可以查看man page。
In order to see which server types are available, use mktap --help. For a given server, mktap --help <name> shows the possible configuration options. mktap supports a number of generic options to configure the application -- for full details, read the man page.
有一个重要的选项是 --append <文件名>。它用于向一个已经被序列化的Twisted应用中增加一个服务器。举例来说,它可以用来增加一个telnet服务器,使你可以通过telnet来检测和重新配置应用。
One important option is --append <filename>. This is used when there is already a Twisted application serialized to which a server should be added. For example, it can be used to add a telnet server, which would let you probe and reconfigure the application by telnetting into it.
另一个有用的工具是tapconvert(1),它用于在三种应用格式之间进行转换。
Another useful utility is tapconvert(1), which converts between all three Application formats.
twistd
拥有一个表现为各种格式的Application,也许在美学角度上是令人愉悦的,但是却不能实际的导致任何事情的发生。因此,我们需要一个程序为死的应用带来生机。在UNIX系统中(并且,在有其他的选择之前,对于别的操作系统也一样),这个程序就是twistd(1)。严格的说,twistd并不是必须的——解序列化应用,得到IService组件,调用startService,当反映器(reactor)关闭时调用stopService,然后调用reactor.run()都能手工完成。然而twistd(1)提供了许多选项可以非常有效的对程序进行设置。
Having an Application in a variety of formats, aesthetically pleasing as it may be, does not actually cause anything to happen. For that, we need a program which takes a dead Application and brings life to it. For UNIX systems (and, until there are are alternatives, for other operating systems too), this program is twistd(1). Strictly speaking, twistd is not necessary -- unserializing the application, getting the IService component, calling startService, scheduling stopService when the reactor shuts down, and then calling reactor.run() could be done manually. twistd(1), however, supplies many options which are highly useful for program set up.
twistd支持选择反映器(reactor)(更对关于reactors的信息,参见“选择反映器(Choosing a Reactor)”),向日志文件记录日志,开启守护进程以及其他功能。twistd支持上面提到的所有应用——和一个额外的。有时,直接用Python写代码来构建一个类很方便。在doc/examples目录里有一个这样的大型例子。当使用在Python文件里直接定义一个叫做application的Application对象的方法的时候,使用 -y 选项。
twistd supports choosing a reactor (for more on reactors, see Choosing a Reactor), logging to a logfile, daemonizing and more. twistd supports all Applications mentioned above -- and an additional one. Sometimes is is convenient to write the code for building a class in straight Python. One big source of such Python files is the doc/examples directory. When a straight Python file which defines an Application object called application is used, use the -y option.
当twistd运行的时候,它把它的进程id记录在twistd.pid文件中(这可以通过命令行开关来进行配置)。为了关闭twistd进程,杀掉那个进程的pid(你通常需要 kill `cat twistd.pid`)。当这个进程被以正常形式杀掉之后,它会留下一个shutdown Application,并在这个Application的原名后面加上-shutdown。它包含一个新的,被应用更改了的配置信息。例如,关闭web.tap会产生一个web-shutdown.tap的新文件。
When twistd runs, it records its process id in a twistd.pid file (this can be configured via a command line switch). In order to shutdown the twistd process, kill that pid (usually you would do kill cat twisted.pid). When the process is killed in an orderly fashion it will leave behind the shutdown Application which is named the same as the original file with a -shutdown added to its base name. This contains the new configuration information, as changed in the application. For example, web.tap when shutdown will have an additional file, web-shutdown.tap.
同样的,可怕的细节都在手册页中。
As always, the gory details are in the manual page.
tap2deb
Twisted 为那些想在Debian上部署基于Twisted服务器的应用的开发者们提供了 tap2deb 程序。这个程序将Twisted应用文件(的任意一种格式——Python、源代码、xml或pickle)封装成一个Debian包,包括正确的安装和删除脚本,以及init.d脚本。这将安装者从人工停止和启动服务的工作中解放出来,并且保证了它在启动和关闭时正确的遵循init级别。 For Twisted-based server application developers who want to deploy on Debian, Twisted supplies the tap2deb program. This program wraps a Twisted Application file (of any of the supported formats -- Python, source, xml or pickle) in a Debian package, including correct installation and removal scripts and init.d scripts. This frees the installer from manually stopping or starting the service, and will make sure it goes properly up on startup and down on shutdown and that it obeys the init levels.
tap2deb同样也可以为那些Debian的高级用户产生源码包,允许她修改和改进那些自动软件不能检测的东西(比如和虚拟包的依赖或关联)。另外,Twisted小组自己也打算出品一些常见服务的Debian包,比如Web服务器和inetd的替代品。那些包将会使全世界的精英们感到高兴——无论是依赖tap2deb产生的,还是Debian维护者的精美手工维护工具,都可以保证和Debian的完美集成。
For the more savvy Debian users, the tap2deb also generates the source package, allowing her to modify and polish things which automated software cannot detect (such as dependencies or relationships to virtual packages). In addition, the Twisted team itself intends to produce Debian packages for some common services, such as web servers and an inetd replacement. Those packages will enjoy the best of all worlds -- both the consistency which comes from being based on the tap2deb and the delicate manual tweaking of a Debian maintainer, insuring perfect integration with Debian.
目前,在Moshe的文档里有一个Web服务器的beta版Debian档案。
Right now, there is a beta Debian archive of a web server available at Moshe's archive.
tap2rpm
tap2rpm类似于tap2deb,只是它产生的是用于Redhat以及其他相关平台的RPM包。
tap2rpm is similar to tap2deb, except that it generates RPMs for Redhat and other related platforms.
Twisted Components: Interfaces and Adapters
twisted.python.components: Twisted's implementation of Interfaces and Components
Components and Inheritance
Object oriented programming languages allow programmers to reuse portions of existing code by creating new classes of objects which subclass another class. When a class subclasses another, it is said to inherit all of its behaviour. The subclass can then override and extend the behavior provided to it by the superclass. Inheritance is very useful in many situations, but because it is so convenient to use, often becomes abused in large software systems, especially when multiple inheritance is involved. One solution is to use delegation instead of inheritance where appropriate. Delegation is simply the act of asking another object to perform a task for an object. To support this design pattern, which is often referred to as the components pattern because it involves many small interacting components, interfaces and adapters were created by the Zope 3 team.
Interfaces are simply markers which objects can use to say I implement this interface. Other objects may then make requests like Please give me an object which implements interface X for object type Y. Objects which implement an interface for another object type are called adapters.
The superclass-subclass relationship is said to be an is-a relationship. When designing object hierarchies, object modellers use subclassing when they can say that the subclass is the same class as the superclass. For example:
1 class Shape:
2 sideLength = 0
3 def getSideLength(self):
4 return self.sideLength
5
6 def setSideLength(self, sideLength):
7 self.sideLength = sideLength
8
9 def area(self):
10 raise NotImplementedError, "Subclasses must implement area"
11
12 class Triangle(Shape):
13 def area(self):
14 return (self.sideLength * self.sideLength) / 2
15
16 class Square(Shape):
17 def area(self):
18 return self.sideLength * self.sideLength
In the above example, a Triangle is-a Shape, so it subclasses Shape, and a Square is-a Shape, so it also subclasses Shape.
However, subclassing can get complicated, especially when Multiple Inheritance enters the picture. Multiple Inheritance allows a class to inherit from more than one base class. Software which relies heavily on inheritance often ends up having both very wide and very deep inheritance trees, meaning that one class inherits from many superclasses spread throughout the system. Since subclassing with Multiple Inheritance means implementation inheritance, locating a method's actual implementation and ensuring the correct method is actually being invoked becomes a challenge. For example:
1 class Area:
2 sideLength = 0
3 def getSideLength(self):
4 return self.sideLength
5
6 def setSideLength(self, sideLength):
7 self.sideLength = sideLength
8
9 def area(self):
10 raise NotImplementedError, "Subclasses must implement area"
11
12 class Color:
13 color = None
14 def setColor(self, color):
15 self.color = color
16
17 def getColor(self):
18 return self.color
19
20 class Square(Area, Color):
21 def area(self):
22 return self.sideLength * self.sideLength
The reason programmers like using implementation inheritance is because it makes code easier to read since the implementation details of Area are in a separate place than the implementation details of Color. This is nice, because conceivably an object could have a color but not an area, or an area but not a color. The problem, though, is that Square is not really an Area or a Color, but has an area and color. Thus, we should really be using another object oriented technique called composition, which relies on delegation rather than inheritance to break code into small reusable chunks. Let us continue with the Multiple Inheritance example, though, because it is often used in practice.
What if both the Color and the Area base class defined the same method, perhaps calculate? Where would the implementation come from? The implementation that is located for Square().calculate() depends on the method resolution order, or MRO, and can change when programmers change seemingly unrelated things by refactoring classes in other parts of the system, causing obscure bugs. Our first thought might be to change the calculate method name to avoid name clashes, to perhaps calculateArea and calculateColor. While explicit, this change could potentially require a large number of changes throughout a system, and is error-prone, especially when attempting to integrate two systems which you didn't write.
Let's imagine another example. We have an electric appliance, say a hair dryer. The hair dryer is american voltage. We have two electric sockets, one of them an american 110 Volt socket, and one of them a foreign 220 Volt socket. If we plug the hair dryer into the 220 Volt socket, it is going to expect 110 Volt current and errors will result. Going back and changing the hair dryer to support both plug110Volt and plug220Volt methods would be tedious, and what if we decided we needed to plug the hair dryer into yet another type of socket? For example:
1 class HairDryer:
2 def plug(self, socket):
3 if socket.voltage() == 110:
4 print "I was plugged in properly and am operating."
5 else:
6 print "I was plugged in improperly and "
7 print "now you have no hair dryer any more."
8
9 class AmericanSocket:
10 def voltage(self):
11 return 110
12
13 class ForeignSocket:
14 def voltage(self):
15 return 220
Given these classes, the following operations can be performed:
>>> hd = HairDryer() >>> am = AmericanSocket() >>> hd.plug(am) I was plugged in properly and am operating. >>> fs = ForeignSocket() >>> hd.plug(fs) I was plugged in improperly and now you have no hair dryer any more.
We are going to attempt to solve this problem by writing an Adapter for the ForeignSocket which converts the voltage for use with an American hair dryer. An Adapter is a class which is constructed with one and only one argument, the adaptee or original object. There is a simple implementation in twisted.python.components.Adapter which defines the init shown below, so you can subclass it if you desire. In this example, we will show all code involved for clarity:
Now, we can use it as so:
>>> hd = HairDryer() >>> fs = ForeignSocket() >>> adapted = AdaptToAmericanSocket(fs) >>> hd.plug(adapted) I was plugged in properly and am operating.
So, as you can see, an adapter can 'override' the original implementation. It can also 'extend' the interface of the original object by providing methods the original object did not have. Note that an Adapter must explicitly delegate any method calls it does not wish to modify to the original, otherwise the Adapter cannot be used in places where the original is expected. Usually this is not a problem, as an Adapter is created to conform an object to a particular interface and then discarded.