在 XML 模式中扩展枚举列表

在列表中添加新值是一种常见而且必要的需求。模式设计者通常希望在系统架构中构建一种添加附加值的方法,并且该附加值在设计阶段是未知的。模式设计者如何创建一个可扩展、易于实现的枚举值列表?本文将介绍几种实现这一目标的方法。
<!--START RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --><!--END RESERVED FOR FUTURE USE INCLUDE FILES-->

模式设计者和实现人员需要一种扩展 XML 模式中现有枚举列表的方法。不幸的是,XML 模式规范不允许在这些列表的创建过程中(参阅 参考资料)进行扩展。设计阶段所选的值是固定的,而且都是可用的。尽管有这样的限制,人们仍使用各种替代方案来实现列表扩展。很多使用现有的不能改变的模式的客户经常提出这一要求。他们希望在添加新功能的同时保持向后兼容性。本文中,您将会看到模式设计者如何克服障碍实现该功能。

枚举列表 是特定数据点的一组指定值。例如,您也许通过固定的值列表查看国家代码,包括 DE(德国)、US(美国)和 JP(日本)。根据给定的值集,当一个新国家被识别出时,如 TL(东帝汶)或者 BA(波斯尼亚及黑塞哥维那),该怎么办?使用以前的名称列表的客户必须改变实现来容纳新值。

当使用 XML 模式对数据建模时,枚举值被显式列出。因此,国家代码列表依次包含各个枚举值。经常需要识别列表中的新值,而且必须将其容纳到列表中,模式设计者试图找到一种扩展列表的方法,实际上,是将这种方法构建到设计中,允许添加在设计时未知的附加值。

创建可扩展的枚举列表

在寻找这一问题的解决方案时,受到四个关键标准的影响:

  • 首先,要在设计阶段之后扩展列表。不管是快速建立一个新的贸易伙伴还是建立时间关键型的新数据字段,在关键时刻进行扩展是一项实际需求。
  • 其次,能够在解析器中验证值对于简化实现是非常关键的。
  • 第三,在单个周期内完成解析和验证是至关重要的。这就避免了像 Genericode 解决方案一样,在一个单独的周期和解析器中进行验证。对于某些设置来说,添加新技术需求会导致成本太高或者太耗时。
  • 最后,解决方案必须能够向后兼容原始的模式。不兼容的列表更改不能称为扩展。

有些人认为根本就不应该扩展枚举列表。数据建模人员也许认为如果想让模型包含更多数据、扩展模型,那么可以根据产品创建模式 — 实际上,在需要时创建更大的模型并减少限制。如果能够控制原始模式和数据模型,这样做是可以的,这种方法也许是理想的方法。但是,如果您需要在设计阶段之后进行实际扩展,这样的方法是行不通的。

还有人认为扩展枚举列表的关键是不使用 XML 模式验证解析器。Genericode(参阅 参考资料)建议在第二层对枚举列表进行验证,脱离初始的 XML 模式解析器验证过程。这种理论是正确的,而且这种方法的应用会越来越广泛。但是,如果要在一个解析周期内完成,这种解决方案是无法做到的。在某些情况下,不可能执行第二个验证周期。

当然,您可以在新列表中创建新元素。但是,不能向后兼容原始模式。我们的目标是在保持向后兼容性的同时实现一个可扩展的列表(参阅 参考资料)。

对于本文的目标,这里作出的假设基于我与客户打交道的经验 —— 即用附加值扩展现有枚举列表的需求。另外,我假设在一个步骤内完成 XML 模式解析与验证等操作。

扩展枚举列表的必要条件

该扩展示例有四个必要条件:

  • 允许在设计阶段之后扩展枚举列表。
  • 用解析器验证枚举列表。
  • 在一个周期内验证枚举列表。
  • 维持和原始模式的向后兼容性。

举例来说,一个团队需要处理一个区域产业协会的枚举列表(或任意现有列表)为例,并根据使用修改模式组件。先前的模式提供 MaritalStatus 组件和值的枚举列表,如 清单 1 所示。


清单 1. 婚姻状况枚举列表
				
