In OO modeling and programming, a bidirectional association is an association that is represented as a pair of mutually inverse reference properties, which allow `navigation´ (object access) in both directions.
The model shown in Figure 12.1 below (about publishers, books and their authors) serves as our running example. Notice that it contains two bidirectional associations, as indicated by the ownership dots at both association ends.
For being able to easily retrieve the committees that are chaired or
co-chaired by a club member, we add two reference properties to our
Committee-ClubMember example model: the
property of a club member to be the chair of a committee
(ClubMember
::chairedCommittee
) and the property
of a club member to be the co-chair of a committee
(ClubMember
::coChairedCommittee
). We assume that
any club member may chair or co-chair at most one committee (where the
disjunction is non-exclusive). So, we get the following model:
Notice that there is a close correspondence between the two
reference properties Committee
::chair
and
ClubMember
::chairedCommittee
. They are the
inverse of each other: when the club member Tom is the chair of the
budget committee, expressed by the tuple ("budget
committee", "Tom"), then the budget committee is the committee
chaired by the club member Tom, expressed by the inverse tuple ("Tom", "budget committee"). For expressing this
inverse correspondence in the diagram, we append an inverse property
constraint, inverse of chair
, in curly braces to the
declaration of the property
ClubMember
::chairedCommittee
, and a similar one
to the property Committee
::chair
, as shown in
the following diagram:
Using the reference path notation of OOP languages, with c referencing a Committee
object, we
obtain the equation:
Or, the other way around, with m
referencing a ClubMember
object, we obtain the
equation:
Notice that when a property p2 is the inverse of a
property p1,
this implies that, the other way around, p1 is the inverse of
p2. Therefore,
when we declare the property
ClubMember
::chairedCommittee
to be the inverse
of Committee
::chair
, then, implicitly,
Committee
::chair
is the inverse of
ClubMember
::chairedCommittee
. We therefore call
Committee
::chair
and
ClubMember
::chairedCommittee
a pair of mutually inverse reference
properties. Having such a pair in a model implies
redundancy because each of the two involved reference properties can
be derived from the other by inversion. This type of redundancy implies
data storage overhead and update overhead, which is the price to pay for
the bidirectional navigability that supports efficient object access in both
directions.
In general, a bidirectional association between the classes
A
and B
is represented by two reference
properties A
::bbb
and
B
::aaa
such that for any object a1
instantiating A
, it holds that
a1.bbb.aaa
= a1
if both
A
::bbb
and B
::aaa
are single-valued,
a1.bbb.aaa
contains a1
if
A
::bbb
is single-valued and
B
::aaa
is multi-valued,
for any b1
from a1.bbb
,
b1.aaa
= a1
if
A
::bbb
is multi-valued and
B
::aaa
is single-valued,
for any b1
from a1.bbb
,
b1.aaa
contains a1
if both
A
::bbb
and B
::aaa
are multi-valued.
For maintaining the duplicate information of a mutually inverse
reference property pair, it is common to treat one of the two involved
properties as the master, and the other
one as the slave, and take this
distinction into consideration in the code of the change methods (such as
the property setters) of the affected model classes. We indicate the slave
of an inverse reference property pair in a model diagram by declaring the
slave property to be a derived property using the UML notation of a slash (/
)
as a prefix of the property name as shown in the following diagram:
The property chairedCommittee
in
ClubMember
is now derived
(as indicated by its slash prefix). Its annotation {inverse of
chair}
defines a derivation rule
according to which it is derived by inverting the property
Committee
::chair
.
There are two ways how to realize the derivation of a property: it may be derived on read via a read-time computation of its value, or it may be derived on update via an update-time computation performed whenever one of the variables in the derivation expression (typically, another property) changes its value. The latter case corresponds to a materialized view in a database. While a reference property that is derived on read may not guarantee efficient navigation, because the on-read computation may create unacceptable latencies, a reference property that is derived on update does provide efficient navigation.
When we designate an inverse reference property as derived by
prefixing its name with a slash (/
), we indicate that it is
derived on update. For instance, the property
/chairedCommittee
in the example above is derived on update
from the property chair
.
In the case of a derived reference property, we have to deal with life-cycle dependencies between the affected model classes requiring special change management mechanisms based on the functionality type of the represented association (either one-to-one, many-to-one or many-to-many).
In our example of the derived inverse reference property
ClubMember
::chairedCommittee
, which is
single-valued and optional, this means that
whenever a new committee object is created (with a mandatory
chair
assignment), the corresponding
ClubMember
::chairedCommittee
property has to
be assigned accordingly;
whenever the chair
property is updated (that is, a
new chair is assigned to a committee), the corresponding
ClubMember
::chairedCommittee
property has to
be unset for the club member who was the previous chair and set for
the one being the new chair;
whenever a committee object is destroyed, the corresponding
ClubMember
::chairedCommittee
property has to
be unset.
In the case of a derived inverse reference property that is
multi-valued while its inverse base property is single-valued (like
Publisher
::publishedBooks
in Figure 12.2 below being
derived from Book
::publisher
), the life cycle
dependencies imply that
whenever a new 'base object' (such as a book) is created, the
corresponding inverse property has to be updated by adding a reference
to the new base object to its value set (like adding a reference to
the new book object to
Publisher
::publishedBooks
);
whenever the base property is updated (e.g., a new publisher is
assigned to a book), the corresponding inverse property (in our
example, Publisher
::publishedBooks
) has to
be updated as well by removing the old object reference from its value
set and adding the new one;
whenever a base object (such as a book) is destroyed, the
corresponding inverse property has to be updated by removing the
reference to the base object from its value set (like removing a
reference to the book object to be destroyed from
Publisher
::publishedBooks
).
Notice that from a purely computational point of view, we are free
to choose either of the two mutually inverse reference properties (like
Book
::authors
and
Author
::authoredBooks
) to be the master.
However, in many cases, associations represent asymmetrical ontological
existence dependencies that dictate which of the two mutually inverse
reference properties is the master. For instance, the authorship
association between the classes Book
and Author
represents an existential dependency of books on their authors. A book
existentially depends on its author(s), while an author does not
existentially depend on any of her books. Consequently, the corresponding
object lifecycle dependency between Book
and
Author
implies that their bidirectional association is
maintained by maintaining Author
references in
Book
::authors
as the natural choice of master
property, while Author
::authoredBooks
is the
slave property, which is derived from
Book
::authors
.