原文:http://www.blogcn.com/User8/flier_lu/index.html?id=3618657
面向对象语言的一大重要特性就是支持类型的继承语义,子类型可以通过接口继承获得父类型的接口定义,也可以通过实现继承只获得父类型的实现。同时继承带来的多态性使得我们能够将针对基类进行操作的代码,直接应用到其子类上。
而在编写 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
面向对象语言的一大重要特性就是支持类型的继承语义,子类型可以通过接口继承获得父类型的接口定义,也可以通过实现继承只获得父类型的实现。同时继承带来的多态性使得我们能够将针对基类进行操作的代码,直接应用到其子类上。
而在编写 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