![]() | Note |
|---|---|
| Please note that this chapter is incomplete. An update will be published in the future. |
By default all objects are immutable in Obix. This means that, once an object is created or a service is initialized, their attributes cannot be set to another value.
If needed, attributes can explicitly be made mutable.
By default, all attributes defined in an RSE (type, factory or service) are immutable.
This signifies that:
once an object is created, it is not possible to set an attribute to another value.
once a service is initialized, it is not possible to set an attribute to another value.
![]() | Note |
|---|---|
This rule is checked at compile-time and also applies to private attributes in factories and services. |
An attribute can explicitly be declared as mutable through the attribute's kind property.
Keeping all attributes immutable is sometimes inappropriate or impossible. For example, a customer's identifier is typically immutable, but its address can change, therefore it makes sense to make attribute address mutable.
Please refer to the section called “Attribute property kind” for more information about how to use property kind
Here is a reprint of the example used in that section:
type person
attribute first_name type:string kind:constant end // first name never changes
// ('kind:constant' could be omitted,
// because 'constant' is default value for 'kind')
attribute last_name type:string kind:variable end // last name can change, e.g. when woman gets maried
attribute birthdate type:date end // birthdate never changes ('kind' defaults to 'constant')
attribute age type:zero_positive32 kind:readonly_variable end // age changes each year, and depends on birthdate
end typeIf an attribute is mutable then its setable property defines which software elements are allowed to change the attribute.
Please refer to the section called “Attribute property setable” for more information about how to use property setable
Here is a reprint of the example used in that section:
type person_2 default_factory:yes
attribute_list type:string
attribute first_name end // attribute is immutable. it can only be set in the creator.
// (property 'kind' defaults to 'constant', in which case
// property 'setable' defaults to 'creator')
attribute last_name &
kind:variable setable:factory end // only scripts in the factory that creates the object can change 'last_name'
attribute city &
kind:variable setable:all end // any script can change the value
end attribute_list
end type![]() | Note |
|---|---|
It is important to be aware of the fact that an immutable object can possibly have attribute values which are themselves mutable. In such a case the object's attributes cannot be set to another value, but an attribute of an attribute (i.e. a child-attribute) can still change its value. Suppose, for example, an immutable list containing 3 customers. Suppose also that attribute It follows from the above that it can sometimes be important to consider whether all attributes of an immutable object are themselves recursively immutable. If they are, then the object is said to be deeply immutable. |
The advantages of immutable objects are very well explained in chapter Favor immutability in Joshua Bloch's excellent book Effective Java (ISBN 0-201-31005-8).
Joshua Bloch concludes (on page 70): [Classes should be immutable unless there is a very good reason to make them mutable!]
![]() | Note |
|---|---|
It is interesting to note that objects are mutable by default in most languages. Java Beans for example impose mutable objects, because the bean specification requires a default no-argument constructor, and a setter-method for each property (attribute). |
Obix favors immutability for the following reasons:
Immutable objects are simple: Once created with all attributes set to valid values, their state doesn't change anymore and remains valid all the time. There are no state transitions to manage. Therefore, immutable objects are less error-prone.
Immutable objects are thread-safe. They don't require synchronization.
Let's look at 2 examples of what can happen if mutable objects are used in a multi-threaded environment.
Consider the following naive and oversimplified type for mutable dates:
type mutable_date
attribute_list type:positive32 kind:variable setable:all
attribute day check: i_day <= 31 end
attribute month check: i_month <= 12 end
attribute year end
end
endNow suppose this type is used to hold the estimated delivery date for a customer's order, and the following happens:
the initial estimated delivery date is set to 31.1.2008 (31th January 2008).
later on the date changes to 15.2.2008 (15th February 2008).
the software does this by:
first changing the day from 31 to 15.
then changing the month from 1 to 2.
the instructions might look like this:
estimated_delivery_date.day = 15 estimated_delivery_date.month = 2
at the same time another thread reads the order's estimated delivery date in order to send an email confirmation to the customer.
the instruction might look like this:
mail_message = "estimated delivery time: " & customer_order.estimated_delivery_date.to_string
The problem with this code appears when the second thread reads the date after the day has been changed by the first thread, but before the month is changed. In that case, the date sent to the customer will be 15.1.2008, instead of 15.2.2008!
Even worse, if the same happened with an initial date of 15.2.2008, and an updated value of 31.3.2008, then the date sent to the customer would be 31.2.2008, which is not only a wrong date, but also an illegal date!
Another example: An accounting application includes a list containing the history of all accounting records. Task 1 appends a new operation by adding two records, one credit record and one debit record. Task 2 scans the list in order to print a balance sheet. Scanning is done after task 1 added the first record and before adding the second record of the new operation. The terrible consequence is that the balance will be unbalanced, because only the first record of the new operation is included in the scanning!
![]() | Note |
|---|---|
It is really important to note that problems like the above ones count among the nastiest ones that can appear in practice. The reason is that they typically appear randomly and rarely, and they frequently just produce wrong results, but no run-time errors. This makes them often extremely difficult to detect, repeat and debug. They might stay undetected during the test phase, and afterwards the most bizarre phenomenon can appear randomly once the software is in production, where more users create more data in different environments. |
Immutable objects can freely be shared. There is no need for creating copies of immutable objects, because their state can simply not change during the whole lifetime of the object. As a nice side-effect, this can sometimes considerably increase performance.
If a command's input argument is immutable, there is no risk of inadvertently (or voluntary) changing the input argument's state within the command.
Immutable objects are less vulnerable to malicious attacks, because evil code cannot change the state of immutable objects.
Despite the advantages of immutable objects, it does not always make sense to make objects immutable.
In some situations it is even impossible. For example, if two objects mutually refer to each other. A concrete example would be a tree with each node holding a reference to its parent node and its child nodes.
Another issue is that immutable types require a different object for each distinct value. Hence, each time the state changes, a new object has to be created, which can be too much time and space consuming. Consider, for example, a list containing all accounting records. Every time a new records was added, a new list would have to be created. Obviously, in a real-world application with thousands or millions of records, all memory resources would quickly be consumed and the performance would drop down to an unacceptable level.
Quite often, the best solution is to provide an immutable type together with a mutable counterpart. The mutable type is first used to build an object. Then, once the object doesn't change anymore it is converted into its immutable counterpart.
This method of first using a mutable type to build an object and then convert it into an immutable one (e.g. with command to_immutable) is largely supported in Obix's standard library.
The following code shows an example of creating an immutable list by first using a mutable list and then converting it to an immutable one:
Example 7.1. Building an immutable list
service geographical_data
attribute list_of_continents type:!indexed_list<string> // immutable list of continents
default
script
// declare mutable list
const !mutable_indexed_list<string> result = !mutable_indexed_list_factory<string>.create
// append items
result.append ( "Africa" )
result.append ( "Antartica" )
result.append ( "Asia" )
result.append ( "Australia" )
result.append ( "Europe" )
result.append ( "North America" )
result.append ( "South America" )
// convert to immutable list
o_list_of_continents = result.to_immutable
end
end
end
end