Consider the simple class hierarchy of the design model in Figure 16.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 hierarchy 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. 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, 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 attributes 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 16.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.