注:翻译得不是很好,欢迎指正
原文: http://trac.edgewall.org/wiki/TracDev/ComponentArchitecture
Trac Component Architecture¶
Trac组件架构As the heart of Trac, trac.core implements a minimal
component kernel that allows components to easily extend each others'
functionality. It provides a "meta-plugin-API" : every component can
easily offer its own plugin API by declaring "extension points" .
trac.core作为trac的核心,实现了一个小型的组件内核,这个内核允许组件很容易扩展其他组件的功能。
它提供了一个"meta-plugin-API": 每一个组件可以很方便通过声明"扩展点"来提供它的插件API。
What is a Component?¶
什么是组件For our purposes, a component is an object that provides a certain
type of service within the context of the application. There is at most one
instance of any component: components are singletons. That implies that a
component does not map to an entity of the application's object
model; instead, components represent functional subsystems.
Components can declare "extension points" that other components can
“plug in” to. This allows one component to enhance the functionality of the
component it extends, without the extended component even knowing that the
extending component exists. All that is needed is that the original component
exposes – and uses – one or more extension points.
A component can extend any number of other components and still offer its own
extension points. This allows a plugin to itself offer a plugin API (i.e.
extension point). This feature is the basis for a plugin-based architecture.
The actual functionality and APIs are defined by individual components . The
component kernel provides the “magic glue” to hook the different subsystems
together – without them necessarily knowing about each other.
Public classes¶
公共类trac.core.ComponentManager
管理组件生命周期,处理组件注册实例的请求。Manages component life cycle, instantiating registered components on demand
trac.core.Component
组件的抽象基类Abstract base class for components.
trac.core.ExtensionPoint
在组件里声明一个扩展点,以便其他组件能够plugin in。Declares an extension point on a component that other components can plug in
to.
trac.core.Interface
每个扩展点规定了契约,扩展者必须是一个 Interface 子类Every extension point specifies the contract that extenders must conform to
via an Interface subclass.
Declaring a component¶
声明一个组件The simplest possible component is an empty class derived from
trac.core.Component :
from trac.core import *
class MyComponent ( Component):
pass
In the context of a component manager, this component can already be used:
在一个组件管理器的上下文里,组件可以这样使用:comp_mgr = ComponentManager()
my_comp = MyComponent( comp_mgr)
Remember that components follow the singleton pattern. There is only one
active instance of any component per component manager. The component
constructor is “magic” in that it checks with the component manager whether
there´s already an active instance before allocating a new instance. If the
component was already instantiated, the existing instance is returned:
my_comp1 = MyComponent( comp_mgr)
my_comp2 = MyComponent( comp_mgr)
assert id ( my_comp1) == id ( my_comp2)
If a component needs to initialize data members, it can override the
__init__ method. But because the component manager needs to be able to
instantiate the component on demand, __init__ must not require
any extra parameters, including the reference to the component manager
passed into the constructor:
from trac.core import *
class MyComponent ( Component):
def __init__ ( self ):
self . data = { }
comp_mgr = ComponentManager()
my_comp = MyComponent( comp_mgr)
Direct Component sub-classes also do not need to worry about
invoking the base classes __init__ method (which is empty).
Declaring an extension point¶
声明一个扩展点The component above doesn't actually do anything. Making an object a
component only makes it act as a singleton in the scope of a component manager,
which isn't that exciting in itself.
The real value of components becomes clearer when the facilities for
extensions are used. As a simple example, the following component provides an
extension point that lets other components listen to changes to the data it
manages (in this case a list of to-do items ) – following the widely known
observable pattern:
from trac.core import *
class ITodoObserver ( Interface):
def todo_added ( name, description):
"""Called when a to-do item is added."""
class TodoList ( Component):
observers = ExtensionPoint( ITodoObserver)
def __init__ ( self ):
self . todos = { }
def add ( self , name, description):
assert not name in self . todos, 'To-do already in list'
self . todos[ name] = description
for observer in self . observers:
observer. todo_added( name, description)
Here, the TodoList class declares an extension point called
observers with the interface ITodoObserver . The interface
defines the contract that extending components need to conform to.
The TodoList component notifies the observers inside the
add() method by iterating over self.observers and calling the
todo_added() method for each. This works because the observers
attribute is a descriptor : When it is accessed, it finds all registered
components that declare to extend the extension point. For each of those
components, it gets the instance from the component manager, potentially
activating it if it is getting accessed for the first time.
Plugging in to an extension point¶
扩展一个扩展点Now that we have an extendable component, let's add another component that
extends it:
class TodoPrinter ( Component):
implements( ITodoObserver)
def todo_added ( self , name, description):
print 'TODO:' , name
print ' ' , description
This class implements the ITodoObserver interface declared
above, and simply prints every new to-do item to the console. By declaring to
implement the interface, it transparently registers itself as an extension of
the TodoList class.
注意,实际上你不能把组件继承与它要实现的接口。这是因为接口的一致性与继承是正交的;
Note that you don't actually derive the component from
the interface it implements. That is because conformance to an interface is
orthogonal to inheritance; and because Python doesn't have static typing,
there's no need to explicitly mark the component as implementing an
interface.
You can specify multiple extension point interfaces to extend with the
implements method by simply passing them as additional arguments.
Putting it together¶
把它们放在一起Now that we've declared both a component exposing an extension point, and
another component extending that extension point, let's use the to-do list
example to see what happens:
comp_mgr = ComponentManager()
todo_list = TodoList( comp_mgr)
todo_list. add( 'Make coffee' ,
'Really need to make some coffee' )
todo_list. add( 'Bug triage' ,
'Double-check that all known issues were addressed' )
Running this script will produce the following output:
运行这个脚本,会生成以下输出:
TODO: Make coffee
Really need to make some coffee
TODO: Bug triage
Double-check that all known issues were addressed
This output obviously comes from the TodoPrinter . Note however that
the code snippet above doesn't even mention that class. All that is needed to
have it participating in the action is to declare the class . (That implies
that an extending class needs to be imported by a python script to be
registered. The aspect of loading components is however separate from the
extension mechanism itself .)
这个输出结果显然来自于 TodoPrinter。注意虽然上面的代码片段甚至没有提到这个类。而需要声明这个类,它能参与这个动作。( 这意味着一个扩展类需要通过导入python脚本来注册。把扩展机制和加载组件分离)
How Trac Uses Components¶
Trac是如何使用组件The typical use of components in Trac starts with a top-level “service
provider” that we'll pick up and use directly. Take, for example, trac.perm.PermissionSystem :
在Trac的组件的通常用法,一开始启动一个最高 级别的"服务提供者",我们可以直接获得和使用。
拿trac.perm.PermissionSystem作为例子:
permission_system = trac. perm. PermissionSystem( env)
actions = permission_system. get_permission_actions()
组件用 environment来初始化
Note that trac.env.Environment inherits
trac.core.ComponentManager , so you'll typically see components
initialized with an environment.注意trac.env.Environment 是继承于trac.core.ComponentManager,所以你可以通常可以看到
These are the first few lines of PermissionSystem as of r5790 (or in context ):
这里有r5790版本的 PermissionSystem 的前几行:class PermissionSystem ( Component):
"""Sub-system that manages user permissions."""
implements( IPermissionRequestor)
requestors = ExtensionPoint( IPermissionRequestor)
Note that this Component :
注意这个组件:- implements the IPermissionRequestor interface . 实现了IPermissionRequestor 接口。
- has an extension point for registering all the Components implementing
IPermissionRequestor (in context ): 有一个扩展点,所有注册的组件都可以实现IPermissionRequestor 。
class IPermissionRequestor ( Interface):
"""Extension point interface for components that define actions."""
def get_permission_actions ():
"""Return a list of actions defined by this component."""
Note that interface authors have not always been consistent about
declaring the “self ” parameter in signatures.注意接口的定义者已经没有符合在签名里(注,方法)定义 "self"参数。
When we use PermissionSystem , the plugin system will have
automatically gathered up all implementations of IPermissionRequestor
and placed them in PermissionSystem 's list of requestors . In
this specific case PermissionSystem will be part of that list as well,
because it implements the IPermissionRequestor interface. In no way a
Component is bound to implement the interfaces it declares an extension point
for, the two operations being entirely independent. But when that make sense,
it's entirely possible to do so.
Note: it's certainly debatable whether it makes sense in this particular
case—but if you do decide to do it, watch out for infinite
recursion as PermissionStore does here .
注意: 当然在这种情况下有意义的是有争议--但当你决定这样做,当心无穷递归,就像这里的
PermissionStore
Next in PermissionSystem there is a declaration of an
ExtensionOption called store :
接下来PermissionSystem 声明了一个ExtensionOption 的store:
store = ExtensionOption( 'trac' , 'permission_store' , IPermissionStore,
'DefaultPermissionStore' ,
"""Name of the component implementing `IPermissionStore`, which is used
for managing user and group permissions.""" )
The above adds an option called permission_store to
trac.ini , declares that the component named by the option implements
IPermissionStore , and sets its default to
DefaultPermissionStore . See trac.config for
ExtensionOption and friends. Methods of service providers such as
PermissionSystem are commonly a thin forwarding layer over such an
ExtensionOption . For example:
def get_all_permissions ( self ):
"""Return all permissions for all users.
The permissions are returned as a list of (subject, action)
formatted tuples."""
return self . store. get_all_permissions()
Thus, service providers are directly manipulated from Python , and are
customized through the automatic aggregation of components implementing
ExtensionPoint s and through configuration of ExtensionOption s
by Trac administrators.