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.
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
endFeature redefinition is done in a child type by changing one or more properties of inherited attributes, input arguments, output arguments and events.
![]() | Note |
|---|---|
| Only properties that change have to be explicitly specified in the child type's source code. All other properties are implicitly inherited. |
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
endIf 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
endIf 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
endIn 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
endThe 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
endIf 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
endThe 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
endtype child8
inherit parent8
command foo
out result type:book end
end
end
endIf 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
endtype child9
inherit parent9
command foo
out result voidable:no end
end
end
endThe 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
endThe 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
endThe 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
endtype child11
inherit parent11
command foo
in item type:product end
end
end
endIf 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
endtype child12
inherit parent12
command foo
in item voidable:yes end
end
end
endThe 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
endThe 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
endA command input argument's default value can be redefined in the child type.
Example:
type parent14
command foo
in string type:string end
end
endtype child14
inherit parent14
command foo
in string default: "bar" end
end
end
endThe 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
endMultiple 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.
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.
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:
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 |
|---|---|
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 |
|---|---|
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
endTypes factory and service are redefined in a similar way:
type factory_
inherit RSE_
attribute id:id type: factory_id end
end
endtype service_
inherit RSE_
attribute id:id type: service_id end
end
endFeature 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
endtype factory_id_
inherit prefixed_id
attribute value and_check: i_value.co_is_start ( "fa_".a_value ) end
end
endtype service_id_
inherit prefixed_id
attribute value and_check: i_value.co_is_start ( "se_".a_value ) end
end
endFurthermore, 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.
![]() | Note |
|---|---|
| Please refer to Obix' standard library API documentation to see the actual source code of the above types. |