This interface defines behaviour common to all interfaces used within the meta data package.
It is often much better for an API to accept and return immutable objects, i.e. those whose externally visible state can never change, rather than mutable objects, i.e. those whose externally visible state can be changed. The advantages are:
It is much safer for both the API and the user of the API that object state cannot be changed. They can store and share the objects at will without having to remember to protect themselves by making defensive copies. Immutable objects are also by definition thread safe so there will be none of those hard to find problems where one thread modifies an object that another thread relies upon.
The fact that code does not have to make defensive copies of the objects when they store and share them reduces the amount of garbage generated.
The disadvantages are:
As objects are immutable it is harder to initialise them. Simple immutable objects, e.g. {@link Integer} are easy to initialiseusing a constructor. However, more complex objects, cannot be initialised through the constructor and need some intermediate builder object that can be populated with the state from which the immutable object can be constructed.
As immutable objects are impossible to modify if a referencing object needs to change an immutable object it needs to do it indirectly. It must first copy its state into a mutable builder object, modify that state, create a new immutable object and finally change its reference to point to that.
Modifying and initialising immutable objects creates garbage that is not created when modifiying generally mutable objects. This needs to be balanced against the amount of defensive copying that is needed when passing around mutable objects to see whether the benefits outweight the costs.
Unfortunately, Java does not have any direct support for this sort of pattern and there are a number of different patterns in common use, all with advantages and disadvantages.
The pattern that is used in the meta data API is to have three interfaces for each basic object type that needs to be immutable. There is one 'abstract' interface that is the base for the other two and represents all objects of that type and may be either mutable, or immutable. It provides read only accessors to the object state as that is common across both mutable and immutable objects. It also provides methods to create both mutable and immutable instances of the object, which are represented by the two other interfaces.
The base interface is abstract in the sense that there are no implementations of it that are not also either implementations of the mutable or immutable interfaces.
The behaviour of the method to create an immutable instance is dependent on whether the object it is called on is immutable or mutable. If it is immutable then it will simply return a reference to itself. If it is mutable then it will either construct a new immutable instance, or if it had previously constructed (or been initialised by) an immutable instance and its state had not changed then it may return a previously constructed immutable instance.
The method to create a mutable instance will always create a new mutable instance even if it is called on a mutable instance.
As the read only accessors are defined on the base interface the immutable interface is a marker interface. It defines an implicit contract that instances that implement that interface cannot be modified. An additional aspect of this contract is that immutable implementations must be thread safe. That means that if they lazily create representations of their external state they must ensure that the creation code is suitably synchronized.
The mutable interface defines any mutating accessors that are needed to update the state of the object. It can be used as the builder for immutable objects. Mutable implementations are not thread safe and are not recommended for use in situations where they may be accessed by multiple threads, that is part of the reason for supporting immutable objects in the first place. If they have to be accessed by multiple threads then it is the responsibility of the accessing code to synchronize access to it.
The following diagram shows an interface hierarchy for two classes, Foo
and Bar
, showing the base interfaces and the immutable and mutable interfaces.
+-----+ +-> | Foo | <-+ +--------------+ / +-----+ \ +------------+ | ImmutableFoo | -+ ^ +- | MutableFoo | +--------------+ | +------------+ ^ +-----+ ^ | +-> | Bar | <-+ | +--------------+ / +-----+ \ +------------+ | ImmutableBar | -+ +- | MutableBar | +--------------+ +------------+
The convertion is for the base interface to have an appropriate name for what it is representing and for the mutable and immutable interfaces to be prefixed by Mutable
and Immutable
and also to be within the mutable
and immutable
sub packages respectively.
Container objects (i.e. any object that references another object that can be immutable) typically hold references to immutable objects. Adding a mutable object to the container will result in an immutable object being retrieved and stored instead. This ensures that the container object is always in a consistent state and simplifies the API as there is no need to provide separate methods for retrieving mutable and immutable instances of objects stored within the container.
This has a number of consequences on the design and usage of the API:
Object hierarchies have to be constructed from the bottom up. i.e. contained objects need to be initialised before they are added to their container as it is not possible to modify them once they are attached.
Mutable instances of immutable objects are typically constructed as shallow copies, i.e. objects referenced from a mutable instance are not copied (as they have to be immutable).
The only exception to this are those objects that form part of a reference cycle as they need a different approach as they cannot be constructed from the bottom up as required by this approach. Fortunately this structure is not required for the meta data API so will not be discussed further.
Not all objects referenced from within an immutable object are going to be of this same pattern, some are going to be completely immutable, e.g. {@link String} and some are going to normally be mutable, e.g. {@link Set}. The completely immutable objects can be referenced without any problems but care needs to be taken with the mutable objects as exposing them from within an immutable object would break its immutability.
Ideally an immutable version of the mutable object should be created using the pattern described here. However, if that is not possible then there are a number of approaches that can be taken. The correct one to use depends on the situation.
In all cases it is the responsibility of the containing class to manage the state of any mutable objects that it references. Either by ensuring it is suitably wrapped, or performing the defensive copying.
Java collections and maps are very common examples of mutable objects that may be referenced and exposed from within an immutable object. Immutable objects should use the umodifiable...
methods within {@link Collections} class to create an umodifiable wrapper that can bereturned from immutable objects without violating their immutability.
The three interfaces have very specific contracts and the appropriate one should be used wherever there is a reference to the object.
Warning: This is a facade provided for use by user code, not for implementation by user code. User implementations of this interface are highly likely to be incompatible with future releases of the product at both binary and source levels.
@volantis-api-include-in PublicAPI @volantis-api-include-in ProfessionalServicesAPI @volantis-api-include-in InternalAPI
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|