Consider the simple class hierarchy of the design model in Figure 17.1 above, showing a disjoint
segmentation of the class Book
. In such a case, whenever there is only one level (or
there are only a few levels) of subtyping and each subtype has only one (or a few) additional
properties, it's an option to re-factor the class model by merging all the additional properties
of all subclasses into an expanded version of the root class such that these subclasses can be
dropped from the model, leading to a simplified model.
This Class Hierarchy Merge design pattern comes in two
forms. In its simplest form, the segmentations of the original class hierarchy are disjoint, which allows to use a
single-valued category
attribute for representing the specific category of each
instance of the root class corresponding to the unique subclass instantiated by it. When the
segmentations of the original class hierarchy are not disjoint, that is, when at least one of
them is overlapping, we
need to use a multi-valued category
attribute for representing the set of types
instantiated by an object. In this tutorial, we only discuss the simpler case of Class Hierarchy Merge re-factoring for disjoint segmentations, where
we take the following re-factoring steps:
Add an enumeration
datatype that contains a corresponding enumeration literal for each
segment subclass. In our example, we add the enumeration datatype
BookCategoryEL
.
Add a category
attribute to the root class with this enumeration as its
range. The category
attribute is mandatory [1], if the segmentation is
complete, and optional [0..1], otherwise. In our example, we add a category
attribute with range BookCategoryEL
to the class Book
. The
category
attribute is optional because the segmentation of
Book
into TextBook
and Biography
is
incomplete.
Whenever the segmentation is rigid (does not allow dynamic
classification), we designate the category
attribute as
frozen, which
means that it can only be assigned once by setting its value when creating a new object,
but it cannot be changed later.
Move the properties of the segment subclasses to the root class, and make them
optional. We call
these properties, which are typically listed below the category
attribute,
segment
properties. In our example, we move the attribute
subjectArea
from TextBook
and about
from
Biography
to Book
, making them optional, that is [0..1].
Add a constraint (in an invariant box attached to the expanded root class rectangle)
enforcing that the optional subclass properties have a value if and only if the instance
of the root class instantiates the corresponding category. In our example, this means
that an instance of Book
is of category "TextBook" if and only if its
attribute subjectArea
has a value, and it is of category "Biography" if and
only if its attribute about
has a value.
Drop the segment subclasses from the model.
In the case of our example, the result of this design re-factoring is shown in Figure 17.7 below. Notice
that the constraint (or "invariant") represents a logical sentence where the logical operator
keyword "IFF" stands for the logical equivalence operator "if and only if" and the property
condition prop=undefined
tests if the property prop
does not have
a value.