模式 是一个格式良好的 XML 文档,它使用强大的 XML 模式定义语言(XSDL,有时也叫做 W3C Schema)来建模和验证其他 XML 数据。根据您是如何定义的,模式粒子(元素、类型、属性和其他构造)都具有一个相关的作用域,可以是全局/暴露的,也可以是局部/隐藏的。模式的作用域设计极大地影响了模式可以如何被改进、重用以及与其他技术协作。
无论您是刚开始使用模式,还是想要更大地发挥当前解决方案的作用,理解模式作用域都是您成功的关键。在本文中,我们首先展示如何为各种模式粒子定义全局或局部作用域,并解释作用域将如何影响它们的行为。然后我们将描述基本的模式设计范式,并将探索创建满足项目需求的作用域设计方面的考虑因素和最佳实践。
模式的最高层容器元素是 schema。schema 元素的直接子元素是全局定义的(就是说,具有全局作用域)。您可以使用全局元素作为根节点,并且可以从模式的其他部分引用它们。元素只要定义一次,您然后就可以在整个模式中使用它。
清单 1 中的模式例子展示了一个简单的数据模型,带有一个名叫 postalCode 的全局元素:
清单 1. 带有单个全局元素的模式
<xs:schema> <xs:element name='postalCode' type='xs:string'/> </xs:schema> |
可以使用 清单 1 中的模式来成功地验证以下数据实例:
<postalCode>14534</postalCode> |
在这个数据实例中,postalCode 是 根元素
— 即数据实例中的最高层容器。只有在相关模式的最高层定义的元素才可以充当数据实例中的根元素。清单 1 中的模式只定义了一个元素,所以很容易理解只有 postalCode 可以充当实例中的根元素。
清单 2 中的例子模式在根层次定义了两个元素:
清单 2. 带有两个可能根元素的模式
<xs:schema> <xs:element name='postalCode' type='xs:string'/> <xs:element name='zipCode' type='xs:string'/> </xs:schema> |
postalCode 或 zipCode 都可以充当 清单 2 中模式所建模的实例中的根元素。
将元素定义为局部的可以防止它们被暴露给模式的其他部分。局部元素的上下文局限于它的当前位置,所以不能从模式的其他部分引用它。在 清单 3 的例子中,zipCode 元素不是全局定义的。相反,它被作为 address 元素的子元素,定义在一个元素定义的 complexType 中。
清单 3. 单个带有局部子元素的全局元素
<xs:schema> <xs:element name='address'> <xs:complexType> <xs:sequence> <xs:element name='street' type='xs:string'/> <xs:element name='city' type='xs:string'/> <xs:element name='state' type='xs:string'/> <xs:element name='zipCode' type='xs:string'/> </xs:sequence> </xs:complexType> </xs:schema> |
由于 zipCode 元素的定义位于 address 元素的声明中,所以它是一个局部定义,它的作用域只在 address 元素中。对于有效的文档实例,zipCode 元素必须出现在 address 元素中,如 清单 4 所示:
清单 4. 清单 3 中模式的有效数据实例
<address> <!-- street, city and state hidden for example purposes --> <zipCode>14534</zipCode> </address> |
在 清单 4 中,address 元素是根元素。zipCode 元素不能充当实例中的根元素,因为它不是在模式模块中的根层次被全局定义的。局部定义的元素只能出现在定义它们的元素定义上下文中。
除了充当根元素之外,任何全局定义的元素也可以在任何需要它们的局部作用域内被引用和出现。在 清单 5 的例子中,全局定义的 zipCode 元素被用于局部作用域上下文中的 address 元素的定义中:
清单 5. 局部作用域内引用的全局元素
<xs:schema>
<xs:element name="address">
<xs:complexType>
<xs:sequence>
<xs:element name='street' type='xs:string'/>
<xs:element name='city' type='xs:string'/>
<xs:element ref='zipCode'/> <!-- reference to globally defined element -->
</xs:sequence>
</xs:complexType>
</xs:element>
<!-- Globally defined element that is referenced in element above -->
<xs:element name='zipCode' type='xs:string'/>
</xs:schema>
|
您可以看到,全局暴露的元素声明支持模块化和重用。您可以在该模式的其他部分和会导入该模式的父模式中引用 zipCode 元素。
属性定义与此类似。例如,在 清单 6 中,全局定义的 state 被引用在局部作用域上下文中的 address 元素中:
清单 6. 局部作用域中引用的全局属性
<xs:schema> <xs:element name='address'> <xs:complexType> <!--[.. elements removed for readability..]--> <xs:attribute ref="state"/> <!-- referencing globally defined attribute --> </xs:complexType> </xs:element> <xs:attribute name="state" type="xs:string"/> <!--globally defined attribute --> </xs:schema> |
就跟您可以全局和局部地定义元素和属性一样,您也可以这样定义类型。前面的例子为 address 元素定义使用了局部定义的类型。要让该类型定义成为全局的,可将它从局部定义删除,取一个独特的名称,并将它放在根 schema 节点下,如 清单 7 所示:
清单 7. 局部作用域内引用的全局类型
<xs:schema>
<xs:element name='address' type="address.type"/>
<xs:complexType name="address.type">
<xs:sequence>
<xs:element name='street' type='xs:string'/>
<xs:element name='city' type='xs:string'/>
<xs:element name='state' type='xs:string'/>
<xs:element name='zipCode' type='xs:string'/>
</xs:sequence>
</xs:complexType>
</xs:schema>
|
此类型定义现在是全局的了,并且具有独特的名称 address.type。要将该类型与一个元素相关联,我们通过将类型属性(type="")与全局类型名称相关联而引用它。您可以通过使用 xs:extension 元素扩展全局类型定义,也可以通过使用 xs:restriction 元素限制它。
确定应该将模式粒子定义为具有局部作用域还是全局作用域并不总是那么容易。根据使用情况、名称空间需求和模式进化等因素,最佳选择也各不一样。一般来说,模式设计归类为四种基本范式:
- Russian doll(俄罗斯套娃)
- Salami slice(意大利香肠片)
- Venetian blinds(软百叶窗)
- Garden of Eden(伊甸园)
要为自己的项目确定最佳解决方案,一定要理解这些范式。
该范式来源于著名的俄罗斯套娃(Russian doll)— 一套尺寸由大到小的木头娃娃,一个套一个地装在一起。Russian doll 范式局部地定义所有子元素;因此,每个元素及其类型都封装在其父元素之中,就像俄罗斯套娃那样。
清单 8 中的例子 — 一种家电的帮助文档的简化表示 — 演示了该范式:
清单 8. Russian doll 风格的模式
<xs:schema>
<xs:element name="HelpDoc">
<xs:complexType>
<xs:sequence>
<xs:element name="Section">
<xs:complexType>
<xs:sequence>
<xs:element name="Title" type="xs:string"/>
<xs:element name="Body" type="xs:string"/>
</xs:sequence>
<xs:attribute name="name" type="xs:string"/>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:/complexType>
</xs:element>
</xs:schema>
|
一个相关的实例类似于 清单 9 这样。
清单 9. 匹配 Russian doll 模式模型的实例
<HelpDoc> <Section name="operation_instructions"> <Title>Operating your appliance.</Title> <Body>First, open the packaging and check to see...</Body> </Section> </HelpDoc> |
您可以看到,清单 8 中的每个子元素、属性和类型都是局部定义的。惟一的全局元素是根元素,即 HelpDoc。该语法紧凑,有些人可能会觉得它容易理解。Russian doll 风格的模式不将它们的组件暴露给其他类型、元素或模式,所以它们也被认为是高度去耦的(就是说,元素不全局地依赖于其他元素)和内聚的(相关元素被分组在单个自包含的父元素中)。
此范式概括为这样一种模式,即很少有跟其他系统的交互,其组件也没有重用。通过以这种方式定义模式,您可以保持结构为自包含的、隐藏名称空间以及防止受到其他系统的影响。
利用 Salami slice 范式,您可进入暴露内容模型的下一步。在该范式中,您可以将自己所有局部定义的元素都移动到全局定义中。清单 10 展示了 清单 8 中 Russian doll 风格例子修改为满足 Salami slice 范式后的样子:
清单 10. Salami slice 范式
<xs:schema>
<xs:element name="Body" type="xs:string"/>
<xs:element name="Title" type="xs:string"/>
<xs:element name="Section">
<xs:complexType>
<xs:sequence>
<xs:element ref="Title"/>
<xs:element ref="Body"/>
</xs:sequence>
<xs:attribute name="name" type="xs:string"/>
</xs:complexType>
</xs:element>
<xs:element name="HelpDocs">
<xs:complexType>
<xs:sequence>
<xs:element ref="Section"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
|
Salami slice 范式暴露所有元素,所以您可以在模式的其他部分引用和重用它们,并且它让它们对其他模式是透明的。该方法一个主要的优点是,元素高度可重用。但是,这也意味着所有名称空间都是全局暴露的,并且元素之间的耦合增大了。在 清单 10 中,Section 元素与 Title 和 Body 元素是全局耦合的。对 Title 和 Body 元素的任何修改随后都会影响 Section 定义。
在 Venetian blinds 范式中,不是全局地定义所有元素,您首先是全局地定义所有类型,如 清单 11 中的例子所示:
清单 11. Venetian blinds 范式
<xs:schema>
<xs:complexType name="section.type">
<xs:sequence>
<xs:element name="Title" type="xs:string"/>
<xs:element name="Body" type="xs:string"/>
</xs:sequence>
<xs:attribute name="name" type="xs:string"/>
</xs:complexType>
<xs:complexType name="helpdocs.type">
<xs:sequence>
<xs:element name="Section" type="section.type"/>
</xs:sequence>
</xs:complexType>
<xs:element name="HelpDocs" type="helpdocs.type">
</xs:schema>
|
Venetian blinds 风格使用全局类型定义来增加重用能力。由于所有子元素都是局部的,所以它额外的优势是能够隐藏名称空间。该方法允许您在使用 elementFormDefault 属性作为隐藏或暴露名称空间的一个切换开关的同时,暴露您的结构定义以便重用。您做到了两全其美!
在 Garden of Eden 设计范式中,您让元素声明和类型声明都是全局的,将全局化发挥到了极致。清单 12 展示了一个 Garden of Eden 风格的模式:
清单 12. Garden of Eden 范式
<xs:schema>
<xs:attribute name="name" type="xs:string"/>
<xs:element name="Title" type="xs:string"/>
<xs:element name="Body" type="xs:string"/>
<xs:element name="Section" type="section.type"/>
<xs:element name="HelpDocs" type="helpdocs.type">
<xs:complexType name="section.type">
<xs:sequence>
<xs:element ref="Title"/>
<xs:element ref="Body"/>
</xs:sequence>
<xs:attribute ref="name"/>
</xs:complexType>
<xs:complexType name="helpdocs.type">
<xs:sequence>
<xs:element ref="Section"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
|
通过让每个可能的元素、属性和类型都成为全局的,您创建了这样一种场景,即无论是在内部还是在模式之间都最大化了重用 — 尽管是通过强迫名称空间为暴露的。通过完全暴露您的结构,您让模式高度耦合却敏捷。由于元素是相互依赖的,所以很快就会应用大规模的更改。
设计模式时,您通常会在暴露可重用组件、隐藏名称空间和限制名称空间暴露以及降低耦合(或者叫做多个全局元素/类型之间的相互依赖)之间取得平衡。图 1 总结了四种模式范式各自的重用潜能,指出了它们耦合和暴露方面的相对等级:
图 1. 暴露和与不同范式的耦合
为重用模式组件提供高潜能可以缩短未来的开发时间和让大规模更改变得容易。但是,它也会带来这样的情况,即多个元素和类型不必要地耦合在一起。当模式变得高度耦合时,元素和类型就变得相互依赖,使得难以管理未来的更改和添加。耦合的猖獗会阻止模式的改进,因为其他系统依赖于您的接口保持一致。对于暴露什么和暴露多少,一定要谨慎。一旦做出选择,就难以改变。
不过,还是有一些方法可以确保未来的成功。首先是适当的作用域设计:
- 如果模式重用不是特别重要,而最小化代码大小却很必要,那么请使用 Russian doll 风格,因为它很紧凑,可以用来最大化名称空间隐藏。
- 如果元素替代对于您的设计很重要,或者您需要让元素对其他模式是透明的,那么请使用 Salami slice 风格或 Garden of Eden。
- 如果您想要高度的重用,还想最大可能地隐藏名称空间,那么请使用 Venetian blinds 风格。然后可以使用
elementFormDefault作为一个切换开关,来要求暴露或隐藏元素。
此外,不要害怕混用。在特定的模式中使用多种模式设计范式是高度有益的。对于您希望保持为私有和隐藏的结构部分,使用 Russian doll 风格。同时,您也可能想要使用 Salami slice 或 Garden of Eden 设计全局地暴露一些元素。例如,清单 13 使用了高度暴露的 Garden of Eden 风格和隐藏的 Russian doll 风格:
清单 13. 混用范式
<xs:schema>
<!-- Garden of Eden style component -->
<xs:element name="Title" type="xs:string"/>
<xs:element name="Body" type="xs:string"/>
<xs:element name="Section" type="section.type"/>
<xs:element name="HelpDocs" type="helpdocs.type">
<xs:complexType name="section.type">
<xs:sequence>
<xs:element ref="Title"/>
<xs:element ref="Body"/>
</xs:sequence>
<xs:attribute name="name"/>
</xs:complexType>
<xs:complexType name="helpdocs.type">
<xs:sequence>
<xs:element ref="Section"/>
<!-- Russian doll style component -->
<xs:element ref="Credits">
<xs:complexType>
<xs:sequence>
<xs:element name="Author" type="xs:string"/>
<xs:element name="Year" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:complexType>
</xs:schema>
|
在此场景中,也许您需要暴露 HelpDocs、Section、Title 和 Body 元素,以便其他模式使用。但是,您想要隐藏 Credits 元素,以防止它与其他模式定义耦合。
如果重用的需求不是十分迫切,那么用这种方式编写模式是完全合理的。通过让元素成为全局的,很容易增加暴露。后面去掉暴露将会很困难。在这种情况下,如果未来设计需要更多的暴露,您可以很容易增加。
在开始任何模式项目之前,您必须做出符合自己目标的设计选择。通过理解模式作用域的使用,可以流水线化管理模式和内容的过程。最终,这将提升您的能力,来管理模式生命周期和让您的模式高效地与其他系统交互。
学习
- Real-world XML Schema(Paul Golick 和 Richard Mader,developerWorks,2002 年 1 月):探索 17 个广泛适用的 XML 应用实践。
- 使用 XML Schema 定义元素的基本知识(Ashvin Radiya 和 Vibha Dixit,developerWorks,2000 年 8 月):发现模式的灵活性,学习如何在 XML Schema 系统中定义最基本的 XML 文档构造块 — 元素。
- 技巧:何时使用局部声明和全局声明(Benoît Marchal,developerWorks,2003 年 9 月):该技巧中比较了元素的全局和局部声明,并指出了关于何时使用何种声明的要点。
- W3C XML Schema primer:查看 XML 模式的 W3C 推荐标准。
- developerWorks XML 专区:在 XML 专区获取提高您的专业技能所需的资源。
- My developerWorks:个性化您的 developerWorks 体验。
- IBM XML 认证:了解如何才能成为一名 IBM 认证的 XML 和相关技术的开发人员。
- XML 技术库:访问 developerWorks XML 专区,获得广泛的技术文章和技巧、教程、标准和 IBM 红皮书。此外,阅读更多 XML 技巧。
- developerWorks 技术活动 和 网络广播:随时关注这些活动中的技术。
- developerWorks
播客:收听面向软件开发人员的有趣访谈和讨论。
获得产品和技术
-
IBM 产品评估试用版软件:下载或 在线试用 IBM SOA Sandbox,并开始使用来自 DB2®、Lotus®、Rational®、Tivoli® 和 WebSphere® 的应用程序开发工具和中间件产品。
讨论
- XML 专区讨论论坛:参与任何一个 XML 相关讨论。
- developerWorks 博客:阅读这些博客并参与讨论。

