Factories implement types. They are used to create objects.
Each factory is identified by a unique factory identifier. A factory identifier is a prefixed identifier starting with fa_ as prefix. For more information about prefixed identifiers see the section called “Prefixed identifier”.
One factory can only create objects of one type, namely the type which is implemented by the factory.
In most cases there exists only one factory for a given type, and all objects of that type are created with that factory. However, there can be an unlimited number of factories implementing the same type in different ways. For example, one factory could be optimized for low memory usage, while another one could be optimized for best performance. Different factories implementing the same type must have different identifiers.
An object is created through a creator defined in the factory. A creator is similar to a command. It can have 0 or more input arguments, but it must have at least one output argument that represents the object to be created. Each factory has at least one creator. But it can have additional alternative creators, so that objects can be created in different ways. Different creators must have different identifiers.
The relations between types, factories and creators is shown in the following figure:
Each attribute and each command defined in a type must be implemented in the factory. However, if an attribute's implementation is not explicitly defined in the factory, then the compiler provides a default implementation, which consists of storing the attribute's value as an object in memory. As this is the most frequent way to implement attributes, the programmer is often released from writing code to implement attributes in factories.
Sometimes a factory needs to manage additional attributes which are not defined in the type implemented by the factory, but which are needed by the factory. Such attributes are called private attributes, and a factory can contain any number of private attributes. Private attributes cannot be accessed from outside the factory.
The same is true for commands. A factory can contain any number of private commands which cannot be accessed from outside, but which are needed for implementation.
Table 17.2. Factory
| Production | Syntax | Links |
|---|---|---|
factory |
| the section called “Factory” |
Example 17.4. Factory supplier
Suppose we want to implement the following simple type supplier with two attributes and a command that returns a XML representation of the supplier:
type supplier
attribute identifier type:positive32 end
attribute name type:string end
command convert_to_XML
out result type:string end
end command
end typeLet's look at a first approach for a factory implementing the above type.
As the Obix compiler provides a default implementation for attributes, we don't need to explicitly define their implementation.
Each factory must provide at least one creator. A creator with an input argument for each attribute defined in the type could be defined as:
creator create
in identifier type:positive32 end
in name type:string end
out result type:supplier end
script
o_result.a_identifier = i_identifier
o_result.a_name = i_name
end script
end creator
As we can see, the creator's script simply assigns the input arguments to the attributes of output argument o_result.
Many factories contain a creator like the above one, namely a creator with one input argument for each attribute defined in the type, and one output argument which is the object to be created by the factory. Therefore, the compiler can be told to automatically build such a creator with the kind:in_all clause. Hence, our above creator can simply be rewritten as:
creator create kind:in_all end
For more information about creators see the section called “Creator”
A factory must implement each command defined in the type. Command convert_to_XML can be written as:
command convert_to_XML
script
o_result = """
<supplier>
<id>{{a_identifier.co_to_string}}</id>
<name>{{a_name}}</name>
</supplier>"""
end script
end command
For explanations about the above code please refer to Chapter 3, Simple example
The complete code for the factory looks as follows:
factory supplier_1 type:supplier
command convert_to_XML
script
o_result = """
<supplier>
<id>{{a_identifier.co_to_string}}</id>
<name>{{a_name}}</name>
</supplier>"""
end script
end command
creator create kind:in_all end
end factoryA supplier can now be created with:
var ty_supplier v_supplier = fa_supplier_1.co_create ( & i_identifier = 123 & i_name = "IT FOR EVERYONE" )
And the following instruction:
se_system.console.write_line ( v_supplier.co_convert_to_XML )
will display the following on the system console:
<supplier>
<id>123</id>
<name>IT FOR EVERYONE</name>
<supplier>Suppose now we want to be able to define if the XML code produced by command convert_to_XML uses XML tags, as in the above example, or XML attributes, so that the output would be:
<supplier id="123" name="IT FOR EVERYONE" />
There are several solutions to this requirement.
The most obvious one would be to add an input argument to command convert_to_XML defined in the type:
type supplier_2
attribute identifier type:positive32 end
attribute name type:string end
command convert_to_XML
in use_XML_attributes type:yes_no end
out result type:string end
end command
end typeThe factory now has to consider the value of input argument use_XML_attributes and could be written as:
factory supplier_2 type:supplier_2
command convert_to_XML
script
if i_use_XML_attributes then
o_result = """<supplier id="{{a_identifier.co_to_string}}" name="{{a_name}}" />"""
else
o_result = """
<supplier>
<id>{{a_identifier.co_to_string}}</id>
<name>{{a_name}}</name>
</supplier>"""
end if
end script
end command
creator create kind:in_all end
end factoryAlthough the above solution would be the one to choose in many cases, we have to consider that modifying a type is not always possible or desirable. For example we might not have access to the type's source code, because it is defined in a public third-party library. Another problem could be that all other factories implementing the type (or child types) would have to be adapted to take care of the new input argument.
Therefore, let's have a look at different solutions, so that we can explore several ways for defining factories.
In case type supplier cannot be modified, one solution would be to simply define a second factory implementing the same type, but in a different way (i.e. using XML attributes instead of XML tags):
Example 17.5. Another factory implementing the same type
factory supplier_factory_using_XML_attributes type:supplier
command convert_to_XML
script
o_result = """<supplier id="{{a_identifier.co_to_string}}" name="{{a_name}}" />"""
end script
end command
creator create kind:in_all end
end factoryNow the programmer can write
var ty_supplier v_supplier = fa_supplier_1.co_create ( & i_identifier = 123 & i_name = "IT FOR EVERYONE" )
if tags should be used for XML
or he/she can write
var ty_supplier v_supplier = fa_supplier_factory_using_XML_attributes.co_create ( & i_identifier = 123 & i_name = "IT FOR EVERYONE" )
to use XML attributes instead of XML tags
Another solution would be to stay with one factory but to add a second creator that is used whenever we want to use XML attributes. In that case we also need a private attribute that remembers how to create the XML.
Example 17.6. Two creators and a private attribute in a factory
factory supplier_3 type:supplier
// private attribute that remembers how to create XML
// default value is no, which means: use XML tags
attribute use_XML_attributes type:yes_no default: no private:yes end
command convert_to_XML
script
// use XML tags or XML attributes, depending on the value stored in private attribute use_XML_attributes
if a_use_XML_attributes then
o_result = """<supplier id="{{a_identifier.co_to_string}}" name="{{a_name}}" />"""
else
result = """
<supplier>
<id>{{a_identifier.co_to_string}}</id>
<name>{{a_name}}</name>
</supplier>"""
end if
end script
end command
// standard creator to use XML tags
creator create kind:in_all end
// alternative creator to use XML attributes
creator create_using_XML_attributes kind:in_all
script // this time we have to explicitly define the script, because the default implementation isn't adequate
o_result.a_identifier = i_identifier
o_result.a_name = i_name
o_result.a_use_XML_attributes = yes // overwrite default value of attribute use_XML_attributes
end
end
end factoryThis time we write
var supplier supplier = supplier_3.create ( & identifier = 123 & name = "IT FOR EVERYONE" )
to use XML tags, or
var supplier supplier = supplier_3.create_using_XML_attributes ( & identifier = 123 & name = "IT FOR EVERYONE" )
to use XML attributes.
Another alternative would be to use only one creator, but to add an input argument that enables us to specify how the XML should be created.
Example 17.7. Creator with additional input argument
factory supplier_4 type:supplier
// private attribute that remembers how to create XML
attribute use_XML_attributes type:yes_no private:yes end
command convert_to_XML
script
// use XML tags or XML attributes, depending on the value stored in private attribute use_XML_attributes
if a_use_XML_attributes then
o_result = """<supplier id="{{a_identifier.co_to_string}}" name="{{a_name}}" />"""
else
o_result = """
<supplier>
<id>{{a_identifier.co_to_string}}</id>
<name>{{a_name}}</name>
</supplier>"""
end if
end script
end command
creator create
in identifier type:positive32 end
in name type:string end
in use_XML_attributes type:yes_no default:no end
out result type:supplier end
script
o_result.a_identifier = i_identifier
o_result.a_name = i_name
o_result.a_use_XML_attributes = i_use_XML_attributes
end
end
end factoryTo use XML tags, we now write:
var supplier supplier = supplier_4.create ( & identifier = 123 & name = "IT FOR EVERYONE" & use_XML_attributes = no )
or, more simply (because the default value for i_use_XML_attributes is no):
var supplier supplier = supplier_4.create ( & identifier = 123 & name = "IT FOR EVERYONE" )
And to use XML attributes we code:
var supplier supplier = supplier_4.create ( & identifier = 123 & name = "IT FOR EVERYONE" & use_XML_attributes = yes )
As we can see, there are several ways for factories to create objects. It is up to the programmer to decide which is the best solution for a given type.
But let us come back to our initial factory that uses XML tags:
factory supplier_1 type:supplier
command convert_to_XML
script
o_result = """
<supplier>
<id>{{a_identifier.co_to_string}}</id>
<name>{{a_name}}</name>
</supplier>"""
end script
end command
creator create kind:in_all end
end factoryLooking at the above script (where the XML tags are created), we can easily see code duplication, one of the biggest enemies in software development. To avoid this, we can add a private command as follows:
Example 17.8. Factory with private command
factory supplier_5 type:supplier
// a private command used to create XML tags
// this command can only be used within this factory
command create_XML_tag private:yes
in tag type:string end
in data type:string end
out result type:string end
script
o_result = """<{{tag}}>{{data}}</{{tag}}>"""
end
end
command convert_to_XML
script
// use private command create_XML_tag in order to avoid code duplication
o_result = """
<supplier>
{{ create_XML_tag ( tag = "id"; data = a_identifier.co_to_string ) }}
{{ create_XML_tag ( tag = "name"; data = a_name ) }}
</supplier>"""
end script
end command
creator create kind:in_all end
end factory
Creating XML tags is certainly something we will use in other factories too. Therefore, instead of defining a private command in the above factory, it would be much better to transfer that utility to a service, so that the functionality can easily be used by other software elements. Please refer to Example 17.10, “XML utilities service” to see how such a service could be defined.
Using that service, our factory now finally becomes:
Example 17.9. Factory supplier using a service
factory supplier type:supplier
command convert_to_XML
script
var string id_tag = se_XML_utility.create_XML_tag ( &
tag = "id" &
data = a_identifier.co_to_string )
var string name_tag = se_XML_utility.create_XML_tag ( &
tag = "name" &
data = a_name )
o_result = se_XML_utility.create_XML_tag ( &
tag = "supplier" &
data = v_id_tag & v_name_tag )
end script
end command
creator create kind:in_all end
end factory