Zope3宝典/组件架构介绍

来自Ubuntu中文
Oneleaf留言 | 贡献2007年5月31日 (四) 09:26的版本 (新页面: == Chapter 7:The Component Architecture - An Introduction(第 7 章:组件架构-介绍) == ---- 原文出处:[http://dev.zope.org/Wikis/DevSite/Projects/ComponentArchitecture/F...)
(差异) ←上一版本 | 最后版本 (差异) | 下一版本→ (差异)
跳到导航跳到搜索

Chapter 7:The Component Architecture - An Introduction(第 7 章:组件架构-介绍)


原文出处:The Zope 3 Developers Book - An Introduction for Python Programmers

原文作者:StephanRichter, zope.org

授权许可:创作共用协议

翻译人员:

校对人员:Leal, FireHare

适用版本:Zope 3

文章状态:校正阶段



Difficulty(难度)

Newcomer(新手)

Skills(技能)

  • Be familiar with object-oriented programming.
    熟悉面向对象编程。
  • Be knowledgeable about interfaces, i.e. by reading the previous chapter on interfaces.
    有接口的相关知识,例如通过学习前面的章节。
  • Knowledge of component-oriented programming is preferable. Optional.
    如果有面向组件编程的知识将更好。(可选)

Problem/Task(问题/任务)

When the Component Architecture for Zope was first thought about, it was intended as an extension to Zope 2, not a replacement as it developed to become. The issue was that the existing Zope 2 API was too bloated and inconsistent, due to constant feature additions, bug fixes and coding inconsistencies. The extremely bad practice of “monkey patching” became a normality among developers to overcome the limitations of the API and fix bugs. Monkey patching is a method of overwriting library functions and class methods after importing them, which is a powerful, but dangerous, side effect of loosely-typed scripting languages.
当组件架构被第一次思考的时候,它是作为一个Zope2的扩展,并不是像现在这样替代者。问题是由于增加持久性的特性、修正错误和代码矛盾,导致Zope2的API非常臃肿和不协调。为了克服 API 的限制和修正错误,“monkey patching”这种非常糟糕的现象在开发人员之间变得十分正常。monkey patching 是导入之后重写库函数和类方法的一种方法,十分强大但在宽松的脚本语言中非常危险。

Another motivation was to incorporate the lessons learned from consulting jobs and building large Web applications, which demonstrated the practical limits of simple object inheritance. The need for a more loosely-connected architecture arose with many objects having rather tiny interfaces in contrast to Zope 2’s few, large objects. This type of framework would also drastically reduce the learning curve, since a developer would need to learn fewer APIs to accomplish a given task.
另一个动机是组合从咨询工作中学到的课程和建立大型的Web应用。这些证明了单对象继承所带来的实际限制。对松散连接架构越来越多的需求也引发了使用微型接口的对象而不是Zope2中又少又巨大的对象。这种类型的框架也将彻底降低学习曲线,因为开发者只需要学习很少的 API 就可以完成给与的任务。

All these requirements pointed to a component-oriented framework that is now known as the “Component Architecture” of Zope 3. Many large software projects have already turned to component-based systems. Some of the better-known projects include:
所有的这些需求都指向一个面向组件的框架,在Zope3里叫做"组件架构(Component Architecture)"。很多大型软件项目都已经转型为面向组件的系统了。下面是一些有名的项目:

  • COM (Microsoft’s Object Model)
  • Corba (Open source object communication protocol)
    Corba (开源的对象通信协议)
  • KParts (from the KDE project)
    KParts (来自KDE项目)
  • Mozilla API (it is some ways very similar to Zope 3’s CA)
    Mozilla API (在某些地方与Zope3的CA非常相像)
  • JMX (Sun’s Java Management Extensions manages Beans)

However, while Zope 3 has many similarities with the above architectures, thanks to Python certain flexibilities are possible that compiled languages do not allow.
然而,虽然Zope3与上面的架构有很多相似之处,还是要感谢Python提供了这样强大的灵活性来使它变得可能,而其他的编译型的语言是不允许的。

Solution(解决方案)

In this chapter I will give you a high-level introduction to all component types. Throughout the book there will be concrete examples on developing most of the component types introduced here.
在这一章,我将向你进行一个对所有组件类型的高级的介绍。在本书中将有大部分使用这里介绍过的组件类型来开发的具体例子。

7.1 Services(7.1 服务)

Services provide fundamental functionality without which the application server would fail to function. They correspond to “Tools” in CMF (Zope 2) from which some of the semantics were also inspired.
服务是在应用服务器没有失去作用的时候提供的基础的功能性。他们相当于CMF(Zope 2)中符合"工具",并从中得到些许的语义上的灵感。

Services do not depend on other components at all. You only interact with other components by passing them as arguments to the constructor or the methods of the service. Any given application should have only a few services that cover the most fundamental functionality. When dealing with locality, services should always delegate requests upward - up to the global version of the service - if a request can not be answered locally.
服务完全不依赖于其他组件。你只需要向其他组件的构造函数或者服务方法传递参数来与它们交互。任何应用都应该只有少数的服务涉及到最基础的功能。当处理本地的请求时,如果不能在本地得到响应,那么服务应该永远将请求向上委派,直到服务的全局版本。

The most fundamental services are the registries of the components themselves. Whenever you register a class as a utility using ZCML, for example, then the class is registered in the “Utility Service” and can be retrieved later using the service. And yes, we also have a “Service Service” that manages all registered services.
最基本的服务是组件注册他们自己。例如,每当你利用ZCML将一个类注册为一个utility的时候,那个类是被注册在"Utility Service"里的,并且可以随后利用服务检索出来。举例来说,每当你使用ZCML将类注册为一个实用工具的时候,这个类就注册到在"Utility Service"里,并且可以随后通过使用该服务来检索到它。是的,我们还有一个 "服务的服务(Service Service)"来管理所有的注册服务。

Another service is the Error Reporting service, which records all errors that occurred during the publication process of a page. It allows a developer to review the details of the error and the state of the system/request at the time the error occurred.
另外的一个服务是错误报告(Error Reporting)服务, 用来记录所有的在页面发布过程中产生的错误。当错误发生的时候,它允许开发者检讨错误的细节和检讨系统/请求的状态。

attachment:figure_7_1.png

Figure 7.1:UML diagram of the Error Reporting Service.
图7.1 错误报告服务的 UML 图表

A convention for service interfaces is that they only contain accessor methods. Mutator methods are usually implementation-specific and are provided by additional interfaces. A consequence of this pattern is that services are usually not modified once the system is running. Please note though that we strongly discourage developers from writing services for applications. Please use utilities instead.
服务接口的一个习惯性用法是他们只包含"访问者(accessor)"方法。整流器(Mutator)方法通常是特别实现的并且是由附加的接口提供的。这种模式的结果是一旦系统在运行中,那么服务通常不能被修改。请注意我们强烈地劝诫开发者放弃为应用编写服务。请改为使用实用工具(utilities)。

Services make use of acquisition by delegating a query if it cannot be answered locally. For example, if I want to find a utility named “hello 1” providing the interface IHello and it cannot be found at my local site, then the Utility Service will delegate the request to the parent site. This goes all the way up to the global Utility Service. Only if the global service cannot provide a result, an error is returned. For more details about local and global components see “Global versus Local” below.
如果请求在本地不能得到回答,那么服务就藉由委派请求的方式来获得。举例来说,如果我想找一个名叫 "hello1" 的实用工具,它提供了IHello接口并且在本地站点没有找到,那么实用工具服务(Utility Service)就将该请求委派到父站点里。这些最终都会到达全局实用工具服务(Utility Service)。只有当全局服务也不能提供结果时,那么一个错误将被返回。更多关于本地和全局组件的内容请参考下面的"全局vs本地"章节。

7.2 Adapters(7.2 适配器)

Adapters could be considered the “glue components” of the architecture, since they connect one interface with another and allow various advanced programming techniques such as Aspect-Oriented Programming. An adapter takes a component implementing one interface and uses this object to provide another interface.
适配器(Adapter)可以被考虑为架构中的"胶合组件",他们将一个接口连接到另外一个接口而且允许各种各样的高级编程技术 , 像面向方面编程(Aspect-Oriented Programming)。一个适配器携带一个实现一个接口的组件,并使用这个对象提供另外一个接口。

attachment:figure_7_2.png

Figure 7.2: UML diagram of an Adapter adapting IExample to IReadFile and IWriteFile.
一个适配器的UML图表,将IExample适配为 IReadFile  IWriteFile

This allows the developer to split up the functionality into small API pieces and keep the functionality manageable. For example, one could write an adapter that allows an IExample content component to be represented as a file in FTP (see diagram above). This can be done by implementing the IReadFile and IWriteFile interface for the content component. Instead of adding this functionality directly to the SimpleExample class by implementing the interfaces in the class, we create an adapter that adapts IExample to IReadFile and IWriteFile . Once the adapter is registered for both interfaces (usually through ZCML), it can be used as follows:
这允许开发者将功能分割成小的API,并且可以保持这些功能的可维护性。举例来说,一个人可以写一个允许将IExample内容组件表现为FTP中的一个文件的适配器(见上面图表)。 这可以通过为内容组件实现 IReadFile IWriteFile 接口来做到。除了直接在 SimpleExample 类里面加入接口实现来达到目的以外,我们还可以创建一个适配器来将IExample适配到 IReadFile IWriteFile 。一但适配器被两个接口注册了 ( 通常经过 ZCML),它可以像下列这样被使用:

#!python
read_file = zapi.getAdapter(example, IReadFile)
write_file = zapi.getAdapter(example, IWriteFile)

The getAdapter() method finds an adapter that maps any of the interfaces that are implemented by example ( SimpleExample instance) to IReadFile. An optional argument named context can be passed as a keyword argument, specifying the place to look for the adapter. None causes the system to look only for global adapters. The default is the site the request was made from.
getAdapter() 方法找一个 IReadFile 的适配器,它可以映射任何一个在本例中( SimpleExample 的实例)实现的接口。一个可选的参数名叫context,可以作为一个关键字参数传递,用来指定查找适配器的地点。如果没有限定条件,则系统只查找全局适配器。默认为发出请求的站点。

In this particular case we adapted from one interface to another. But adapters can also adapt from several interface to another. These are known as multi-adapters. While multi-adapters were first thought of as unnecessary, they are now used in a wide range of applications.
在这个特别的情形中我们从一个接口适配到另外一个接口。但是适配器也能从一些接口适配到另外一个接口。这些即是多重适配器。虽然多重适配器在开始时被看作是多余的,但是它们现在有着广泛的应用。

The best side effect of adapters is that it was not necessary to touch the original implementation SimpleExample at all. This means that I can use any Python product in Zope 3 by integrating it using adapters and ZCML.
适配器最好的副作用就是你根本不需要碰 SimpleExample 的原始实现。这就意味着我可以通过使用适配器和ZCML来在Zope3中集成任何Python产品。

7.3 Utilities(7.3 实用工具)

Utilities are similar to services, but do not provide vital functionality, so applications should not be broken if utilities are missing. This statement should be clarified by an example.
实用工具(Utilities)和服务很相像,但是不提供重要的功能,所以当使用工具丢失的时候应用程序应该不会被破坏。这句话应该用一个例子来阐述。

In pre-alpha development of Zope 3, SQL Connections to various relational databases were managed by a service. The SQL Connection Service would manage SQL connections and the user could then ask the service for SQL connections by name. If a connection was not available, then the service would give a negative answer. Then we realized the role of utilities, and we were able to rid ourselves of the SQL Connection Service and implement SQL connections as utilities. Now we can ask the Utility Service to give us an object implementing ISQLConnection and having a specified name. We realized that many services that merely acted as registries could be thrown out and the objects they managed became utilities. This greatly reduced the number of services and the complexity of the system. The lesson here is that before you develop a service, evaluate whether it would just act as a container, in which case the functionality is better implemented using utilities.
在Zope3的前阿尔发开发中,到不同关系数据库的SQL连接是由一个服务管理的。SQL 连接服务会处理 SQL 连接,而且使用者然后可以为名字的 SQL 连接服务。如果一个连接是无效的,那么服务会给一个否定的回答。这时我们了解了实用工具的角色,而且我们能够摆脱SQL连接服务(SQL Connection Service)并将SQL连接作为实用工具来实现。现在我们能要求实用工具服务(Utility Service)给我们一个实现ISQLConnection的对象,而且还有一个被指定的名字。我们意识到许多担当注册用途的服务是可以被丢弃的,而由它们管理的对象则变成了实用工具。这大大减少了服务的数量而且降低了系统的复杂度。本节的课程是,当你开发一个服务之前,应该先评估它是否仅仅担当一个容器,在这种情况下采用实用工具来实现这些功能将是更好的选择。

7.4 Factories (Object Classes/Prototypes) (7.4 工厂(对象类/属性))

Factories, as the name suggests, exist merely to create other components and objects. Factories can be methods, classes or even instances that are callable. The developer only encounters them directly when dealing with content objects (since ZCML creates factories for you automatically) if you specify the factory directive. The functionality and usefulness of factories is best described by an example.
工厂(Factories),如名字所说的那样,是为了创建其他的组件和对象而存在的。工厂可以是方法,类或甚至是可调用的实例。如果你指定了工厂指令,开发者就可以在处理内容对象的时候直接遇到它们了(因为ZCML可以自动为你创建工厂)。当处理满足的物体 (自 ZCML 以后为你自动地产生工厂) 如果你叙述工厂指令的时候 , 开发者直接地只遇到他们。工厂的功能和可用性可以通过一个例子来很好的描述出来。

Let’s consider our SimpleExample content component once more. A factory has to provide two methods. The obvious one is the <u>call</u>() method that creates and returns a SimpleExample instance. The second method, called getInterfaces() returns a list of interfaces that the object created by calling <u>call</u> will provide.
让我们再一次考虑我们的 SimpleExample 内容组件。一个工厂必须提供二个方法。明显的一个是<u>call</u>()方法,它创建并返回一个 SimpleExample 实例。第二个方法叫做getInterfaces(),它返回一个这个使用<u>call</u>方法创建的对象可提供的接口的列表。

Factories are simply named utilities that provide IFactory. Using a special sub-directive you can register a factory using an id, such as example. SimpleExample . Once the factory is registered, you can use the component architecture’s createObject() method to create Simple Examples using the factory (implicitly):
工厂被简单的命名为提供IFactory的utilities。通过使用一个特别的子指令的,你能使用一个id来注册一个工厂, 就像例子 SimpleExample 那样。一旦工厂被注册了,你就能使用组建架构的createObject()的方法隐式的使用工厂来创建Simple Examples:

#!python
ex = zapi.createObject('example.SimpleExample')

The argument is simply the factory id. By the way, a factory id must be unique in the entire system and the low-level functionality of factories is mostly hidden by high-level configuration. Optionally you can specify a context argument, that specifies the location you are looking in. By default it is the site of the request; if you specify None, only global factories are considered.
参数只是工厂的id。顺便一提,一个工厂id在整个的系统中必须是唯一的并且工厂的低级功能大部分被高级的配置所隐藏起来了。你可以指定一个可选性的上下文参数,它指定了你正在访问的位置。默认情况下,上下文是发起请求的站点;如果你指定为None,则只有全局的工厂被考虑。

7.5 Presentation Components - Views, Resources, Skins, Layers(7.5 显示组件--视图、资源、皮肤、层)

Presentation components, especially views, are very similar to adapters, except that they take additional parameters like layers and skins into account. In fact, in future versions of Zope 3, the presentation service will be removed and presentation components will become adapters. Presentation components are used for providing presentation for other components in various output formats (presentation types), such as HTML, FTP, XML-RPC and so on.
显示组件,尤其是视图,很类似于适配器的,除了他们似层皮肤那样使用附加参数之外。事实上,今后版本的zope3,此显示服务将被移除并且显示组件将变成适配器。显示组件用于为其它的组件提供多样的输出格式(显示类型),比如HTML,FTP,XML-RPC等等。

In order to make a view work, two pieces of information have to be provided. First, the view must know for which object it is providing a view. This object is commonly known as the context of the view. Second, we need to know some protocol-specific information, which is stored in a Request object that is always accessible under the variable name request in the view. For HTML, for example, the request contains all cookies, form variables and HTTP header values, but also the authenticated user and the applicable locale. The return values of the methods of a view depend on the presentation type and the method itself. For example, HTTP views usually return the HTML body, whereas FTP might return a list of “filenames”.
为了使得一个视图工作,二条信息必须被提供。首先,此视图必须知道它为哪些对象提供视图。这个对象通常被认为是视图的上下文。其次,我们需要知道一些协议特指的信息,它们存储在请求对象中,该对象在视图中可永远通过此请求的变量名来访问。对于HTML,例如,请求包含所有的cookies,表单变量和HTTP头信息值,也包含授权用户和可应用的场所。一个视图的方法的返回值依赖于显示的类型和方法它自己。视图方法的返回值取决于表现类型和方法本身。例如,HTTP 视图通常的返回HTML体,而FTP 可能返回"文件名"的列表。

Resources are presentation components in their own right. In comparison to views, they do not provide a presentation of another component but provide some presentation output that is independent of any other component. HTML is a prime example. Stylesheets and images (layout) are often not considered content and also do not depend on any content object, yet they are presentation for the HTTP presentation type. However, not all presentation types require resources; both FTP and XML-RPC do not have such independent presentation components.
资源是显示组件在他们自己的权力。与视图相比,他们没有提供显示另一个组件,但是提供一些独立于任何的其它的组件的显示输出。HTML是一个主要的例子。样式表和图像(布局)经常不被认为是内容,并且同样没有依赖于任何的内容对象,但是他们是HTTP显示类型。然而,不是所有的显示类型都要求资源;FTP和XML-RPC两者都没有这样的独立的显示组件。

Next, views and resources are grouped by layers, which are usually used for grouping similar look and feel. In Zope 2’s CMF framework, layers are the folders contained by the portal_skins tool. An example for a layer is debug, which simply inserts Python tracebacks into exception HTML pages.
然后,视图和资源通过层来分组,层通常的被用来分组相似的外观和感觉。在Zope2的CMF 框架,层是被包含在门户皮肤工具里面的文件夹。一个层的例子是debug,它简单插入Python的tracebacks到异常的HTML页面中。

Multiple layers can then be stacked together to skins. Currently we have several skins in the Zope 3 core: “rotterdam” (default), “Basic”, “Debug”, and “ZopeTop” (excluded from the 3.0 distribution). The skin can simply be changed by typing ++skin++SKINNAME after the root of your URL, for example:
多重的层可以被堆叠为皮肤。当前我们在Zope3核心中有几个皮肤:"rotterdam(默认)","Basic ","Debug "和" ZopeTop(在3.0分发版中被剔除)"。皮肤可以简单通过在你的根URL的后面键入++skin ++SKINNAME来改变,例如:

http://localhost:8080/++skin++ZopeTop/folder1

When you develop an end-user Web site, you definitely want to create your own layer and incorporate it as a new skin. You want to avoid writing views for your site that each enforce the look and feel. Instead you can use skins to create a look and feel that will work for all new and existing templates.
当你开发一个最终用户网站,你明确的想创建你们自己的层并且合并它为新皮肤。你想避免为你的站点编写视图并分别执行此外观和感觉。你可以使用皮肤来创建一个外观,它将可用于所有新的和已经存在的模板。

7.6 Global versus Local(7.6 全局 VS 本地)

Zope 3 separates consciously between local and global components. Global components have no place associated with them and are therefore always reachable and present. They are always initialized and registered at Zope 3 startup via the Zope Configuration Markup Language (ZCML), which I mentioned before. Therefore, global components are not persistent meaning they are not stored in the ZODB at all. Their state is destroyed (and should be) upon server shutdown.
Zope3有意识的隔离本地和全局的组件。全局的组件没有关联它们的地方,并且因此它们将永远可以被达到和表现。 他们永远在Zope3启动的时候经由Zope配置标记语言(ZCML)来初始化和注册,就像我之前提到的那样。因此,全局的组件不是持久的,这意味着他们根本不是存储在ZODB里。 他们的状态在服务器关闭的时候被销毁(也应该被销毁)。

Local components, on the other hand, are stored in the ZODB at the place in the object tree they were defined in. Local components always only add and overwrite previous settings; they can never remove existing ones. Creating new local components can be done using the Web Interface by clicking “Manage Site”. This will lead you into the configuration namespace and is always marked by ++etc++site in the URL.
另一方面,本地组件是存储在ZODB里,位置在他们被定义的对象树里面。本地组件永远仅仅增加和重写先前的设置;他们永远不能删除现有的设置。可以利用点击Web界面上的"Manage Site"来创建新本地组件。这将领引你进入到配置名字空间之内并且永远使用URL里的++etc++site来标记。

Every folder can be promoted to become a site. Once a local site manager is declared for a folder by clicking on “Make Site”, we call this object/folder a site.
每个文件夹都可以被晋升为一个站点。 当通过点击"Make Site"来声明一个文件夹为一个本地站点管理器时,我们称这个对象/文件夹为一个站点。

As mentioned before, local components use an explicit acquisition process when looking up information. For example, I want to get the factory for SimpleExample .
象之前提到的那样,本地组件当查询信息时使用显式的采集处理。 例如,我想为 SimpleExample 得到工厂。

When looking for any component, the original site of the publication is chosen. However, sometimes it is desired to start looking for a component from a different site. In these cases you simply specify the context in the call.
当查询组件时,发布所使用的原始站点将被选中。然而,有时要求从一个不同的站点来启动查询组件。在这种情况下,你简单在调用里指定上下文即可。

#!python
factory = zapi.getFactory('example.SimpleExample', context=other_site)

If a local Utility Service exists and an IFactory utility with the name example. SimpleExample is found, then it is returned. If not, then the local Utility Service delegates the request to the next site. Requests can be delegated all the way up to the global Utility Service at which point an answer must be given. If the global Utility Service does not know the answer either, a ComponentLookupError is raised.
如果一个本地Utility Service存在并且找到一个名称为example. SimpleExample e的IFactory实用工具,那么它将被返回。否则,本地Utility Service委派这个请求到下一个站点。请求可以一直被委派到全局的Utility Service,在那里必须给出一个回答。如果全局的Utility Service也不知道答案,那么一个 ComponentLookupError 将被抛出。

We can see that there are slight semantic differences between global and local implementations of a service, besides the difference in data storage and accessibility. The global service never has to worry about place or the delegation of the request. The net effect is that global components are often easier to implement than their local equivalent. Furthermore, local components usually have writable APIs in addition to the readable ones, since they have to allow run-time management.
我们可以看见在一个服务的全局和本地实现之间有轻微语义上的差异,除了在数据存储和可访问性上的差异以外。全局服务从不担心请求的地方或者委派。净效应是全局的组件相对于他们的同等本地组件来说经常更加容易的实现。此外本地组件除了可读的APIs之外通常有可写的APIs,因为他们必须提供运行时的管理。