<xsd:simpleType name="MaritalStatusEnumType">
    <xsd:restriction base="xsd:normalizedString">
        <xsd:enumeration value="Divorced"/>
        <xsd:enumeration value="Married"/>
        <xsd:enumeration value="NeverMarried"/>
        <xsd:enumeration value="Separated"/>
        <xsd:enumeration value="SignificantOther"/>
        <xsd:enumeration value="Widowed"/>
        <xsd:enumeration value="Unknown"/>
    </xsd:restriction>
</xsd:simpleType>

<xsd:element name="MaritalStatus" type="MaritalStatusEnumType"/>

假设一个公司要使用这些值,另外,还要支持它的重要贸易伙伴使用另一个值。CivilUnion 是一个扩展值,公司识别出该值不属于原始模式。但是从语义上来说,使用现有元素 —MaritalStatus — 也是可以的。公司要如何实现呢?





回页首


解决方案 1: 编辑原始模式使其包含新枚举值

当然,编辑原始模式使其包含新枚举值是最直接的方法。保留模式的本地副本,然后编辑这些模式以支持公司使用的枚举值。

  • 优点:易于实现
  • 缺点:
    • 需要编辑原始模式,这些模式将逐渐改变,以至于无法控制。如果扩展一个先前存在的列表,那么创建者(贸易伙伴、协会等)可能要发布列表的新版本。您需要将编辑的内容传播到每个新版本中。
    • 手动编辑模式会导致意外的编辑错误。

如果您不能(或不想)编辑原始模式,则需要一种替代方法。





回页首


解决方案 2: 创建新枚举列表并加入到原始列表中

第二个选择是创建新枚举列表,并将其加入到原始枚举列表中。清单 1 显示原始婚姻状况列表。清单 2 显示最新创建的枚举列表。


清单 2. 新婚姻状况枚举列表
				
<xsd:simpleType name="MyExtMaritalStatusEnumType">
    <xsd:restriction base="xsd:normalizedString">
        <xsd:enumeration value="CivilUnion"/>
    </xsd:restriction>
</xsd:simpleType>

使用 <xsd:union> 标记将其与原始列表结合,如 清单 3 所示。


清单 3. 将两个列表组合起来
				
<xsd:simpleType name="MaritalStatusType_Union">
     <xsd:union memberTypes="MyExtMaritalStatusEnumType MaritalStatusEnumType"/>
</xsd:simpleType>

<xsd:element name="MaritalStatus" type="MaritalStatusType_Union"/>

该解决方案仍然需要对模式进行编辑 — 即将元素 MaritalStatusMaritalStatusType 类型转换为 MaritalStatusType_Union 类型。改动不大,但仍然有一些手动编辑工作。

  • 优点:不改变原始枚举列表。
  • 缺点:
    • 在设计阶段所有的值必须是已知的,防止后期绑定解决方案。
    • 需要 <xsd:union> 标记支持,但有时该标记无法用工具实现。




回页首


解决方案 3: 创建一个模式,并与原始枚举类型结合

现在看一下有关眼睛颜色的人口数据用例。清单 4 显示这一列表。


清单 4. Person Eye Color 枚举列表
				
<xsd:simpleType name="PersonEyeColorType">
    <xsd:restriction base="xsd:string">
        <xsd:enumeration value="Black"/>
        <xsd:enumeration value="Hazel"/>
        <xsd:enumeration value="Gray"/>
        <xsd:enumeration value="Brown"/>
        <xsd:enumeration value="Violet"/>
        <xsd:enumeration value="Green"/>
        <xsd:enumeration value="Blue"/>
        <xsd:enumeration value="Maroon"/>
        <xsd:enumeration value="Pink"/>
        <xsd:enumeration value="Dichromatic"/>
        <xsd:enumeration value="Unknown"/>
    </xsd:restriction>
</xsd:simpleType>

接下来,创建采用新值的模式(一个正则表达式)。该模式是以 x: 为前缀的任意字符串。x: 是标准枚举列表和扩展列表之间的描绘程序。清单 5 显示这一模式。


清单 5. 用于扩展的正则表达式
				
<xsd:simpleType name="StringPatternType">
    <xsd:restriction base="xsd:string">
        <xsd:pattern value="x:\S.*"/>
    </xsd:restriction>
</xsd:simpleType>

最后,使用 <xsd:union> 标记结合列表与模式,如 清单 6 所示。


清单 6. 枚举列表与扩展模式的结合
				
