级别: 初级 Ayesha Malik (ayesha.malik@objectmachines.com), 高级顾问, Object Machines
2002 年 10 月 01 日 XML 模式提供了一组对 XML 文档的词汇表和语法进行约束和形式化的功能强大的工具。随着 XML 迅速地发展成为今后数据传输的格式,有一点很清楚:必须以有组织的方式来创建和存储 XML 的结构(由模式概述)。有面向对象设计经验的开发人员知道,一个灵活的体系结构能在整个系统中确保一致性并能帮助适应增长与变化。这篇指导性文章用面向对象的框架向您演示如何设计可扩展的、灵活的和模块化的 XML 模式。
在利用已建立的面向对象编程模式构造 XML 模式时,我用到了面向对象设计的三个主要原则:
封装、
继承和
多态性。为了帮助在本文的上下文中讨论面向对象框架,我用一个假想的公司 Bond Publishing 作为示例。
Bond Publishing是一家书籍和杂志供应商,它在底特律的一些工厂出版其产品。这些工厂交付产品时会使用 XML 以电子方式发送关于产品的信息,随后将这些信息转发给分销公司 Distributor。每个月的月尾,Distributor 返回关于产品销售的信息。图 1 概述了处理流程。
图 1. Bond Publishing 工作流
Bond Publishing 的业务在持续扩张,不断有新的产品加入到它的产品清单。该公司希望它的 XML 是可扩展的,这样的话,无需大量重新编写模式即可方便地添加新的产品。Bond Publishing 将模式发送给工厂和分销商以便他们理解收到的 XML 的结构。这样的协议一旦达成,则该工作流中的每个实体都可以在数据到达目的地时验证收到的数据。
因此,需要两个 XML 文档:
- 包含
产品信息的 XML(Product.xml)
- 包含
销售信息的 XML(Sales.xml)
封装
封装指的是使对象成为
黑箱的概念,这样当使用对象时就不知道它的内部工作原理。在模式方面,这一概念就变成了创建这样的类型:类型是预先定义的,并且只要引用该类型就可以很容易地在任何地方访问它。
在本文的示例中,会创建一个
Book 类型,它指明一本书必须有
Author(作者) 、
Title(书名) 和
ISBN 。因此,
Book 类型封装了所有与书有关的信息。将该数据类型放入名为 DataTypes.xsd 的数据类型模式中(请参阅清单 1)。该模式包含可以被许多不同模式使用的所有一般数据类型。例如,Sales、Product 和 Accounting 模式都需要
Book 的定义。
清单 1. DataTypes.xsd
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xs:complexType name="Book">
<xs:sequence>
<xs:element name="Title" type="xs:string"/>
<xs:element name="Author" type="xs:string"/>
<xs:element name="ISBN" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="Magazine">
<xs:sequence>
<xs:element name="Title" type="xs:string"/>
<xs:element name="Editor" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
|
由于这只是一个数据类型库,所以没有根元素。其实它只是一个复杂元素的集合,每个复杂元素都描述一个组件的详细信息。为了成为可引用的元素,元素必须是
全局元素 ― 也就是说,是
<schema> 的直接子元素的元素声明。局部元素是嵌套在另一个组件内的元素声明;例如,
Title 元素是全局元素
Book 内的局部元素。
访问数据类型库最简单的方法是将其位置包括在您的模式中。用这种方法,被包括的模式采用包含它的模式的名称空间。在这种情况下,DataType 模式的名称空间将是“http://www.Bond.com”。这就是
变色龙效果,因为被包含的模式可根据包含它的模式改变自己的名称空间,就象变色龙改变它的外表一样。如您在清单 2 中所见,Product.xsd 模式包含 DataType.xsd 模式。
Product.xsd 是定义 Product.xml 的模式,Product.xml 包含有关从印书厂送往 Bond Publishing 的书籍的信息。当您想使用
Book 组件时,只需输入
type="Book" ,Product.xsd 就会使用 DataType.xsd 中的定义。杂志印刷厂可以用类似的方式使用
Magazine 数据类型的定义。
清单 2. Product.xsd
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema targetNamespace="http://www.Bond.com"
xmlns="http://www.Bond.com"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified" attributeFormDefault="unqualified">
<xs:include schemaLocation="DataType.xsd"/>
<xs:element name="Product">
<xs:complexType>
<xs:sequence>
<xs:element name="Books">
<xs:complexType>
<xs:sequence maxOccurs="unbounded">
<xs:element name="Book" type="Book"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
|
工厂与 Bond Publishing 共享 Product.xsd 和 DataTypes.xsd,这样就可以创建和验证他们交换的数据。清单 3 中的代码片段就是产生的 XML 文档(Product.xml)的一个示例。创建象这样的组件有几个好处。一个好处是可以重用组件并能方便地更新它,因为更新工作可以在一处完成(例如,DataType.xsd)。另一个好处是可以随着业务的拓展而向库添加组件。这样,使用封装为您的系统提供了灵活性和标准化。
清单 3. Product.xml
<?xml version="1.0" encoding="UTF-8"?>
<Product xmlns="http://www.Bond.com"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.Bond.com Product.xsd">
<Books>
<Book>
<Title>Complete Works</Title>
<Author>Shakespeare</Author>
<ISBN>0517053616</ISBN>
</Book>
<Book>
<Title>Being Rich is Cool</Title>
<Author>Donald Trump</Author>
<ISBN>05146553616</ISBN>
</Book>
</Books>
</Product>
|
继承
软件重用是面向对象设计的另一个重要部分。您可以通过
继承实现软件重用。在编程语言中,通过子类提供这一能力。在 XML 模式中,可以使用
抽象类或仅使用指定基类型
扩展或
派生的标记来做到这一点。
抽象类
抽象类不能在实例文档中使用;它们只是为其派生类型提供占位符。在下面的例子中,将
Magazine 作为有
Title 和
Editor 的抽象类型。女性杂志(
WomensMagazine )是从抽象的
Magazine 类型派生而来,因此继承了
Title 和
Editor 。可以通过在
WomensMagazine 的定义中使用代码
extension base="Magazine" 实现这一点。此外,女性杂志还有化妆品广告,因此要将
CosmeticsAdvert 添加到继承的基类型。
清单 4. DataTypes.xsd
<xs:complexType name="Magazine" abstract="true">
<xs:sequence>
<xs:element name="Title" type="xs:string"/>
<xs:element name="Editor" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="WomensMagazine">
<xs:complexContent>
<xs:extension base="Magazine">
<xs:sequence maxOccurs="unbounded">
<xs:element name="CosmeticsAdvert"
type="xs:string"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
|
如您在清单 5 中所见,实例文档包含所有的基类特征以及添加的
CosmeticsAdvert 组件。
清单 5. Product.xml
<?xml version="1.0" encoding="UTF-8"?>
<Product xmlns="http://www.Bond.com"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.Bond.com Product.xsd">
<Magazines>
<WomensMagazine>
<Title>Vogue</Title>
<Editor>Anna Wintour</Editor>
<CosmeticsAdvert>L'oreal</CosmeticsAdvert>
<CosmeticsAdvert>Estee Lauder</CosmeticsAdvert>
</WomensMagazine>
</Magazines>
</Product>
|
通过扩展实现的派生
另一个派生的示例是使用不带抽象类型的扩展。
BookSales 类型包含有关书的信息,并且包含书的销售数量和售价。可以通过使用
extension base 关键字扩展
Book 类型以创建
BookSales 类型。DataTypes 模式的以下代码片段演示了如何做到这一点。
清单 6. DataTypes.xsd
<xs:complexType name="Book">
<xs:sequence>
<xs:element name="Author" type="xs:string"/>
<xs:element name="Title" type="xs:string"/>
<xs:element name="ISBN" type="xs:string"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="BookSales">
<xs:complexContent>
<xs:extension base="Book">
<xs:sequence>
<xs:element name="Number" type="xs:integer"/>
<xs:element name="Price" type="xs:double"/>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
|
这意味着无论何时引用元素
BookSales ,XML 实例文档都将在包含书名、作者和 ISBN 的同时包含销量和售价信息(请参阅清单 7)。
清单 7. Product.xml
<?xml version="1.0" encoding="UTF-8"?>
<Sales xmlns="http://www.Bond.com"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.Bond.com Sales.xsd">>
<Books>
<BookSales>
<Author>Shakespeare</Author>
<Title>Complete Works</Title>
<ISBN>0517053616</ISBN>
<Number>234</Number>
<Price>14.50</Price>
</BookSales>
</Books>
</Sales>
|
通过限制实现的派生
通过限制实现的派生在希望创建基类型的子集的情况下有用。一个这样的示例是限制值的范围。在本例中,希望限制
Pamphlet (小册子)的定义,除了没有作者以外,它与
Book 完全相似。在创建小册子时,可以使用代码
restriction base="Book" 。
清单 8. DataTypes.xsd
<xs:complexType name= "Pamphlet">
<xs:complexContent>
<xs:restriction base="Book">
<xs:sequence>
<xs:element name="Title" type="xs:string"/>
<xs:element name="ISBN" type="xs:integer"/>
</xs:sequence>
</xs:restriction>
</xs:complexContent>
</xs:complexType>
|
禁止派生
在编程时,可以将一些接口和类声明为
final,这样就不能用它们产生子类。通过使一些组件成为 final,可以在模式中实现同样的目标。例如,Bond Publishing 有一个非常严格的描述自己的方法:名称、总部和 CTO。公司不希望任何人能够扩展这个定义。因此,它将组件
BondDefinition 声明为 final,如下所示。当使用关键字
#all 时,对组件既不能扩展也不能限制。在另外两种情况下,
final 会禁止扩展或限制。
<xsd:complexType name="BondDefinition" final="#all">
<xsd:complexType name="BondDefinition" final="extension">
<xsd:complexType name="BondDefinition" final="restriction">
|
多态性
多态性意味着在不同的上下文中对某对象赋予不同的意义或用法的能力 ― 具体而言,就是允许对象有多种形式。在象 Java 技术这样的编程语言中,多态性指的是对输入的不同反应。更具体地说,它是子类对相同消息做出不同反应的能力。简单的继承允许两个不同的子类向那些继承自超类的子类添加不同的方法,而多态性则增加了实现同一方法的思想,但用的是不同的方法和不同的子类。
因为 XML 不是行为语言,所以多态性出现在属性级别。让我们采用上面的继承示例。我们说过
Pamphlet 从
Book 继承
Title 和
ISBN 属性。然而,现在您希望指定
Pamphlet 的
ISBN 具有其专有的特征,而且与
Book 的特征不同。换句话说,您知道
Pamphlet 的
ISBN 一直有五位数字的限制。您希望在您的模式中实施这一规则,以便在获得五位数以上时有一个验证错误。
因此您需要构建一个名为
PamphletISBN 的新类型的组件。您从类型
ISBN 派生该组件并限制其 ISBN 最大为五位。然后,就象您在
清单 8 中做的那样,构造
Pamphlet 组件。但是,这一次您将
ISBN 的类型设置为
PamphletISBN ,这样就可保证虽然元素的名称将保持为
ISBN ,但验证程序会检查更受限制的
PamphletISBN 。改变子类型中派生组件的实现的能力演示了 XML 模式中多态性的一种形式。
清单 9. DataTypes.xsd
<xs:complexType name="Pamphlet">
<xs:complexContent>
<xs:restriction base="Book">
<xs:sequence>
<xs:element name="Title"
type="xs:string"/>
<xs:element name="ISBN"
type="PamphletISBN"/>
</xs:sequence>
</xs:restriction>
</xs:complexContent>
</xs:complexType>
<xs:simpleType name="ISBN">
<xs:restriction base="xs:string"/>
</xs:simpleType>
<xs:simpleType name="PamphletISBN">
<xs:restriction base="ISBN">
<xs:maxLength value="5"/>
</xs:restriction>
</xs:simpleType>
|
清单 10. Product.xml
<?xml version="1.0" encoding="UTF-8"?>
<Product xmlns="http://www.Bond.com"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.Bond.com Product.xsd">
<Books>
<Book>
<Title>Complete Works</Title>
<Author>Shakespeare</Author>
<ISBN>0517053616</ISBN>
</Book>
</Books>
<Magazines>
<WomensMagazine>
<Title>Vogue</Title>
<Editor>Anna Wintour</Editor>
<CosmeticsAdvert>L'oreal</CosmeticsAdvert>
<CosmeticsAdvert>Estee Lauder</CosmeticsAdvert>
</WomensMagazine>
</Magazines>
<Pamphlets>
<Pamphlet>
<Title>Guide to Yoga</Title>
<ISBN>25274</ISBN>
</Pamphlet>
</Pamphlets>
</Product>
|
更多面向对象的概念
用于去耦合的设计模式
最近,已经出现了一些解决 XML 中
去耦合(decoupling)和
内聚(cohesiveness)的设计模式。我们已经讨论了如何创建可重用的组件。现在,您将学习如何改变数据类型的粒度。这与试图回答“如何重构代码,在给定情形下多大程度的重构才是合适的?”这一问题相似。目前有三种设计模式,它们代表着创建组件时的三种级别的粒度:
- 级别 1:俄罗斯娃娃(Russian Doll)
- 级别 2:意大利香肠片(Salami Slice)
- 级别 3:软百叶窗(Venetian Blind)
级别 1:俄罗斯娃娃
将所有相关组件包含于自身的组件(象一个俄罗斯娃娃)。请观察下面的示例:类型
Book 由组件
Title 、
Author 和
ISBN 组成。这些组件都在
Book 组件内局部定义。俄罗斯娃娃式设计是最不易扩展的,因为四个类型中有三个是局部定义的,所以它们不能被任何其它组件访问。
清单 11. 俄罗斯娃娃式设计
<xs:element name="Book">
<xs:complexType>
<xs:sequence>
<xs:element name="Title" type="xs:string"/>
<xs:element name="Author" type="xs:string"/>
<xs:element name="ISBN" type="xs:integer"/>
</xs:sequence>
</xs:complexType>
</xs:element>
|
级别 2:意大利香肠片
通过引用不同的类型将组件组合在一起或聚合在一起。这样,
Book 、
Title 、
Author 和
ISBN 每一个都是全局元素。
Book 类型则引用其它三个元素作为其定义的部分,如清单 12 所示。该设计模式被称为意大利香肠片,因为每个组件或类型代表一片香肠。这一级别比俄罗斯娃娃级别有更多的粒度。
清单 12. 意大利香肠片式设计
<xs:element name="Title" type="string"/>
<xs:element name="Author" type="string"/>
<xs:element name="ISBN" type="integer"/>
<xs:element name="Book">
<xs:complexType>
<xs:sequence>
<xs:element ref="Title"/>
<xs:element ref="Author"/>
<xs:element ref="ISBN"/>
</xs:sequence>
</xs:complexType>
</xs:element>
|
级别 3:软百叶窗
将所有的元素和组件都定义为类型。这意味着当您定义名为
“Title” 的组件时,即使它是字符串这一事实决定了它是一个简单类型,您也需要通过类型
“Title” 引用它。这表明了将组件构造到其最细微阶段中的最高级别。
因为每个组件都是一个类型,所以如果
elementFormDefault="qualified" 的话,每个组件都可以由一个名称空间限定。象软百叶窗那样显示或隐藏名称空间的能力使得该设计模式得到这一名称。
清单 13. 软百叶窗式设计
<xs:simpleType name="Title">
<xs:restriction base="xs:string"/>
</xs:simpleType>
<xs:simpleType name="Name">
<xs:restriction base="xs:string">
<xs:minLength value="1"/>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="Book">
<xs:sequence>
<xs:element name="Title" type="Title"/>
<xs:element name="Author" type="Name"/>
</xs:sequence>
</xs:complexType>
|
如何在这三种模式中进行选择取决于您的业务需求。如果模式大小是要考虑的问题,那么俄罗斯娃娃比较适合。然而,如果可扩展性是主要的关注对象,那么软百叶窗就是可应用的最佳设计模式。
通过使用名称空间模拟包
面向对象编程非常强调根据类的服务来封装它们。包结构对代码加以组织并推动模块化和维护。通过根据 XML 模式的功能来组织它们,您可以获得相似的好处。例如,会计部门创建自己的模式 Accounting.xsd,而销售部门用 Marketing.xsd 指定其数据类型和定义。
为了区别这些模式,除了将它们保存为不同的文件之外您还必须做更多的工作 ― 需要为它们分配单独的名称空间。因此会计部门在名称空间 http://www.Bond.com/Accounting 下组织其模式。图 2 显示了如何用这种方式分隔模式来模拟软件包。通过查看 .xsd 文件的名称空间,很容易了解谁负责这个模式。
图 2. 模式和软件包
扩展模式
到目前为止我们讨论的所有模式都位于 http://www.Bond.com 名称空间中。这意味着 Bond Publishing 唯一地定义了所有组件,如
Book 、
Magazine 和
BookSales 。但是,Distributor 有许多客户(其中包括 Bond Publishing)并且需要扩展上述
BookSales 模式以包括自己的信息,如分销商酬金。因为分销商报酬的特征是由 Distributor 定义的,所以这些特征定义在它的名称空间 http://www.Distributor.com 中,其模式名为 Fees.xsd,如下所示。
两家公司都希望 XML 实例文档指出每个组件从哪个名称空间派生其定义。指出名称空间能帮助解析和查询数据,并能帮助分隔两家公司的定义。该方法需要通过元素的名称空间代码
限定元素。
清单 14. Fees.xsd
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema targetNamespace="http://www.Distributor.com"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns="http://www.Distributor.com" elementFormDefault="qualified">
<xs:element name="Fees">
<xs:complexType>
<xs:sequence>
<xs:element name="Date" type="xs:date"/>
<xs:element name="Amount" type="xs:double"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
|
接下来,Distributor 将 Fees 模式导入到 Sales 模式,有效地对后一模式进行了扩展。Distributor 和 Bond Publishing 现在都将共享新的 Sales 和 Fees 模式。正象设计软件系统时共享协议是必需的一样,共享用来发送数据的格式是对于公司间通信是必不可少的。两家公司现在都有更新的 Sales.xsd 用于验证和查询。
清单 15. Sales.xsd
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema targetNamespace="http://www.Bond.com"
xmlns:dist="http://www.Distributor.com"
xmlns="http://www.Bond.com"
xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xs:include schemaLocation="DataType.xsd"/>
<xs:import namespace="http://www.Distributor.com"
schemaLocation="Fees.xsd"/>
<xs:element name="Sales">
<xs:complexType>
<xs:sequence>
<xs:element name="Books">
<xs:complexType>
<xs:sequence maxOccurs="unbounded">
<xs:element name="BookSales" type="BookSales"/>
<xs:element ref="dist:Fees"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
|
到达 Bond Publishing 后生成的 XML 会用前缀
dist 限定所有由 Distributor 名称空间定义的信息。因此,Sales.xml 中任何特定于 Distributor 的元素都有这个前缀。
清单 16. Sales.xml
<?xml version="1.0" encoding="UTF-8"?>
<Sales xmlns="http://www.Bond.com"
xmlns:dist="http://www.Distributor.com"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.Bond.com Sales.xsd">
<Books>
<BookSales>
<Title>Complete Works</Title>
<Author>Shakespeare</Author>
<ISBN>0517053616</ISBN>
<Number>98</Number>
<Price>31</Price>
</BookSales>
<dist:Fees>
<dist:Date>2002-10-13</Distributor:Date>
<dist:Amount>300</Distributor:Amount>
</dist:Fees>
</Books>
</Sales>
|
扩展和业界标准
这一技术是扩展模式的最简便的方法。它不只在公司希望分隔其定义时有用,而且当公司希望添加业界标准以进行内部使用时,它也很有用。例如,公司使用由业界标准 ebXML 指定的模式来交换企业信息。通过使用 ebXML,公司无需定义和说明其模式即可与许多同行通信;每个公司都遵守由 ebXML 定义的业界模式。如果公司想使用业界标准,但希望添加专有的信息以进行内部通信和存储,它们可使用前面描述的方法
扩展业界标准。
因为企业意识到标准对于一致性、可伸缩性和集成是不可缺少的,所以业界标准正在成为规范。理解如何处理不符合某人的信息需要的业界标准将成为以后几年的重要主题。使用本文描述的扩展,公司可以为它们自己的内部使用扩展业界模式体系结构,并且在它们希望将 XML 在外部用于公司间通信的任何时候,可以去掉附加的信息。用这种方式,可以很容易地再将 XML 文档精炼成其业界标准级别,并重用它们以进行业界通信。
结束语
如果您的系统要使用 XML 在内部或外部传输数据信息,那么您应认真地考虑如何正确地设计 XML 模式。在本文中,您已经看到如何创建使用继承、封装和多态性的模式,并且还大致了解了 XML 模式设计中新兴的设计模式。利用这些面向对象的框架有助于您设计模块化和可扩展的、保持数据完整性和易于与其它 XML 协议集成的 XML 模式。
参考资料
关于作者
对本文的评价
|