devxlogo

Wed Yourself to UML with the Power of Associations

Wed Yourself to UML with the Power of Associations

he UML association is a concept that is often misunderstood. The most common misperception is that an association is the same as a pointer, or maybe two pointers, in a program. UML associations are much more powerful than that.

This article will explain exactly what UML associations are and the ways in which you can implement them. The implementation examples are written in Java, but you can perform a translation to another programming language easily. The code examples in this article were all completely generated by the UML/OCL tool called Octopus, which can be downloaded from http://www.klasse.nl/english/research/octopus-intro.html. The code examples themselves can be downloaded here or from the link in the left-hand column.

The Difference Between Associations and Pointers
In a programming language a pointer can be used to reference another element. That other element may be a single object or a collection of objects (for instance, a list or set). The referenced element is completely unaware of the fact that some object holds a reference to it. Furthermore, the object holding the reference may freely distribute the reference amongst other objects.

In short, a pointer is simply information on how to find an element, comparable to a Web site address. If I were to have the address of a Web site that holds, for instance, CIA secrets, the CIA need not be aware that I have that address. And I can share that address with anyone and the CIA need never know.

A UML association is, like a pointer, a reference to another element or a collection of elements, but in this case the other element is aware of the reference. Using a UML association, the CIA would know who I am and that I have its Web site address. Most associations are two-way navigable, that is, if object A has a reference to object B, then by definition object B has a reference to object A. Later in this article, we will discuss one-way navigable associations.

Author’s Note: Besides generating all the code that implements associations, Octopus is capable of generating a user interface prototype and an XML storage facility from your UML model. This allows you to run the application and create instances of the classes in the model.

Associations are “Marriages”
UML associations represent relationships. Therefore, we have found that the best way to explain what an association is, is by comparison with a marriage. Figure 1 shows a class diagram with the classes Man and Woman, and an association between these classes. Also shown are two example instances of this configuration: a marriage between John and Mary, and one between Bob and Olivia.

Figure 1. Marriages: This class diagram shows two examples of a simple association between a Man and Woman class.

Any two objects that are linked through an association are “married,” just like John and Mary. Such a marriage has a number of characteristics, which we call the ABACUS rules. The characteristics that make up ABACUS (and compose the acronym) are:

  • Awareness: Both objects are aware of the fact that the relationship exists.
  • Boolean existence: If the partners agree to a divorce, the relationship (in UML called the link) is dissolved completely.
  • Agreement: Both objects have to agree with the relationship; they need to say “I do.”
  • Cascaded deletion: If one of the partners dies, the link is dissolved as well.
  • USe of rolenames: An object may refer to its partner using the role name provided with the association: “my husband” or “my wife.”

Multiplicities
So far so good, but associations may have different multiplicities than the one-to-one version in a monogamous marriage. What’s happens when one of the association ends has a multiplicity that is higher than one? In that case, clearly, you have some kind of polygamous marriage (see Figure 2).

Figure 2. Multiplicity: The concept of a polygamous marriage serves as an easy-to-grasp example of multiplicity in UML associations.

Still, all of the ABACUS rules for associations apply. As in the monogamous marriage, the objects at both sides of the relationship are aware of the fact that the relationship exists. However, an object at the plural side need not be aware of all the other objects that are in this relationship. In this example, John knows that he is married to Mary. He knows that it is a polygamous marriage, but he need not know Mary’s other husbands. The same holds for Bob. On the other hand, Mary knows all of her husbands, both Bob and John.

Likewise, both Bob and John may still refer to Mary as “my wife,” and Mary may refer to the group (collection) consisting of John and Bob as “my husbands.” But referring to a single element in this group, for instance to Bob, becomes harder. Mary needs to use some selection mechanism on her collection of husbands, for instance based on the name of the husband. Written in OCL, Mary would need to use an expression like self.myHusbands->select(name = ‘Bob’). If Mary and Bob decide to divorce, only the link between them is dissolved; the link between Mary and John remains intact.

The case where both association ends have a multiplicity greater than one, is could be modeled as a kind of group marriage, where John might be married to three women: Linda, Olivia, and Mary, and Olivia might be married to two men, as shown in Figure 3. All the characteristics of associations hold in this case as well.

Figure 3. Complex Multiplicity: Five polygamous persons demonstrate the ability of associations to be multiple on both ends.

