介绍接口 难度:新手 技能: 你应该很熟悉Python,这是所有章节都需要的。 一些在软件设计里使用接口的知识。(可选) 问题/任务: 在本书的每一章里你都可以通过这样或那样的途径听说接口。因此,理解接口的意义对于读者来说是相当重要的。 解决方案: 介绍 在超大型的软件项目里,特别是那些期望与很多其他软件互动的,开发精确良好的应用程序接口(APIs)是非常必要的。我们可将APIs想象成一种框架,就像是RFC或POSIX标准那样。一旦定义了接口并且发布了,它就很难再被改变。但是,API在单体软件里也有用途,被看作是内部API。 当其他编程语言,例如Java,就将接口作为语言内部的特性,而直到最近Python才有了同等意义的正式的接口。通常,在Python里接口是通过类(class)来定义并实现的,并且它是通过文档来创建的API的表现。有很多关于这方面的文章。开发人员经常在没有意识到破坏了其他人的代码的情况下修改了类的API。编程接口可以完全解决这个问题,当一个API被破坏时警铃就会响起(通过测试)。 下面是一个简单的Python接口的例子(已经被使用在Zope项目中) 1 from zope.interface import Interface,Attribute 2 3 class IExample(Interface): 4 """This interface represents a generic example.""" 5 6 text = Attribute("The text of the example") 7 8 def setText(text): 9 "This method writes the passed text to the text attribute." 10 11 def getText(): 12 "This method returns the value of the text attribute." 行 1:我们从zope.interface导入了2个重要的对象--元类Interface,类Attribute。 行 3:我们"误用"定义类的方式来定义接口。注意,虽然接口不是类并且也没有类的行为,但是当你把接口作为类的基类时,这个对象就变成了接口。 行 4:在Zope3里,这是接口的文档。所以,在这里写上明确的文档字符串是很必要的。接口文档字符串给出了实现接口功能的详细描述。 行 6:Attribute类是用来在接口中声明属性的。这个类的构造方法仅接受一个字符串参数,就是这个属性的文档字符串。现在你可能会说:"还有很多的元数据属性存在,例如整型的最小值或者字符串的最大长度等。"这一类型的扩展属性类是由另外一个模块提供的,叫zope.schema。我们将在"ZOPE的SCHEMAS和WIDGETS"章节中阐述。 行 8-9和11-12:像通常那样使用def关键字来定义方法。不同的是第一个参数不是self。你只是列出了所有的公共参数。文档字符串依然作为文档来使用的。 除此之外,方法中可以包含任何你想要的东西,Zope不会使用方法里的任何内容。如果你在Zope3之外使用zope.interface包,那么你可以方法体来为先决条件、参数类型和返回类型提供一个正式的描述。 上面是一个典型的但不实际的接口例子。既然我们使用Python,就没有必要同时为属性实现setter和getter方法。在这种情况下,我们通常可以将属性制定为接口的一部分并且用Python的属性来实现它,如果需要的话。 高级用途 一旦你有了基于Python的接口,很多新的可能性逐步显示出来了。现在你可以使用接口来作为定义对象的构造函数。例如,你可以说有一个类AllText可以将IExample转换为IAllText,后者的接口含有一个getAllText()的方法,可以从IExample返回所有人能读懂的文本。这样的类被称作适配器(Adapter)。更加正式的来讲,适配器就是使用一个接口(IExample)来提供/实现另外一个接口(IAllText)。 更加通用的来讲,接口是被用作标识符。Zope3的工具注册经常执行查询表单:"给我所有实现了接口I1的工具。"接口甚至被用来分类其它接口!例如,我可能我的IExample声明为IContentType,那我局可以到工具注册的地方查询:"给我所有表现为一种类型(IContentType)的接口。"一旦我知道了这些内容类型的接口,我就可以指出哪个类是内容类型。 你可以看到接口为广泛的用例提供了解决方案。顺便说一下,创建一个空的接口用来做为标识的用途是非常普遍的,这一类的接口被称作"标记接口"。 使用接口 在对象里接口是这样被使用的: 1 from zope.interface import implements 2 from interfaces import IExample 3 4 class SimpleExample: 5 implements(IExample) implements()方法告诉系统这个类的实例提供IExample。但是当然,模块和类本身也可以实现接口。对于模块来说你可以使用moduleProvides(*interfaces)。对于类来说,你可以直接在类的定义里插入classImplements(*interfaces)或者对现有的类使用classProvides(cls,*interfaces)。同样,你可以使用在任何对象(包括类的实例)上使用directlyProvides(instance,*interfaces)。 接口对象本身含有一些非常棒的方法。最普通的一个就是providedBy(ob),它可以检测传递过来的对象是否是实现了这个接口。 1 >>> ex = SimpleExample() 2 >>> IExample.providedBy(ex) 3 True 同样的,你也可以通过调用IExample.implementedBy(SimpleExample)来传递到类里面并测试这个类的实例是否实现了这个接口。最后一个有用的方法是isOrExtends(interface)。这个方法用来检测传递的接口是否是接口或者传递的接口是否是这个接口的基(继承)接口。 当丛接口创建类的时候,有一个很有帮助性的脚本叫pyskel.p,它可以帮助你生成类的骨架。在使用这个脚本之前,请确保ZOPE3/src在你的Python路径中。 使用方法: python2.3 utilities/pyskel.py dotted.path.ref.to.interface 这个调用将在你的控制台里创建类的骨架,它可以帮助你省去大量的输入。属性和方法的顺序与在接口里面出现的顺序是一样的。 最后需要注意的是,由于Python没有提供接口,那么zope. Interface包提供的一些实现了Python内建类型的接口。你可以在zope.interface.common里找到它们。 这就是接口的概览。这些介绍应该足够你开始下面章节的学习了。更多的概念将在你学习和使用本书余下部分的过程中变得愈加清晰起来。