编程:从入门到实践
2010年9月17日- 作为后续的读者的评论,笔者在人数做出更新的代码3 , 11 , 12 ,和13 。
架构是格式良好的XML文档,它使用功能强大的XML架构定义语言(XSD,有时也称为W3C架构)来建模和验证其他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>
在清单2中的模式建模的实例中, postalCode
或zipCode
都可以用作根元素。
用局部作用域定义元素
在本地定义元素可防止将元素暴露给架构的其他部分。 本地元素的上下文仅限于其当前位置,因此无法从架构的其他部分进行引用。 在清单3的示例中, zipCode
元素未全局定义。 相反,它是在元素定义的complexType
内部定义的,作为address
元素的子元素。
清单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:element>
</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
元素是root元素。 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中 ,在局部范围内的上下文中的address
元素内引用了全局定义的state
属性:
清单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属性( type=""
)与全局类型名称相关联来对其进行引用。 您可以使用xs:extension
元素扩展全局类型定义,也可以使用xs:restriction
元素对其进行xs:restriction
。
基本设计模式
确定是否应该使用本地或全局范围定义架构粒子并不总是那么容易。 根据用例,命名空间要求和架构演变,最佳选择可能会有所不同。 通常,模式设计分为四个基本模式:
- 俄罗斯娃娃
- 萨拉米香肠片
- 百叶帘
- 伊甸园
了解这些模式对于确定项目的最佳解决方案很重要。
俄罗斯娃娃图案
这种图案是在著名的俄罗斯套娃(Matryoshka Russian dolls)上创造出来的,俄罗斯套娃的尺寸越来越小。 俄罗斯玩偶模式在本地定义了所有子元素; 因此,每个元素及其类型都由其父元素封装,就像俄罗斯的玩偶一样。
清单8中的示例(家用电器帮助文档的简化表示)展示了这种模式:
清单8.俄罗斯娃娃风格的模式
<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.匹配俄罗斯玩偶模式模型的实例
<HelpDoc>
<Section name="operation_instructions">
<Title>Operating your appliance.</Title>
<Body>First, open the packaging and check to see...</Body>
</Section>
</HelpDoc>
您可以看到清单8中的每个子元素,属性和类型都是在本地定义的。 唯一的全局元素是根HelpDoc
。 该语法很紧凑,有些人可能认为它易于阅读。 俄罗斯玩偶风格的架构不会将其组件暴露给其他类型,元素或架构,因此它们也被认为是高度解耦的 (也就是说,元素不全局依赖于其他元素)和内聚的 (相关的元素被分组在一个自身中) -包含父项)。
这种模式代表了一个旨在与其他系统进行很少交互且不重用其组件的模式。 通过以这种方式定义架构,您可以使结构保持独立,隐藏名称空间并防止其他系统的影响。
快速提示:命名空间
当模式被命名为命名空间(即具有targetNamespace
)时,所有全局粒子必须由完全限定的名称(即prefix : name
)引用。 据说这些名称空间是公开的。 例如:
<xs:schema xmlns:xyz="http://xyzcompany.com"
targetNamespace="http://xyzcompany.com">
<xs:element name="HelpDocs">
<xs:complexType>
<xs:sequence>
<!-- Global element is referenced, -->
<!-- must contain namespace prefix. -->
<xs:element ref="xyz:Section"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="Section">
<!-- -->
</xs:element>
</xs:schema>
由于Section
元素是全局元素,并且声明了targetNamespace
,因此在对Section
元素的引用中需要xyz
名称空间前缀。 <xs:element ref="Section"/>
不是有效的引用。
萨拉米香肠切片模式
使用Salami Slice模式,您将朝着展示内容模型的方向迈出下一步。 在这种模式下,您将所有本地定义的元素移到全局定义中。 清单10显示了清单8中的俄罗斯娃娃样式示例,该示例经过修改以适合萨拉米香肠切片模式:
清单10.萨拉米香肠切片模式
<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>
萨拉米切片模式公开了所有元素,因此您可以在架构的其他部分中引用和重用它们,并使它们对其他架构透明。 这种方法的主要优点是元素是高度可重用的。 但是,这也意味着所有名称空间都是全局公开的,并且元素之间的耦合也会增加。 在清单10中 , Section
元素全局耦合到Title
和Body
元素。 对Title
和Body
元素的任何修改将随后影响Section
定义。
威尼斯百叶窗图案
在“威尼斯式百叶窗”模式中,而不是全局定义所有元素,而是从全局定义所有类型开始,如清单11中的示例所示 :
清单11.威尼斯百叶窗模式
<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>
威尼斯式百叶窗样式使用全局类型定义来增加重用能力。 由于所有子元素都已本地化,因此它具有能够隐藏名称空间的额外好处。 这种方法允许您在使用elementFormDefault
属性作为隐藏或公开名称空间的开关时公开结构定义以供重用。 您将两全其美!
伊甸园模式
在伊甸园设计模式中,使元素声明和类型声明成为全局变量,从而将全球化发挥到极致。 清单12显示了Eden风格的Garden模式:
清单12.伊甸园模式
<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.不同图案的曝光与耦合
![图表说明了曝光与四种模式模式的耦合](https://i-blog.csdnimg.cn/blog_migrate/2d80eec2a99af9383074416d3a40467a.png)
提供重用模式组件的巨大潜力可以减少将来的开发时间,并使全面的更改变得容易。 但是,它也可能创建不必要地耦合多个元素和类型的场景。 当架构高度耦合时,元素和类型变得相互依赖,从而难以管理将来的更改和添加。 耦合运行amok会阻止架构演变,因为其他系统依赖于您的接口保持一致。 重要的是要注意暴露的量和暴露量。 一旦做出选择,就很难撤消。
但是,有一些方法可以确保将来取得成功。 从适当的示波器设计开始:
- 如果模式重用不是必须的并且要最小化大小,则使用俄语玩偶风格,因为它紧凑且可用于最大化名称空间隐藏。
- 如果元素替换对于您的设计是必不可少的,或者您需要使元素对其他架构透明,请使用Salami切片样式或Eden花园。
- 如果您想要高水平的重用以及最大程度地隐藏名称空间的可能性,请使用百叶窗样式。 然后,您可以将
elementFormDefault
用作切换,以要求公开的名称空间或隐藏它们。
另外,不要害怕将其混淆。 在特定架构中使用多个架构设计模式可能会非常有益。 对于您想保留私人和隐藏结构的某些部分,请使用俄罗斯娃娃风格。 同时,您可能想使用萨拉米香肠切片或伊甸园设计来全局公开某些元素。 例如, 清单13使用了高度暴露的伊甸园风格和隐藏的俄罗斯玩偶风格部分:
清单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 name="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
元素以防止其与其他模式定义耦合。
如果对重用的需求不是很大,则以这种方式编写模式是完全合乎逻辑的。 通过使元素具有全局性,很容易增加曝光度。 以后去除暴露物可能是巨大的痛苦。 在这种情况下,如果将来的设计需要更多曝光,则可以轻松添加它。
结论
在开始任何模式项目之前,必须将设计选择与目标保持一致。 通过了解架构作用域的用法,可以简化管理架构和内容的过程。 最终,这将提高您管理架构生命周期的能力,并允许您的架构与其他系统高效地交互。
翻译自: https://www.ibm.com/developerworks/xml/library/x-schemascope/index.html
编程:从入门到实践