Zope接口的目的是什么?
我开始在代码中使用Zope接口,目前它们对我来说主要是用来做文档。我用它们来说明一个类应该具备哪些属性,然后在合适的类中明确实现这些属性,并在我预期会用到这些属性的地方进行检查。这种做法还不错,但我希望它们能做得更多,比如实际验证这个类是否真的实现了接口,而不仅仅是检查我是否声明了这个类实现了接口。我看过几次Zope的维基,但还是没能找到比我现在使用的方式更多的接口用法。所以,我想问问,这些接口还能用来做什么,怎么才能用得更好。
4 个回答
Zope接口可以帮助我们把两段代码分开,这样它们就不需要互相依赖了。
假设我们有一个组件,它在a.py模块里负责打印问候语:
>>> class Greeter(object):
... def greet(self):
... print 'Hello'
还有一些代码需要在b.py模块里打印问候语:
>>> Greeter().greet()
'Hello'
这种安排让我们很难在不修改b.py的情况下更换处理问候语的代码,因为b.py可能是单独打包分发的。为了避免这个问题,我们可以引入一个新的模块c.py,里面定义一个IGreeter接口:
>>> from zope.interface import Interface
>>> class IGreeter(Interface):
... def greet():
... """ Gives a greeting. """
现在我们可以用这个接口来解耦a.py和b.py。b.py不再直接创建一个Greeter类的实例,而是会请求一个提供IGreeter接口的工具。而a.py会声明Greeter类实现了这个接口:
(a.py)
>>> from zope.interface import implementer
>>> from zope.component import provideUtility
>>> from c import IGreeter
>>> @implementer(IGreeter)
... class Greeter(object):
... def greet(self):
... print 'Hello'
>>> provideUtility(Greeter(), IGreeter)
(b.py)
>>> from zope.component import getUtility
>>> from c import IGreeter
>>> greeter = getUtility(IGreeter)
>>> greeter.greet()
'Hello'
在我工作的地方,我们使用接口(Interfaces)来利用ZCA,也就是Zope组件架构。这个架构是为了创建可以互换和插拔的组件而设计的,使用接口可以让我们更灵活地处理每个客户的定制需求,而不需要修改我们的软件主干。可惜的是,Zope的维基文档常常不够完整。不过在它的pypi页面上,有对ZCA大部分功能的简要说明。
我并不使用接口来检查一个类是否实现了某个接口的所有方法。理论上,这在你给接口添加新方法时可能有用,可以检查你是否记得把新方法添加到所有实现该接口的类中。个人而言,我更喜欢创建一个新的接口,而不是修改旧的接口。一旦旧的接口已经发布到pypi或你的组织中,修改它通常是个坏主意。
关于术语的小提示:类是“实现”(implement)接口的,而对象(类的实例)是“提供”(provide)接口的。如果你想检查某个接口,可以写 ISomething.implementedBy(SomeClass)
或 ISomething.providedBy(some_object)
。
接下来,我们来看看ZCA在哪些方面有用。假设我们正在写一个博客,使用ZCA来使其模块化。我们会为每篇文章创建一个 BlogPost
对象,它会提供一个 IBlogPost
接口,所有这些都定义在我们的 my.blog
包中。我们还会把博客的配置存储在提供 IBlogConfiguration
的 BlogConfiguration
对象中。以此为起点,我们可以实现新功能,而不必触碰 my.blog
。
以下是一些使用ZCA而不需要修改基础 my.blog
包的例子。我和我的同事在实际的客户项目中做过这些事情(并且觉得很有用),尽管当时我们并没有在实现博客。:) 这里的一些用例可以通过其他方式更好地解决,比如使用打印CSS文件。
为所有提供
IBlogPost
的对象添加额外的视图(BrowserView
,通常在ZCML中用browser:page
指令注册)。我可以创建一个my.blog.printable
包。这个包会注册一个名为print
的浏览器视图,专门为IBlogPost
渲染博客文章,使用一个Zope页面模板,生成适合打印的HTML。这个BrowserView
将出现在URL/path/to/blogpost/@@print
。Zope中的事件订阅机制。假设我想发布RSS订阅,并且希望提前生成它们,而不是在请求时生成。我可以创建一个
my.blog.rss
包。在这个包中,我会为提供 IObjectModified(zope.lifecycleevent.interfaces.IObjectModified
)的事件注册一个订阅者,针对提供IBlogPost
的对象。每当提供IBlogPost
的对象的某个属性发生变化时,这个订阅者就会被调用,我可以用它来更新所有应该包含该博客文章的RSS订阅。在这种情况下,可能更好的是在每个修改博客文章的
BrowserView
结束时发送一个IBlogPostModified
事件,因为IObjectModified
在每次属性变化时都会发送一次,这可能会影响性能。适配器(Adapters)。适配器实际上是将一个接口转换为另一个接口的“桥梁”。对于编程语言爱好者来说:Zope适配器在Python中实现了“开放”的多重分发(“开放”是指“你可以从任何包添加更多情况”),更具体的接口匹配优先于不太具体的匹配(
Interface
类可以是彼此的子类,这正是你所希望的效果)。从一个
Interface
调用适配器时,可以使用非常简洁的语法ISomething(object_to_adapt)
,或者通过函数zope.component.getAdapter
查找。多个Interface
的适配器必须通过函数zope.component.getMultiAdapter
查找,这样稍微复杂一些。你可以为一组给定的
Interface
有多个适配器,通过在注册适配器时提供的字符串name
来区分。默认情况下,名称是""
。例如,BrowserView
实际上是适配器,它们适配于它们注册的接口和HTTP请求类实现的接口。你还可以使用zope.component.getAdapters( (IAdaptFrom,), IAdaptTo )
查找所有从一个接口序列到另一个接口的注册适配器,这会返回一对对的(名称,适配器)。这可以作为一个很好的方式,提供插件的挂钩。假设我想把我所有的博客文章和配置保存为一个大的XML文件。我创建一个
my.blog.xmldump
包,定义一个IXMLSegment
,并注册一个从IBlogPost
到IXMLSegment
的适配器,以及一个从IBlogConfiguration
到IXMLSegment
的适配器。现在我可以通过写IXMLSegment(object_to_serialize)
来调用适合某个对象的适配器。我甚至可以从其他包添加更多适配器到
IXMLSegment
。ZCML有一个特性,可以在某个包安装时运行特定的指令。我可以利用这个特性,让my.blog.rss
在my.blog.xmldump
安装时注册一个从IRSSFeed
到IXMLSegment
的适配器,而不需要让my.blog.rss
依赖于my.blog.xmldump
。Viewlet
就像小型的BrowserView
,可以“订阅”页面中的特定位置。我现在记不清所有细节了,但这些非常适合像侧边栏这样的插件。我不记得它们是否是基础Zope或Plone的一部分。我建议除非你要解决的问题确实需要一个真正的CMS,否则不要使用Plone,因为它是一个庞大而复杂的软件,通常运行较慢。
不过你不一定需要
Viewlet
,因为BrowserView
可以相互调用,使用'TAL表达式中的 'object/@@some_browser_view',或者使用queryMultiAdapter( (ISomething, IHttpRequest), name='some_browser_view' )
,但无论如何它们都很不错。标记接口(Marker Interface)。标记接口是一个不提供任何方法和属性的接口。你可以在运行时使用
ISomething.alsoProvidedBy
将标记接口添加到任何对象。这允许你,例如,改变在特定对象上使用的适配器和定义的BrowserView
。
抱歉我没有详细说明每个例子的实现方法,但每个例子大概都需要一篇博客文章来讲解。
其实你可以测试一下你的对象或者类是否实现了你定义的接口。为此,你可以使用 verify
模块(通常在测试时会用到它):
>>> from zope.interface import Interface, Attribute, implements
>>> class IFoo(Interface):
... x = Attribute("The X attribute")
... y = Attribute("The Y attribute")
>>> class Foo(object):
... implements(IFoo)
... x = 1
... def __init__(self):
... self.y = 2
>>> from zope.interface.verify import verifyObject
>>> verifyObject(IFoo, Foo())
True
>>> from zope.interface.verify import verifyClass
>>> verifyClass(IFoo, Foo)
True
接口还可以用来设置和测试不变条件。你可以在这里找到更多信息: