How to enable polymorphism for generalization?
On polymorphism
In some cases, there might be a need to define a general concept with various specific interpretations. Some examples:
- In Domain Driven Design, three
DomainObjectscan be defined (Value Object, Entity, Service). - A task is characterized by its processing, which can be asynchronous or synchronous.
- A microserver can contain communication nodes to external parties, with a certain protocol (MQTT, REST)
Here, it might be interesting to define general ElementClasses to capture the common properties, while creating instantiable ElementClasses for the specific entities.
This concept of providing a general interface for different entities is called polymorphism.
Modelling superclasses
In our metamodel tooling, ElementClasses can define a superclass. Instances of one of these ElementClasses can then also be used as an instance of the SuperClass. This way, we support polymorphism in the models.
Modelling steps:
- Create the superclass (the general concept). Mark it
isAbstract, so it cannot be instantiated directly, but only through its subclasses. SeeisAbstractfor the exact contract, andisSealedif you want to close off the set of subclasses to your own ontology. - Define the subclasses (the specific entities). Set
superClasson each. The subclass inherits all Attributes and References of the superclass, seesuperClassfor details.
This setup looks like standard object-oriented inheritance, but the intent is different: it exists strictly to support polymorphism, not deep hierarchical implementation reuse. Class inheritance introduces tight coupling and cascading changes down the tree. Keep generalization hierarchies flat, ideally one level deep (Abstract Base → Concrete Implementation).
- SuperClass
- Subclass
- Targeting the superclass
<elementClass name="ValueType">
<isAbstract>true</isAbstract>
<isSealed>false</isSealed>
<group name="valueTypes" ontology="elements"/>
<role name="data"/>
<attributes>
<attribute name="name">
<isIdentifier>true</isIdentifier>
<dataType name="Name" ontology="elements"/>
</attribute>
<attribute name="description">
<isIdentifier>false</isIdentifier>
<dataType name="Description" ontology="elements"/>
</attribute>
</attributes>
</elementClass>
<elementClass name="EnumType">
<group name="root" ontology="enumTypeElements"/>
<role name="element"/>
<superClass name="ValueType" ontology="elements"/>
<attributes>
<attribute name="packageName">
<isIdentifier>false</isIdentifier>
<dataType name="PackageName" ontology="enumTypeElements"/>
</attribute>
</attributes>
</elementClass>
A reference can target the abstract superclass directly. Any subclass instance then satisfies it:
<reference name="type">
<associationType name="association"/>
<owner name="Field" ontology="demo"/>
<target name="ValueType" ontology="elements"/>
<cardinality>
<minimum>1</minimum>
<maximum>1</maximum>
</cardinality>
</reference>
An expanderExpanderA unit of generation that combines a data tree instance (functional M1 model) with a template to generate an artifact. can do the same, targeting the superclass to generate an artifactArtifactA build or deployment result, such as a Docker container image or Java archive, produced by a software project. shared by every subclass:
<expander name="ValueTypeExpander">
<packageName>org.normalizedsystem.custom.expander</packageName>
<layerType name="SHARED_LAYER"/>
<technology name="COMMON"/>
<sourceType name="SRC"/>
<elementType component="elements" name="ValueType"/>
<artifactName>$valueType.name$.java</artifactName>
<artifactPath>$componentRoot.directory$/$artifactSubFolders$/valueTypes</artifactPath>
<isApplicable>true</isApplicable>
</expander>
This one expanderExpanderA unit of generation that combines a data tree instance (functional M1 model) with a template to generate an artifact. now runs for EnumType and every other subclass of ValueType.