Flier's Sky

天空,蓝色的天空,眼睛看不到的东西,眼睛看得到的东西

导航

在 XML Schema 中使用抽象节点实现元素继承语义

Posted on 2004-09-02 00:38  Flier Lu  阅读(3725)  评论(6编辑  收藏  举报
原文: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

    我们可以简单定义一个元素表示注释

<element name="comment" type="string" abstract="true"/>

    此元素的 abstract 属性为 true,所以此元素如果被引用或在父元素中直接使用,会无法通过校验。必须通过 Substitution Groups 机制定义替换的元素,才能满足父元素实例化的需求。
<element name="shipComment" type="string" substitutionGroup="ipo:comment"/>
<element name="customerComment" type="string" substitutionGroup="ipo:comment"/>

    这里定义了 shipComment 和 customerComment 两个元素,用于替换前面的抽象元素 comment。
.
 
<items>
   
<item partNum="833-AA">
     
<productName>Lapis necklace</productName>
     
<quantity>1</quantity>
     
<USPrice>99.95</USPrice>
     
<ipo:shipComment>
       Use gold wrap if possible
     
</ipo:shipComment>
     
<ipo:customerComment>
       Want this for the holidays!
     
</ipo:customerComment>
     
<shipDate>1999-12-05</shipDate>
   
</item>
 
</items>
.


    通过这种机制,我们可以在定义 item 元素的时候,通过预先定义一个抽象元素 comment,支持使用者以后根据需求对 item 元素进行扩充。同时可以限制元素 comment 不能被直接使用,而只是作为占位符存在。

    这种抽象机制也可以被应用于类型的定义,可以完全实现 OO 编程中的抽象类型的语义。
<schema xmlns="http://www.w3.org/2001/XMLSchema"
         targetNamespace
="http://cars.example.com/schema"
         xmlns:target
="http://cars.example.com/schema">

 
<complexType name="Vehicle" abstract="true"/>

 
<complexType name="Car">
  
<complexContent>
   
<extension base="target:Vehicle"/>
  
</complexContent>
 
</complexType>

 
<complexType name="Plane">
  
<complexContent>
   
<extension base="target:Vehicle"/>
  
</complexContent>
 
</complexType>

 
<element name="transport" type="target:Vehicle"/>
</schema>

    这儿定义的 Vehicle 类型是一个抽象类型,Car 和 Plane 是其子类,可以对其进行功能上的扩展。因为 transport 元素被定义为抽象基类 target:Vehicle,所以在使用 transport 元素时,必须通过 xsi:type 显式指定一个子类来实例化。
<transport xmlns="http://cars.example.com/schema"/>

    上面这样的实例化是无效的,因为 transport 的类型是一个抽象类型,必须象下面这样显式指定子类实例化元素。

<transport xmlns="http://cars.example.com/schema"
           xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance"
           xsi:type
="Car"/>


    在了解了这些基本思想和使用方法后,我们来看一个实际的抽象元素使用示例。

    编写 XML Schema 的目标是要为一个目录服务器的连接提供必要的信息,但又不希望在定义整体文件结构时限定死连接只能支持哪些类型。因此我选择在整个文件中定义一个抽象元素 Connection 来表示服务器连接信息,此元素是一个 Connection.class 类型的实例,但因为具有抽象属性,故而不能直接在其父类型 DirectoryService 元素中实例化。
<xs:complexType name="Connection.class">
    
<xs:annotation>
        
<xs:documentation>抽象服务器连接信息类</xs:documentation>
    
</xs:annotation>
    
<xs:sequence>
        
<xs:element ref="Security"/>
    
</xs:sequence>
    
<xs:attribute name="class" type="class.type" use="required"/>
</xs:complexType>

<xs:element name="Connection" type="Connection.class" abstract="true">
    
<xs:annotation>
        
<xs:documentation>服务器连接信息</xs:documentation>
    
</xs:annotation>
</xs:element>

<xs:element name="DirectoryService">
    
<xs:annotation>
        
<xs:documentation>目录服务映射定义</xs:documentation>
    
</xs:annotation>
    
<xs:complexType>
        
<xs:sequence>
            
<xs:element ref="Connection">
                
<xs:annotation>
                    
<xs:documentation>服务器连接信息</xs:documentation>
                
</xs:annotation>
            
</xs:element>
            
        
</xs:sequence>
    
</xs:complexType>
</xs:element>

    也就是说下面这样的使用方式是无效的
<DirectoryService>
  
<Connection>
    
  
</Connection>
</DirectoryService>

    使用者必须通过预定义或自定义的 Connection 元素的子类,来提供有效的连接信息,如预定义了一个 LDAP 服务连接信息类。
<xs:complexType name="LdapConnection.class">
    
<xs:annotation>
        
<xs:documentation>LDAP 服务器连接信息类</xs:documentation>
    
</xs:annotation>
    
<xs:complexContent>
        
<xs:extension base="Connection.class">
            
<xs:sequence>
                
<xs:element name="ContextFactory"/>
                
<xs:element name="ProviderUrl"/>
            
</xs:sequence>
        
</xs:extension>
    
</xs:complexContent>
</xs:complexType>

<xs:element name="LdapConnection" type="LdapConnection.class" substitutionGroup="Connection">
    
<xs:annotation>
        
<xs:documentation>LDAP 服务器连接信息</xs:documentation>
    
</xs:annotation>
</xs:element>

    LdapConnection 元素是 LdapConnection.class 类型的实例,它一方面通过 substitutionGroup 属性定义对 Connection 元素的取代,另一方面通过 LdapConnection.class 类型定义时的 extension 方式继承 Connection 抽象元素的类型 Connection.class。
    使用相同的机制,还可以为 Connection.class 类型使用的 Security 抽象元素定义不同的替代元素。
<xs:element name="Security" abstract="true">
    
<xs:annotation>
        
<xs:documentation>安全信息</xs:documentation>
    
</xs:annotation>
</xs:element>
<xs:element name="SimpleSecurity" substitutionGroup="Security">
    
<xs:annotation>
        
<xs:documentation>简单认证安全信息</xs:documentation>
    
</xs:annotation>
    
<xs:complexType>
        
<xs:sequence>
            
<xs:element name="Authentication">
                
<xs:annotation>
                    
<xs:documentation>验证方法</xs:documentation>
                
</xs:annotation>
                
<xs:simpleType>
                    
<xs:restriction base="xs:string">
                        
<xs:enumeration value="simple"/>
                    
</xs:restriction>
                
</xs:simpleType>
            
</xs:element>
            
<xs:element name="Principal" type="xs:string">
                
<xs:annotation>
                    
<xs:documentation>用户标识符</xs:documentation>
                
</xs:annotation>
            
</xs:element>
            
<xs:element name="Credentials" type="xs:string">
                
<xs:annotation>
                    
<xs:documentation>密码</xs:documentation>
                
</xs:annotation>
            
</xs:element>
        
</xs:sequence>
    
</xs:complexType>
</xs:element>

    这样一来,就可以从连接信息和连接安全信息两个层面对现有架构进行扩充。
<DirectoryService xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="DirectoryService.xsd">
    
<LdapConnection class="com.nsfocus.platform.util.ldap.LdapConnection">
        
<SimpleSecurity>
            
<Authentication>simple</Authentication>
            
<Principal>username</Principal>
            
<Credentials>password</Credentials>
        
</SimpleSecurity>
        
<ContextFactory>com.sun.jndi.ldap.LdapCtxFactory</ContextFactory>
        
<ProviderUrl>ldap://127.0.0.1:389</ProviderUrl>
    
</LdapConnection>
    
</DirectoryService>

    如果要增加新的服务器连接,只需要 include 现有类型定义文件 DirectoryService.xsd,然后定义类似 LdapConnection 的元素即可,如 ADConnection;而如果要增加新的认证方式,也只需要定义类似 SimpleSecurity 的元素即可,如 GAPISecurity。

    非常完美的抽象基类解决方案,呵呵

btw: 最近手头项目工作忙得要死,又被某人天天催稿交 XCon 的皇粮,没时间折腾新东西更新 blog,只好把项目上的一些体会贴上来凑数 :P