<xsd:simpleType name="MyExtPersonEyeColorType">
    <xsd:union memberTypes="PersonEyeColorType StringPatternType"/>
</xsd:simpleType>

<xsd:element name="PersonEyeColor" type="MyExtPersonEyeColorType"/>

同一节点拥有标准和扩展值。两个值很容易分离,而且都可以用解析器验证,如 清单 7 所示。


清单 7. XML 实例样例
				
<PersonEyeColor>Black</PersonEyeColor>
<PersonEyeColor>x:Teal</PersonEyeColor>

  • 优点:
    • 同一元素可用于所有数据。
    • 用解析器对基本枚举列表进行验证。
    • 清晰地分隔扩展值。
    • 该解决方案允许在以后绑定新值。
  • 缺点:
    • 必须解析元素的内容,以确定是否已经被扩展。
    • 模式解析器必须支持正则表达式。
    • 需要 <xsd:union> 标记支持。




回页首


解决方案 4:使用单独的字段用于扩展

在该解决方案中,枚举字段不会变化。然而,您要在模式中设计一个扩展字段来容纳附加值。在本例中,初始列表是依赖型的(就业受益者和受养人之间的关系),如 清单 8 所示。


清单 8. 依赖关系枚举列表
				
<xsd:simpleType name="DependentRelationshipEnumType">
    <xsd:restriction base="xsd:string">
        <xsd:enumeration value="AdoptedChild"/>
        <xsd:enumeration value="Brother"/>
        <xsd:enumeration value="Child"/>
        <xsd:enumeration value="ExSpouse"/>
        <xsd:enumeration value="Father"/>
        <xsd:enumeration value="Granddaughter"/>
        <xsd:enumeration value="Grandson"/>
        <xsd:enumeration value="Grandfather"/>
        <xsd:enumeration value="Grandmother"/>
        <xsd:enumeration value="LifePartner"/>
        <xsd:enumeration value="Mother"/>
        <xsd:enumeration value="Sister"/>
        <xsd:enumeration value="Spouse"/>
        <xsd:enumeration value="Extension"/>
    </xsd:restriction>
</xsd:simpleType>

需要一个能够容纳新值的附加属性 — extension—。清单 9 显示了该属性。


清单 9. 依赖关系的 extension 属性
				
<xsd:complexType name="DependentRelationshipType">
    <xsd:simpleContent>
        <xsd:extension base="DependentRelationshipEnumType">
            <xsd:attribute name="extension" type="xsd:string"/>
        </xsd:extension>
    </xsd:simpleContent>
</xsd:complexType>			

<xsd:element name="DependentRelationship" type="DependentRelationshipType"/>

清单 10 显示一些反映 extension 的 XML 实例。


清单 10. 示例 XML 实例
				
<DependentRelationship>Child</DependentRelationship>
<DependentRelationship extension="MyNewRelationship">Extension</DependentRelationship>

  • 优点:
    • 不需要编辑原始模式。
    • 该解决方案允许在以后绑定新值。
    • 在原始模式中显式设计 extension 方法。
  • 缺点:
    • 在设计阶段,必须在每个枚举列表中设计 extension 方法。
    • 必须在元素中而不是在属性中设置枚举值。




回页首


解决方案 5: 基于文档的方法 —— 与字符串结合

注意:解决方案 5 和解决方案 6 违反了在一个周期内进行验证 这一要求。但是,我之所以在这里介绍它们,是因为在很多实际环境中可以使用这些方法。

在第 5 个解决方案中,使用 <xsd:union> 标记将枚举列表与字符串结合。实际上,该解决方案提示接收系统哪些值是标准的(包括包装和拼写)。但实际上字符串字段可以存放任何值。因此,解析器并不验证值。相反,这些值在第二个周期或者在接收数据的应用程序中验证。有些 XML 组织就使用这样的方案。

清单 11 显示通过 <xsd:union> 将一个枚举列表和 <xsd:string> 结合。因为任意值都可以是一个字符串,所以不用验证枚举列表。这些值建议使用标准值。


清单 11. 与字符串结合的 DayOfWeek 枚举列表
				