Sets, Ordered Sets, Bags, and Sequences
UML associations are much more fun than our example has demonstrated thus far. The UML offers a choice in the type of collection used for an association end with a multiplicity greater than one. You may choose one of the following four types:

  1. Set: Every element may be present in the collection only once. This is the default collection type for association ends with a multiplicity larger than one.
  2. Ordered Set: A set in which the elements are ordered. There is an index number for each element. Note that the elements are not sorted, that is, an element with a lower index number is not in any way larger or smaller than one with a higher index. An association end with this type is indicated in the diagram by adding the marking .
  3. Bag: An element may be in the collection more than once. In the marriage example this means that, for instance, Mary may be married to John twice. Note that if one end of an association has this type, than the other end must be either a bag or a sequence. The type is indicated in the diagram by .
  4. Sequence: A bag in which the elements are ordered. It is indicated in the diagram by or . If one end of an association has this type, than the other end must be either a bag or a sequence.

What are the effects of choosing a different collection type for an association end? Let us first stress that the ABACUS rules must hold, whatever type you choose. The type of the association end is relevant only when an element is added to the relationship, and this is very important for the way associations are implemented.

In the diagram for the group marriage (Figure 3) the end is marked “my wives” as an ordered set. This means that the implementer of the addWife operation in the class Man that adds a woman to the ordered set must decide how to order the set of wives. The simplest option is to add the “new wife” as the last in the ordered set. Also, the implementer of addWife must make sure that the new wife is not already present in the list of wives. The OrderedSet type does not allow this.

Implementing Associations
The most important thing about implementing associations is to make sure that the two-way connection is always correct. It should always abide to the ABACUS rules. This means that whenever there is a change in the links, the implementation needs to take care of readjusting the references at both sides of the association. Let’s start with the simplest case: the one-to-one association.

