面向对象语言的一大重要特性就是支持类型的继承语义,子类型可以通过接口继承获得父类型的接口定义,也可以通过实现继承只获得父类型的实现。同时继承带来的多态性使得我们能够将针对基类进行操作的代码,直接应用到其子类上。
而在编写 XML Schema 进行 XML 结构设计的时候,我们往往也喜欢能够实现类似的语义。在设计初期只定义一个通用的简化版本类型,然后随着后期需求的不断变更,继承而非对现有版本进行修改,在不调整现有代码的基础上最大限度进行扩展。
以前在实现类似语义的时候,往往是通过 Choice 或者 Group 类似的方式来模拟。虽然语义上很类似,但仍然需要对现有 Schema 做较大的调整,并且无法约束继承类树上哪些类型可以被实例化而哪些不行。
其实 XML Schema 规范中提供了抽象元素 (Abstract Element) 和抽象类型 (Abstract Type) 来实现这一语义。
XML Schema Part 0: Primer - 4.7 Abstract Elements and Types
我们可以简单定义一个元素表示注释
|
此元素的 abstract 属性为 true,所以此元素如果被引用或在父元素中直接使用,会无法通过校验。必须通过 Substitution Groups 机制定义替换的元素,才能满足父元素实例化的需求。
|
这里定义了 shipComment 和 customerComment 两个元素,用于替换前面的抽象元素 comment。
|
通过这种机制,我们可以在定义 item 元素的时候,通过预先定义一个抽象元素 comment,支持使用者以后根据需求对 item 元素进行扩充。同时可以限制元素 comment 不能被直接使用,而只是作为占位符存在。
这种抽象机制也可以被应用于类型的定义,可以完全实现 OO 编程中的抽象类型的语义。
|
这儿定义的 Vehicle 类型是一个抽象类型,Car 和 Plane 是其子类,可以对其进行功能上的扩展。因为 transport 元素被定义为抽象基类 target:Vehicle,所以在使用 transport 元素时,必须通过 xsi:type 显式指定一个子类来实例化。
|
上面这样的实例化是无效的,因为 transport 的类型是一个抽象类型,必须象下面这样显式指定子类实例化元素。
|
在了解了这些基本思想和使用方法后,我们来看一个实际的抽象元素使用示例。
编写 XML Schema 的目标是要为一个目录服务器的连接提供必要的信息,但又不希望在定义整体文件结构时限定死连接只能支持哪些类型。因此我选择在整个文件中定义一个抽象元素 Connection 来表示服务器连接信息,此元素是一个 Connection.class 类型的实例,但因为具有抽象属性,故而不能直接在其父类型 DirectoryService 元素中实例化。
|
也就是说下面这样的使用方式是无效的
|
使用者必须通过预定义或自定义的 Connection 元素的子类,来提供有效的连接信息,如预定义了一个 LDAP 服务连接信息类。
|
LdapConnection 元素是 LdapConnection.class 类型的实例,它一方面通过 substitutionGroup 属性定义对 Connection 元素的取代,另一方面通过 LdapConnection.class 类型定义时的 extension 方式继承 Connection 抽象元素的类型 Connection.class。
使用相同的机制,还可以为 Connection.class 类型使用的 Security 抽象元素定义不同的替代元素。
|
这样一来,就可以从连接信息和连接安全信息两个层面对现有架构进行扩充。
|
如果要增加新的服务器连接,只需要 include 现有类型定义文件 DirectoryService.xsd,然后定义类似 LdapConnection 的元素即可,如 ADConnection;而如果要增加新的认证方式,也只需要定义类似 SimpleSecurity 的元素即可,如 GAPISecurity。
非常完美的抽象基类解决方案,呵呵
btw: 最近手头项目工作忙得要死,又被某人天天催稿交 XCon 的皇粮,没时间折腾新东西更新 blog,只好把项目上的一些体会贴上来凑数 :P