Chapter 18. Feature redefinition

Description

Feature redefinition is used in child types to change features (attributes, commands and events) inherited from parent types.

For example, suppose that type product has attribute identifier of type string, and that type book is a child type of product. If the specification requires that each book is identified by its ISBN number, then feature redefinition can be used to modify (i.e. to redefine) the type of attribute identifier in type book. Instead of being a simple string, attribute identifier now becomes a value of type ISBN_number, which is a string restricted to be a valid ISBN number.

Feature redefinition contributes to a better typing system, because it allows for more control and flexibility about what is allowed and banned in parent and child types, as shown by the rules explained in the following section.

Rules

  1. The general syntax to redefine inherited features in a child type is to embed feature redefinition instructions within the inherit instruction.

    Example: If type bar inherits from type foo and redefines inherited features then the syntax is as follows:

    type foo
    
       // attributes
       // commands
       // events
    
    end
    type bar
    
       inherit foo
          // feature redefinition instructions
       end
    
    end
  2. Feature redefinition is done in a child type by changing one or more properties of inherited attributes, input arguments, output arguments and events.

    [Note]Note
    Only properties that change have to be explicitly specified in the child type's source code. All other properties are implicitly inherited.

  3. Whenever feature redefinition is used, then type compatibility must be preserved.

    This rule results from the type compatibility rule which requires that a child type is always compatible to all its parent types.

    The consequence is that features in child types cannot be redefined arbitrarily, because otherwise type compatibility could be broken. The compiler takes care of that by applying the rules explained below. Some of these rules might appear a bit confusing at first, but in practice they just make sense and contribute to well designed types.

    The following rules apply to attributes:

    • In a child type, the type T of an immutable attribute can be changed to any compatible type (i.e. any child type) of type T.

      In the following example, an attribute of type product in a parent type is redefined to be of type book in a child type (we suppose that book is compatible to product):

      type parent1
      
         attribute item type:product end
      
      end
      type child1
      
         inherit parent1
            attribute item type:book end
         end
      
      end
    • If property voidable of an immutable attribute is set to yes in the parent type then it can be changed to no in a child type.

      Example:

      type parent2
      
         attribute security_level type:positive32 voidable:yes end
      
      end
      type child2
      
         inherit parent2
            attribute security_level voidable:no end
         end
      
      end
    • If property kind of an attribute is set to readonly_variable in the parent type then it can be changed to readonly_constant in a child type.

      Example:

      type parent3
      
         attribute item_count type:zero_positive32 kind:readonly_variable end
      
      end
      type child3
      
         inherit parent3
            attribute item_count kind:readonly_constant end
         end
      
      end
    • In a child type, property setable of an attribute can be set to a less restrictive value than the value defined in the parent type.

      Example:

      type parent4
      
         attribute foo type:string kind:variable setable:factory end
      
      end
      type child4
      
         inherit parent4
            attribute foo setable:all end
         end
      
      end
    • The check of an immutable attribute can be made stronger in the child type, so that the set of allowed values in the child type is a subset of those allowed in the parent type.

      Practically this is realized with a boolean and applied to the checks defined in the parent and child types.

      If a check exists in the parent type, and another one is specified in the child type, then the resulting check in the child type is the result of a boolean and of both checks. This means that an object of the parent type must only fulfill the condition specified through the check defined in the parent type, but an object of the child type must fulfill the condition specified through the parent type's check, as well as the condition specified through the child type's check.

      If no check exists in the parent type, then the check defined in the child type simply is the check applied to any object of the child type.

      In the example below, the parent type requires that an attribute is a string containing 2 to 7 characters, and the child type furthermore requires the string to start with "A":

      type parent5
      
         attribute acronym type:string check: i_acronym.item_count >= 2 and i_acronym.item_count <= 7 end
      
      end
      type child5
      
         inherit parent5
            attribute acronym and_check: i_acronym.is_start ( "A" ) end
         end
      
      end
    • If an attribute's type remains unchanged in a child type, then the attribute's default value can be redefined in the child type. However, if an attribute's type changes in a child type, then the attribute's default value must be redefined in the child type if a default value exists in the parent type, and the attribute's default value can be defined in the child type if a default value doesn't exists in the parent type.

      Example:

      type parent6
      
         attribute index type:zero_positive32 default: 0 end
         
      end
      type child6
      
         inherit parent6
            attribute index type:positive32 default: 1 end
         end
      
      end

    The following rules for command output arguments are similar to those of immutable attributes:

    • In a child type, the type T of a command output argument can be changed to any compatible type (i.e. any child type) of type T.

      In the following example we again suppose that book is compatible to product:

      type parent8
      
         command foo
            out result type:product end
         end
      
      end
      type child8
      
         inherit parent8
            command foo
               out result type:book end
            end
         end
      
      end
    • If property voidable of a command output argument is set to yes in the parent type then it can be changed to no in a child type.

      Example:

      type parent9
      
         command foo
            out result type:product voidable:yes end
         end
      
      end
      type child9
      
         inherit parent9
            command foo
               out result voidable:no end
            end
         end
      
      end
    • The check of a command output argument can be made stronger in the child type, so that the set of allowed values in the child type is a subset of those allowed in the parent type.

      As for attributes, this is realized with a boolean and applied to the checks defined in the parent and child types.

      In the following example the parent type guarantees that the result of a command is a string starting with "a" and that the first file passed as input argument will exist after execution of the command:

      type parent10
      
         command foo
            in file1 type: file_handle end
            in file2 type: file_handle end
      
            out result type:string check: i_result.is_start ( "a" ) end
            out_check check: i_file1.exists end
         end
      
      end

      The child type below goes a step further by guaranteeing that the result will start with "ab", and that both files, file1 and file2, will exists after execution:

      type child10
      
         inherit parent10
            command foo
               out result and_check: i_result.is_start ( "ab" ) end
               and_out_check check: i_file2.exists end
            end
         end
      
      end

    The following rules for command input arguments are the counterpart of those for output arguments:

    • In a child type, the inherited type T of a command input argument can be changed to any parent type of type T.

      In the following example we suppose that type book is a child of product:

      type parent11
      
         command foo
            in item type:book end
         end
      
      end
      type child11
      
         inherit parent11
            command foo
               in item type:product end
            end
         end
      
      end
    • If property voidable of a command input argument is set to no in the parent type then it can be changed to yes in a child type.

      Example:

      type parent12
      
         command foo
            in item type:product voidable:no end
         end
      
      end
      type child12
      
         inherit parent12
            command foo
               in item voidable:yes end
            end
         end
      
      end
    • The check of a command input argument can be weakened in the child type, so that the set of allowed values in the child type is a superset of those allowed in the parent type.

      Practically, this is realized with a boolean or applied to the checks defined in the parent and child types.

      In the following example the parent type requires an input string to start with "a":

      type parent13
      
         command foo
            in string type:string check: i_string.is_start ( "a" ) end
         end
      
      end

      The child type weakens the condition by specifying that the input string can start with "a" or "b":

      type child13
      
         inherit parent13
            command foo
               in string or_check: i_string.is_start ( "b" ) end
            end
         end
      
      end
    • A command input argument's default value can be redefined in the child type.

      Example:

      type parent14
      
         command foo
            in string type:string end
         end
      
      end
      type child14
      
         inherit parent14
            command foo
               in string default: "bar" end
            end
         end
      
      end

    The following rule applies to events:

    • In a child type, the inherited type T of an event can be changed to any compatible type (i.e. any child type) of type T.

      Example (we suppose that object_state_changed_event is a child type of object_event:

      type parent7
      
         event foo type:object_event end
      
      end
      type child7
      
         inherit parent7
            event foo type:object_state_changed_event end
         end
      
      end
  4. Multiple redefinitions can appear in one type, and each child type of a child type can add its own redefinitions..

    For example, a child type CT can redefine two properties of attribute A1, and one property of attribute A2. Furthermore, a child type of CT (i.e. a child of a child) can add its own redefinitions for one or both attributes, as long as type compatibility is preserved.

Rationale

For a concrete example of how feature redefinition makes software less error-prone and more maintainable, see the section called “Feature redefinition in child types” in the tutorial.

Although feature redefinition might be new and unusual for some programmers, it quickly becomes used again and again, once its possibilities and advantages are understood.

Feature redefinition is a useful extensions of type inheritance, and both are based on the well known principle [Whoever can do more can do less].

In the real world, we are all used to the idea of feature redefinition. Consider, for example, a journeyman-carpenter who is able to construct a simple cupboard (command construct_cupboard of type journeyman_carpenter returns a simple_cupboard object). A master-carpenter can obviously create anything a journeyman can create, but he can also create more sophisticated things (whoever can do more can do less). In our example, a master can construct a more sophisticated cupboard with more features. Type inheritance and feature redefinition allow us to express such relations: sophisticated_cupboard is a child type of type simple_cupboard (type inheritance), master_carpenter is a child type of type journeyman_carpenter (type inheritance again), and, while command construct_cupboard returns a simple_cupboard object in type journeyman_carpenter, it returns a sophisticated_cupboard object in type master_carpenter (feature redefinition).

Hence, feature redefinition opens the door for a more expressive typing system that results in a better design and more reliability, because more errors can be detected at compile-time, and maintenance is eased.

Examples

Feature redefinition is often used with scalar types (i.e. a type that represents a single value), because it allows to create child types that can hold only a subset of the values allowed in the parent type.

Consider, for example, 32 bits integer numbers. The parent type for these numbers in the Obix standard library is signed_integer32. This type has attribute value of type integer32_value that allows to store any signed integer value in the range -214748648 to 2147483647.

Type zero_positive32 is a child type of signed_integer32 and allows to store only positive integers in the range 0 to 2147483647. Furthermore, positive32 is a child type of zero_positive32 which doesn't allow the value zero. Similar types exist for negative numbers: zero_negative32 (-214748648 to 0) and negative32 (-214748648 to -1). The following figure shows the type inheritance tree:

Figure 18.1. Integer types

Integer types

Now suppose we need a type to store the day of a month, which is an integer ranging from 1 to 31. The idea is of course to inherit from type positive32, but restrict the set of allowed values to the range 1 to 31. Feature redefinition enables us to do this as follows:

type day_in_month

   inherit positive32
      attribute value and_check: i_value <= 31.a_value end
   end

end
[Note]Note
The above type inherits all features of type positive32, including arithmetic calculations and other features not needed in type day_in_month. To avoid this, we could inherit from type simple_positive32, which has fewer features and simplifies the implementation of day_in_month.

Another very typical example of feature redefinition can be found in types type, factory and service of Obix's standard library. The common parent for these types is type RSE (Root Software Element). Each RSE is identified by a prefixed identifier (see the section called “Prefixed identifier” for more information about prefixed identifiers). Therefore, type RSE is defined as follows:

type RSE_

   attribute id:id type: prefixed_id end
   
   // other features not shown here

end
[Note]Note
The underscore after the type's identifier is just used here to make a difference between this example code and the real code of type RSE in the Obix library.

However, as explained in previous chapters, a type is identified by a type_identifier, which is a prefixed_identifier starting with "ty_", a factory is identified by a factory_identifier that starts with "fa_", and a service is identified by a service_identifier that starts with "se_"

Therefore, feature redefinition is used to specify the more specialized identifiers. In type type, for example, attribute id is redefined to be of type type_id:

type type_

   inherit RSE_
      attribute id:id type: type_id end
   end

end

Types factory and service are redefined in a similar way:

type factory_

   inherit RSE_
      attribute id:id type: factory_id end
   end

end
type service_

   inherit RSE_
      attribute id:id type: service_id end
   end

end

Feature redefinition is also used for types type_id, factory_id and service_id. These types are all children of type prefixed_id, and their code looks like this:

type type_id_

   inherit prefixed_id
      attribute value and_check: i_value.co_is_start ( "ty_".a_value ) end
   end

end
type factory_id_

   inherit prefixed_id
      attribute value and_check: i_value.co_is_start ( "fa_".a_value ) end
   end

end
type service_id_

   inherit prefixed_id
      attribute value and_check: i_value.co_is_start ( "se_".a_value ) end
   end

end

Furthermore, type prefixed_id is a child of type id. And type id is a child of type non_empty_string, which is a child of type string. Again, all these types (except string) use feature redefinition, because each child type is a string with a more restricted set (i.e. a subset) of allowed values.

The following figure shows the type inheritances and examples of valid values.

Figure 18.2. Inheritance path of type_id

Inheritance path of type_id

[Note]Note
Please refer to Obix' standard library API documentation to see the actual source code of the above types.

See also