One-to-one Association
One-to-one associations can be implemented using two fields (pointers) in the two associated classes. In class Man the type of the field should be Woman, and vice versa. Because the association is symmetrical, the same implementation can be used in both classes. Next to the field we need get and set operations: getMyWife and setMyWife. The body of the getMyWife operation is simple; it just returns the value of the field, as shown below. (We use the Java syntax here, but the examples can be easily translated into another programming language.

     public Woman getMyWife() {          return f_myWife;     }

The body of the setMyWife operation is more complex; it is responsible for making the new wife object aware of the fact that it/she is about to get married. It would be very convenient if this could be done by simply calling the setMyHusband operation, which must be part of the Woman class. However, this would cause an infinite loop, because setMyHusband would in its turn call setMyWife again. Therefore, you need to check whether the field is already set to the correct value. This is done in the first line of the body:

     public void setMyWife(Woman element) {          if ( this.f_myWife != element ) {    // prevent infinite loop               if ( this.f_myWife != null ) {    // there is a previous wife!                    // remove the link with the previous wife                    this.f_myWife.z_internalRemoveFromMyHusband( (Man)this );               }               this.f_myWife = element;       // set the field to the new value               if ( element != null ) {                      // make the new wife aware of the link                    element.setMyHusband( (Man)this );               }          }     }

Furthermore, you have to take into account the possibility that the man is already married. There are two ways to implement this. Either check whether this is the case and, if so, refuse to do anything, or make sure that the link with the previous wife is dissolved. In the above code we have chosen the latter option. We use a special operation called z_internalRemoveFromMyHusband for this purpose. This operation should only be called from setMyWife, which explains its obscure name. The only thing the operation does is set the f_myHusband field in the previous wife object to null.

     public void z_internalRemoveFromMyHusband(Man element) {          this.f_myHusband = null;     }

With this, we have made sure that all elements involved are aware of the links that exists between them. If you need to implement certain demands on the new wife or new husband (there should be agreement between the partners), it is easy to add to the set operations. First, check whatever needs to be checked on the candidate partner (for example whether the person is the right age and has good health), and only if the result is OK, perform the rest of the operation.

Most of the other characteristics of the association are ensured as well by the above code. First, the Use of Rolenames principle for the partner element is implemented in the get operation. Second, the link only exists if the fields of both partners actually refer to each other. The only thing that is not handled is the case where one of the partners is deleted (deceased). In that case the implementer of the delete operation should make sure that the setMyWife or setMyHusband operation of the partner object is called with null as parameter. Our implementation completely relies on garbage collection to handle deleted objects, therefore there is no delete or destroy operation that handles this case.

The given implementation can also be used when one or both of the association ends is optional, that is, its multiplicity is zero or one (0..1).

One-to-many Association
Next we are going to examine how to implement a one-to-many association. For this we will extend the polygamy example, where one woman can marry several men. The implementation of the Man class is not much different from the one-to-one implementation, which makes sense since from the point of view of the Man class nothing much has changed. He is still married to just one woman. The Man class has a field called f_myWife of type Woman, and a get and set operation for the field. The body of the get operation is completely equal to the one-to-one case, and even the body of the set operation is similar.

     public void setMyWife(Woman element) {          if ( this.f_myWife != element ) {               if ( this.f_myWife != null ) {                    this.f_myWife.z_internalRemoveFromMyHusbands( (Man)this );               }               this.f_myWife = element;               if ( element != null ) {                    element.z_internalAddToMyHusbands( (Man)this );               }          }     }

The only difference is that the new wife has to add the husband object to a collection instead of just setting the value of the field that represents the husband. The field that represents the husbands in the class Woman is called f_myHusbands and has the type java.util.Set. It is initialized to an empty set. This is the implementation of the special add operation in the class Woman:

     public void z_internalAddToMyHusbands(Man element) {          this.f_myHusbands.add(element);     }

We use a special operation here that must be distinguished from the addToMyHusbands operation that is also available in the class Woman. Like the z_internalRemoveFromMyHusband operation this special operation should only be called from the setMyWife operation, as the latter takes care of all the rest. In contrast, the addToMyHusbands operation may be used from any place in the system. In that case it is responsible for setting up the links correctly. This is the implementation of the addToMyHusbands operation:

     public void addToMyHusbands(Man element) {          if ( element == null ) {               return;  // never add null to a collection          }          if ( this.f_myHusbands.contains(element) ) {               return; // f_myHusbands is a set. If the association end was a                       // bag or a sequence these lines would not be present.          }          this.f_myHusbands.add(element); // the actual adding of the husband          if ( element.getMyWife() != null ) {               // a husband may have only one wife, make sure that the link               // of the old wife to this man is removed               element.getMyWife().z_internalRemoveFromMyHusbands(element);          }          // let the new husband know this object is its wife          element.z_internalAddToMyWife( (Woman)this );     }

Let’s go through the code statement by statement. First, when the operation is called with a null value as parameter, you should do nothing. Second, when the operation is called with an object that is already in the set of husbands, again, nothing is the appropriate action. In very strict coding, we probably should have issued a warning: “this object is already present in the set of husbands,” for instance by throwing an exception. As with parenting, we believe the proper approach is to be firm but not too strict.

The third statement is the actual addition of the parameter to the set of husbands. Next, you have to take care of the other references. If the new husband had a previous wife, this link should be removed. Again ,use a special operation called z_internalRemoveFromMyHusbands to implement this. The last statement sets up the link from the side of the new husband, using the special operation z_internalAddToMyWife.

Author’s Note: The z_internalAddToMyWife operation should ideally be called z_internalSetMyWife. When we wrote this part of the Octopus code generator we were a bit lazy and we reused the name of the operation in the many-to-many case.

In the implementation of the Woman class a single set operation for the field f_myHusbands will not be sufficient. You need to implement the following cases:

  • a set operation with a collection as parameter that sets the collection of husbands to be the given collection.
  • an add operation with a single object as parameter, as explained above, that adds a single husband.
  • an add operation with a collection as parameter that adds all elements in the parameter collection to the set of husbands.
  • a remove operation with a single object as parameter that removes a single husband.
  • a remove operation with a collection as parameter that removes all elements in the parameter collection from the set of husbands.
  • a clear operation that removes all associated objects that leaves the set of husbands empty.

You can find the implementation of all of these operations in the source code download.

Many-to-many Association
The implementation of the many-to-many case is of course very similar to the implementation of the many side in the one-to-many situation. Because of the symmetry, the classes at both ends can be implemented in the same manner as in the one-to-one situation. In both classes we need the field, the get operation, and the list of operations that implement the setting, as well as add and remove elements from the collection.

Now take a look at the implementation of the addToMyHusbands operation in the many-to-many case:

     public void addToMyHusbands(Man element) {          if ( element == null ) {               return;          }          if ( this.f_myHusbands.contains(element) ) {               return;          }          this.f_myHusbands.add(element);          element.z_internalAddToMyWifes( (Woman)this );     }

You can see that it is almost identical to the addToMyHusbands operation in the one-to-many situation. What is missing is the part that takes care of removing the link of the new husband with any previous wife. Because of the many-to-many semantics this link may stay in place.

Another interesting operation, which is completely identical to the one-to-many case, is the set operation that takes a collection as parameter. It can be clearly divided into three parts. First, you need to remove the links with all the current husbands. Next you set the field to the new value. Third, set up the link with the new husbands.

     public void setMyHusbands(Set elements) {          if ( this.f_myHusbands != elements ) {               // remove the link with all current husbands               Iterator it = this.f_myHusbands.iterator();               while ( it.hasNext() ) {                    Man x = (Man) it.next();                    x.z_internalRemoveFromMyWifes( (Woman)this );               }               this.f_myHusbands = elements;               if ( f_myHusbands != null ) {                    // set up the link with the new husbands                    it = f_myHusbands.iterator();                    while ( it.hasNext() ) {                         Man x = (Man) it.next();                         x.z_internalAddToMyWifes( (Woman)this );                    }               }          }     }

Finally, because we want to keep the links between the husbands and wives consistent, we have implemented the get operations to return an unmodifiable list. If you wanted to add or remove a husband or wife, you would have to use the operations made for this purpose. This enforces the ABACUS rules: associated objects are aware of adding or removing links.

     public Set getMyWifes() {          if ( f_myWifes != null ) {               return Collections.unmodifiableSet(f_myWifes);          } else {               return null;          }     }

One-way Navigable Associations
Now that we have explained much of the complex stuff about associations, let’s go back to a simpler case: one where only one of the association ends is marked navigable. When making a class diagram (see Figure 4), an arrowhead is used to depict the association end that is navigable; the other end is non-navigable. This means that the object that is an instance of the class the arrow is pointing to is not aware of the relationship. In fact, this is simply a way to model the ordinary pointer.

Figure 4. Obsessions Go One Way: For one-way navigable associations, denoted by the arrowheads at one end of each association, it made sense to change the example from a marriage to a case of celebrity adoration.

You wouldn’t use this configuration if you want to model a relationship like a marriage; it works when you want to model a directional relationship, like between a pop star and his/her fans. Britney Spears can’t be expected to know each of her fans in person. By the way, it is also possible in UML to make both ends non-navigable, but this makes no sense at all.

Implementing a one-way navigable association means that the class that the arrow is pointing at, Woman in this example, is not affected at all. The other class, Man, only needs a field and simple get and set operations?or if the multiplicity is many, the operations that implement adding and removing of associated objects.

In the modeling community there is a debate over whether or not you should add the rolename and multiplicity at the side that is non-navigable. From the point of view of the implementation of the association this information is irrelevant. It will not be represented in the code anywhere. However, adding them could give the reader of the model some insight. In the example, we could have added the rolename fan, and the multiplicity zero to many (0..*), and you would have known that there are possibly many instances of the class Man that hold a pointer to a single instance of the class Woman.

Generation of Code for Associations
From the above it is clear that implementing associations is not a trivial matter. Don’t you find it amazing how much code you need to implement such a simple diagram, or how much of the code needs to be changed when you decide to change a ‘1’ in the diagram into a ‘1..*’? This certainly shows the power of modeling.

Yet there are only a limited number of different configurations for an association. In this article we have shown you how to implement four: the one-to-one, the one-to-many, the many-to-many, and the one-way-navigable configuration. Other configurations come from combining these with an association class. (The implementation of these classes will be the subject of our next article.) Furthermore, every association with the same configuration can be implemented in the same manner. That is why we advocate the generation of code, particularly for implementing associations.

When you have to implement associations manually, you have to do the same thing over and over again, which is not very interesting. Besides, when you do this manually (and get bored), you easily make mistakes. And when the modeler decides that the multiplicities should be changed, you can start all over again. This is the reason that implementing associations is a good example of the power of the Model Driven Architecture (MDA). MDA tools that generate code from a UML model also generate the implementation of the associations. Examples of such tools are Octopus and the Eclipse Modeling Framework.

Author’s Note: The Eclipse Modeling Framework is a modeling framework and code generation facility for building tools and other applications based on a structured data model. From a model specification described in XMI, EMF provides tools and runtime support to produce amongst other things a set of Java classes for the model.

In this article we have explained the output of Octopus. However, different implementations are possible, as long as they adhere to the ABACUS rules. For instance, EMF uses a completely different approach than Octopus. Behind the scenes there is a complete notification framework that does the job of notifying the partner object of the creation of a link. We feel that this framework is unnecessarily complicated, and we have shown that a simpler implementation also runs correctly. We adhere to the principle that the code you generate should be (almost) the same as the code you would have written by hand. At least, the generated code should be readable and easy to understand.

Note that the traditional UML modeling tools also claim that they generate code, but they do not generate a correct implementation of associations. Tools such as Together, Poseidon, or Rational Rose simply generate a pair of pointers and leave the rest up to you. If you want to do us a favor, then please, take up your duty as consumers of these tools and demand from the suppliers that they generate correct implementations of associations. It can be done and it will decrease your efforts to implement a UML model.

Mind Teasers
To conclude this article we leave you some mind teaser that can help you to become fluent in the use of associations in a model. Simply play around with them and try to understand what a particular model means for the instances of the given classes.

  1. Figure out how to model a marriage using one class: Person, without using the classes Man and Woman. Also try to model a homosexual marriage.
  2. Another interesting situation is where there are two associations between the same two classes. Try to model a situation where a person may work for a bank, thus having a employee-employer relationship, while at the same time that person is a client to the bank. What if the person works for several banks?

Finally, we would like to stress the fact that introducing a Man and a Woman class to represent people is not always a good idea. We used them in this article to explain the UML association, but in real life things are usually more complicated, which creates demand for a different classification.

See also  Should SMEs and Startups Offer Employee Benefits?
devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist