Composition versus Subclassing

(A Collectively Developed Set of Schema Design Guidelines)

Table of Contents

Issue

Should you design your schemas to build type hierarchies (design by subclassing) or should you design your schemas to aggregate components (design by composition)?

Introduction

Note: you should read and understand the material in Variable Content Containers prior to reading this.

Let's examine the two design approaches with the following scenario. You are tasked to design a schema for a 35mm camera. Here's what a sample camera instance looks like:

<camera>
    <lens>
        <length>35mm</length>
        <f-stop>1.8</f-stop>
        <shutter-speed>
             <min>1/30 sec</min>
             <max>2 sec</max>
        </shutter-speed>
    </lens>
    <housing>
        <description>Ergonomically designed casing for easy handling</description>      
    </housing>
    <film>
        <ASA>200</ASA>
        <exposures>24</exposures>
        <brand>Kodak</brand>
    </film>
</camera>
Here's how you might design your schema:
<xsd:element name="camera">
    <xsd:complexType>
        <xsd:sequence>
            <xsd:element name="lens" type="lens-type"/>
            <xsd:element name="housing" type="housing-type"/>
            <xsd:element name="film" type="film-type"/>
        </xsd:sequence>
    </xsd:complexType>
</xsd:element>
Suppose that a little while later your requirements expand to require your schema to support cameras with mini-disks to hold the image (i.e., a digital camera) as well as cameras that use 35mm film. One approach is to create a type hierarchy (SLR - single lens reflex - represents the original 35mm film camera):
      camera
       /  \
     SRL  digital
Let's suppose that later your requirements expand further to require you to support cameras with large-format film (3 x 5). Continuing with the hierarchical design methodology we now have:
      camera
      / |  \
    SLR | digital
        |
    large-format
Suppose next you need to support cameras with infrared film, and then ... Over time you may need to support cameras with dozens of different recording-mediums, and the hierarchy will expand accordingly.

There are two major disadvantages of this approach:

1. Complexity: suppose that camera derives from optical-device. Consider now what is involved with understanding a SRL:

      optical-device
            |
         camera
            |
           SLR
To understand a SLR you must understand the camera. But to understand the camera you must understand the optical-device (white box reuse). In a long hierarchical chain it can be exceedingly difficult to understand an element because this approach forces you to understand the entire chain.

2. Tight Coupling: all the different kinds of cameras are tightly bound together by a common root. If the root should change then everything below it is potentially impacted. This is contrary to the principle of designing systems comprised of loosely coupled components. Thus, this approach leads to brittle, unmodifiable designs.

A better approach is to recognize upfront that requirements typically expand. "Find what varies and encapsulate it." [1] In our scenario it is the requirement for different types of recording-mediums that keeps growing. It should be encapsulated. Create a component for each type of recording-medium. Design the camera so that each recording-medium component may be plugged into the camera:

      ----------
      | Camera |
      ----------
          ^       --------   --------   -------
          |-------| 35mm |   | disk |   | 3x5 |
                  --------   --------   -------
When a new recording-medium is required you simply encapsulate it, and it is ready to be used in the camera. Contrast this with the hierarchical approach where it was necessary to create a new subclass which gets linked into an ever-increasingly complex type tree.

Let's look at the XML Schema representation of the camera:

<xsd:element name="camera">
    <xsd:complexType>
        <xsd:sequence>
            <xsd:element name="lens" type="lens-type"/>
            <xsd:element name="housing" type="housing-type"/>
            <xsd:element ref="recording-medium"/>
        </xsd:sequence>
    </xsd:complexType>
</xsd:element>
The film element has been replaced by a reference to an abstract recording-medium element:
<xsd:element name="recording-medium" abstract="true" type="recording-medium-type"/>
The 35mm, disk, and 3x5 components are simply declared as standalone elements which may be substituted for the abstract recording-medium element:
<xsd:element name="35mm" substitutionGroup="recording-medium" type="35mm-type"/>
<xsd:element name="disk" substitutionGroup="recording-medium" type="disk-type"/>
<xsd:element name="3x5" substitutionGroup="recording-medium" type="3x5"/>
The advantages of the design-by-composition approach are:

1. Simplicity: all of the components are stand-alone. They can be understood and developed independently, in isolation (separation of concerns, black box reuse).

2. Decoupled, changeable parts: this approach focuses on creating a collection of independent, loosely coupled components. This enables robust, modifiable, plug-and-play designs.

Note: the solution shown above is just one way to implement design-by-composition. Let's consider other approaches:

How an Application Processes an Element that is Designed using Composition

In the design-by-composition approach we have this collection of elements:
camera
  - lens
  - housing
  - recording-medium(abstract)
       ^
       |
       ---------- 35mm or disk or 3x5 etc
A stylesheet application can process a camera element by delegating to each subordinate element:
<xsl:template match="camera">
    <!-- delegate to lens element -->
    <xsl:apply-templates select="lens"/>
    <!-- delegate to housing element -->
    <xsl:apply-templates select="housing"/>
    <!-- delegate to whatever recording-medium component is present -->
    <xsl:apply-templates select="*[last()]"/>
</xsl:template>
As new types of recording-medium components are required you simply drop in a template rule. For example:
<xsl:template match="35mm">
  ... process 35mm recording-medium ...
</xsl:template>

<xsl:template match="disk">
  ... process disk recording-medium ...
</xsl:template>

<xsl:template match="3x5">
  ... process 3x5 recording-medium ...
</xsl:template>
All of these recording-medium components have a type that is the same as, or derived from, the recording-medium-type. Thus, if desired, we could have a single template rule for all the recording-medium components:
<xsl:template match="*[type() = 'recording-medium-type']">
  ... do some processing common to all recording-medium components ...
</xsl:template>
Note: the type() function is in XSLT 2.0, i.e., it's not available yet.

The important point is that an application can process the camera by delegating to each of its subordinate elements.

Best Practice

Unquestionably, design by subclassing has an important role to play in XML Schema design. However, it is being greatly overused. Long, extended type hierarchies lead to brittle, non-modifiable designs that are virtually impossible to understand.

Design by composition is the preferred approach. It yields simpler, robust, modifiable, plug-and-play designs.

"Favoring element composition over type inheritance helps you keep each element encapsulated and focused on one task. Your type hierarchies will remain small and will be less likely to grow into unmanageable monsters." [2]

Editorial comment: it is my observation after analyzing many XML Schemas that the community is overusing design-by-subclassing approach. The community is heading down the same path that the Object-Oriented (OO) community headed down in its early phases. Just as the OO community eventually recognized that "composition is to be favored" [3], so too must XML Schema designers recognize that composing elements is to be favored.

If you do use subclassing I recommend that you follow these simple rules:

Acknowledgements

This past week I posed the question of composition vs subclassing to the XML class that I was teaching. I got many excellent insights from the students, especially, Alexis Marrero-Narvaez, Keith Alexander, Carleton Jillson, Sonny Natarajan. Thanks guys! Also, this topic was discussed on the xmlschema-dev list group, which generated excellent discussions from Jeni Tennison, Simon Cox, Paul Daisey, Mark Feblowitz, Jeff Rafter, and Curt Arnold.

[1] Design Patterns Explained by Shalloway and Trott
[2] Paraphrase from Design Patterns by Gamma et al
[3] Paraphrase from Design Patterns by Gamma et al