Chapter 12. Bidirectional Associations

Table of Contents

1. Inverse Reference Properties
2. Making an OO Class Model
2.1. The basic procedure
2.2. How to eliminate unidirectional associations
2.3. How to eliminate bidirectional associations
2.4. The resulting OO class model
3. Quiz Questions
3.1. Question 1: Table populations of bidirectional associations
3.2. Question 2: Implications of bidirectionality
3.3. Question 3: Elimination of bidirectional associations
3.4. Question 4: Meaning of bidirectional association

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.

Figure 12.1. The Publisher-Book-Author information design model with two bidirectional associations

The Publisher-Book-Author information design model with two bidirectional associations

1. Inverse Reference Properties

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:

Equation 12.1. 

c.chair.chairedCommittee = c

Or, the other way around, with m referencing a ClubMember object, we obtain the equation:

Equation 12.2. 

m.chairedCommittee.chair = m

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

  1. = a1 if both A::bbb and B::aaa are single-valued,

  2. contains a1 if A::bbb is single-valued and B::aaa is multi-valued,

  3. for any b1 from a1.bbb, = a1 if A::bbb is multi-valued and B::aaa is single-valued,

  4. for any b1 from a1.bbb, 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

  1. whenever a new committee object is created (with a mandatory chair assignment), the corresponding ClubMember::chairedCommittee property has to be assigned accordingly;

  2. 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;

  3. 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

  1. 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);

  2. 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;

  3. 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.