Excerpt |
---|
Derived features allows you to compute a feature value based on other feature values or implement virtual features that hides the fact that the value is computed, not stored. |
Introduction
It is pretty common that features (attributes and references) in a class are related in such a way that some may be computed from others. E.g. a fullName attribute may be computed from givenName and familyName attributes. This may be modelled using operations, e.g. getFullName(), but by using a derived feature (here: attribute) instead, accessing the value becomes more natural, particularly when using languages like OCL. In addition, you may more easily support setting the derived value, e.g. splitting the full name and setting both the givenName and familyName features. In such a case, you can consider it a virtual feature, in the sense that there is no way of knowing which features are stored and which are computed. This allows you to change the implementation, e.g. move to storing the fullName and deriving givenName and familyName, without affecting the user of the model (instances).
Derived features in Ecore
Ecore supports derived features , both by including snippets of interpreted code (OCL or other interpreted languages) as annotations in the model or by modifying the generated code. In both cases, the feature must be marked as derived, but setting the derived in its derived attribute of the EStructuralFeature (EAttribute or EReference). How you set it depends on the Ecore editor you're using, but typically it's done in a property sheet, either in the separate Properies view or in a dedicated pane.
In addition, you should consider the following EStructuralFeature attributes:
- changeable: can this feature be set or just read? A derived feature is typically read-only, so the changeable attribute will be typically be set to false. Note that OCLInEcore uses the readonly keyword representing the inverse value.
- volatile: should this feature's value be stored in the EObject (and a Java field be generated)? Since a derived feature is computed, its value will typically not need to be stored so the volatile attribute will be true. However, if the computation is costly, it may make sense to store the last computed value, and reuse it if it hasn't changed.
- transient: should this value be serialized? Similar to the volatile attribute, the transient attribute is typically set to true, since a computed value needn't be stored in a file.
Summarised, a derived feature is typically not changeable, it is volatile and it is transient. In OCLInEcore's syntax this is written as { readonly, volatile, transient }.
Implementing a derived feature
The actual logic for a derived feature may be included in two ways: editing the generated code or including code (OCL or other interpreted languages) in an EAnnotation on the EStructuralFeature.
When generating code using Genmodel, you will need to add code to the getter method and, if your derived feature is changeable, the setter method. Note that when returning an EList for a multi-valued feature, you must return an immutable list, consider using BasicEList.UnmodifiableEList or EcoreUtil.unmodifiableList(...).
Ecore supports delegating the handling of derived features (and constraints and operations) to a language engine. When the value of the feature is read (using the default getter) or written (using the default setter), the language engine will be invoked to perform the actual work. The mechanism relies on EAnnotations, both for indicating which features should be handled this way, and the code (in the language) to interpret/evaluate to perform the desired read or write. OCLInEcore supports this mechanism for computing derived values, and the details of configuring the necessary EAnnotations is handled by the editor. See https://wiki.eclipse.org/OCL/OCLinEcore for details. This makes using OCL for implementing derived features pretty easy.