-
Notifications
You must be signed in to change notification settings - Fork 0
Traits
Model entities have common traits, both in state and behaviour.
Traits project similarities on public and private APIs, as well as on bean SPIs. So, we factor them out in dedicated components.
Traits are complementary to containers as mechanisms for code reuse in the model.
Traits include:
-
Identified
,Identified.Private
, andIdentified.Bean
define and implement APIs for identification, bean delegation, and reified updates. -
Named
,Named.Private
, andNamed.Bean
define and implement the API for accessing and updating names. -
Described
,Described.Private
, andDescribed.Bean
define and implement the API for accessing and updating attributes. -
Definition
,Definition.Private
, andDefinition.Bean
define and implement the API for accessing and updating occurrence ranges. We reuse it in entities that have instances, such as attribute definitions and link definitions. -
Defined
andDefined.Bean
define the API for accessing and updating the entity definitions. We reuse it in attributes and links.
Note that the API is not implemented as a trait, i.e. there is no Defined.Private
available for reuse. This is because links are described while attributes are not, we cannot arrange them in a single hierarchy. Here we hit the limit of single class inheritance and our ability to emulate real implementation traits (see below).
-
BeanOf
is an interface for beans that act as entity factories. It is a technical trait used solely in containers and justified in detail there.
The public APIs of traits are pairwise independent. We combine them in the public APIs of concrete entities, e.g.:
interface Codelist extends Identified, Named, Attributed {
...
}
We cannot do the same for private APIs. Since they are classes, we can only reuse them if we arrange them in a hierarchy:
abstract class Named.Private ... extends Named.Private implements Named {...}
...
abstract class Described.Private ... extends Named.Private implements Described {...}
...
abstract class Codelist.Private ... extends Described.Private ... implements Codelist {...}
note: the private API of traits are all abstract.
We also deliberately arrange bean SPIs in an hierarchy, even through they are interfaces. This is for convenience, as it simplifies the parametrisation of private APIs as well as SPI implementations.
interface Named.Bean ... extends Identified.Bean ... {...}
...
interface Described.Bean ... extends Named.Bean ... {...}
...
interface Codelist.Bean ... extends Described.Bean ... {...}
The private APIs of traits make heavy use of generics, which deserves some clarification.
Identified.Private
introduces two type parameters:
interface Identified {
...
interface Bean {...}
...
abstract class Private<SELF extends Private<SELF,B>, B extends Bean> ... {...}
}
The second parameter B
is fairly straightforward, so we begin from there. B
stands for the state bean of the entity, which Identified.Private
holds and exposes on behalf of subclasses. We use B
to avoid type loss when we return the bean to clients, particularly to the subclasses themselves:
private final B bean;
protected Private(B bean) {
this.bean=bean;
}
public B bean() {
return bean;
}
We also specify Identified.Bean
as the upper bound of B
because we need to access its identifier within Identified.Private
itself.
The first parameter SELF
is less obvious, it stands for the generic subclass that will inherit from Identified.Private
. We use it here to avoid type loss in the base implementation of the API for reified updates:
public update(SELF changeset) {...}
This is a covariant binary method, i.e. it's meant to take another instance of the class that declares it. We want it inherited in any subclass so that it takes an instance of that subclass. In Codelist.Private
, for example, we want to override it as follow:
public update(Codelist.Private changeset) {
super.update(changeset);
...
}
Up here in Identified.Private
we can only use some placeholder for Codelist.Private
or whatever the subclass will be. Since Java
has no special support for this, we emulate it with a type parameter that subclasses will need to instantiate to themselves. For example, if Codelist.Private
derived directly Identified.Private
(it doesn't), it would instantiate SELF
as follows:
class Codelist.Private extends Identified.Private<Codelist.Private,...> {...}
The tong-twisting upper bund on SELF
is our best attempt to say "it must extend Identified.Private
and have itself as a parameter". This it's not as tight as we like, but it's our best approximation of a true
self type as found in other (non-mainstream) languages.
Of course, the traits below Identified.Private
carry over the pattern:
interface Named {
...
interface Bean ... {...}
abstract class Private<SELF extends Private<SELF,B>, B extends Bean> ... {...}
}
interface Described {
...
interface Bean ... {...}
abstract class Private<SELF extends Private<SELF,B>, B extends Bean> ... {...}
}
until we hit the private API of an entity, where we finally instantiate both parameters. For Codelist.Private
:
interface Codelist ... {
...
interface Bean ... {...}
class Private extends Described.Private<Private,Bean> ... {...}
}