Browse DevX
Sign up for e-mail newsletters from DevX


Wed Yourself to UML with the Power of Associations : Page 2

Some UML concepts are more difficult to grasp than others, and associations are a concept that proves the rule. But by thinking of associations as marriages, we've hit on a way of making even complex associations easy to learn. Walk through this tutorial, which includes implementation examples in Octopus.




Building the Right Environment to Support AI, Machine Learning and Deep Learning

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.

Thanks for your registration, follow us on our social networks to keep up-to-date