<xsd:simpleType name="DayOfWeekEnumType">
    <xsd:restriction base="xsd:string">
        <xsd:enumeration value="Sunday"/>
        <xsd:enumeration value="Monday"/>
        <xsd:enumeration value="Tuesday"/>
        <xsd:enumeration value="Wednesday"/>
        <xsd:enumeration value="Thursday"/>
        <xsd:enumeration value="Friday"/>
        <xsd:enumeration value="Saturday"/>
    </xsd:restriction>
</xsd:simpleType>
<xsd:element name="DayOfWeek" type="DayOfWeekEnumType"/>

<xsd:simpleType name="ExtendedDayOfWeekType">
	<xsd:union memberTypes="DayOfWeekEnumType xsd:string"/>
</xsd:simpleType>
<xsd:element name="DayOfWeek_solution5" type="ExtendedDayOfWeekType"/>

  • 优点:可以添加任意的扩展值,即使在后期绑定时也可以添加。
  • 缺点:
    • 解析器不验证枚举值,在第二个步骤中才进行验证。
    • 需要 <xsd:union> 标记支持。




回页首


解决方案 6: 基于文档的方法 —— 使用 <xsd:annotation>

要使用该方法,将实际的枚举值放到 <xsd:documentation> 标记内,同时将数据字段保留为一个简单字符串。清单 12 显示枚举值。


清单 12. 在 <xsd:documentation> 标记内的枚举值
				
<xsd:element name="DayOfWeek" type="xsd:string">
    <xsd:annotation>
        <xsd:documentation>
            <!-- suggested enumerations -->
            <xsd:enumeration value="Sunday"/>
            <xsd:enumeration value="Monday"/>
            <xsd:enumeration value="Tuesday"/>
            <xsd:enumeration value="Wednesday"/>
            <xsd:enumeration value="Thursday"/>
            <xsd:enumeration value="Friday"/>
            <xsd:enumeration value="Saturday"/>
        </xsd:documentation>
    </xsd:annotation>
</xsd:element>

  • 优点:
    • 可以添加任意的扩展值,即使在后期绑定时也可以添加。
    • 只需要最简单的 XML 模式特性。
  • 缺点:解析器不验证枚举值。




回页首


未讨论的方法

我省略了其他几种扩展枚举列表的解决方案。下面简单介绍了两种没有使用的方法:

  • 使用 <xsd:redefine> 标记: 通常不使用 XML 模式的这一特性,而且一般无法用工具实现它。该方法经常被认为是避免重新定义的最佳实践。
  • 使用 substitutionGroup 元素替换包含所有值的联合列表: 另外一种出色的解决方案,使用了替换组和联合。将原始列表与新列表联合以创建一个完整的枚举列表,然后使用 substitutionGroups 标记(或 <xsi:type> 标记)替换一个全局作用域元素。该方法的缺点是替换不能派生有效的联合,替换需要两个组件来自相同的基类型。扩展和限制是替换的两个有效方法。但是,根据 XML 模式规范,联合并不是有效的派生技术(参阅 参考资料)。




回页首


结束语

XML 模式设计者和实现人员需要一种方法来扩展现有的枚举列表。因为一旦原始列表创建后,规范不允许进行扩展,因此需要找到一种方法实际实现扩展。实现人员可以使用本文的示例来设计和扩展枚举列表。每种方法都有优缺点,没有一种方法在所有用例中都是最佳方法。那么,应该使用哪种方法呢?

请考虑这些经验法则:

  • 如果您习惯编辑原始枚举列表或模式,而且在设计阶段就知道所有要扩展的枚举值,最好使用 解决方案 1(手动编辑原始列表)或 解决方案 2(创建新列表并加入到原始列表中)。
  • 如果想使用相同的语义元素来包含基本枚举列表和扩展枚举列表,可以考虑 解决方案 3(与模式联合)。
  • 如果允许原始列表与扩展列表有不同的字段,可以使用 解决方案 4(独立的字段)。
  • 如果不想在解析器中解析枚举值,可以考虑 Genericode 方法或使用 解决方案 5解决方案 6

这些指导原则可以使模式设计者找到实用的最佳实践,而且可以帮助他们创建易于实现、可扩展的枚举列表。






回页首


下载

描述 名字 大小 下载方法 XML 模式和 XML 实例示例
ExtendEnumeratedListsCode.zip 2KB HTTP
关于下载方法的信息

你可能感兴趣的:(设计模式,xml,正则表达式)