devxlogo

Wed Yourself to UML with the Power of Associations, Part 2

Wed Yourself to UML with the Power of Associations, Part 2

n the first article in this series, we explained the concept of associations in UML and showed how to implement them. This article takes that knowledge one step further and explains the UML association class and its implementation. The implementation examples are again written in Java, but you can translate them to another programming language easily. The code examples are 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.

Association Classes and Their Meaning
After reading our earlier article you might think that implementing an ordinary association is already plenty complicated, but the UML has more in store for us in the form of an association class.

Last time, we compared a normal association, one without an association class attached, to a marriage. Any two objects that are linked through an association are “married.” This comparison still holds for association classes. So we are going to explore the concept of associations using the model shown in Figure 1.

Figure 1. One to One: A monogamous marriage as an association class.

The model shows that Man instances can be married to Woman instances, and this relationship is an instance of another class called Marriage. This is what an association class really means: It upgrades a normal association to a class that can be instantiated. Thus the relationship itself can be treated as an object.

A reason for upgrading the association to an association class, could be that you wish to keep information that is specific to the association itself but not to either of the partners. For instance, if you want to keep the date and place that the marriage was celebrated, neither the Man nor the Woman class is a good place to store the information. This information should be stored as attributes in the Marriage class. Each instance of this class would have its own values for the attributes.

The association class is a complete class. Besides the fact that it can be instantiated, it may have operations, attributes, and associations of its own. For instance, the Marriage class can have an association with the church in which the ceremony was held, or with the person that performed the marriage service. Figure 1 shows the latter. The model does not provide a gender-neutral Person class, so for the sake of the example we have arbitrarily made this officiant a man.

The ABACUS Rules for Association Classes
In our previous article we explained that normal associations have a number of characteristics, which we have dubbed the ABACUS rules. The ABACUS rules all hold true in the case that an association is upgraded to an association class. We’ll go through the rules again briefly to see how their application differs from the normal associations.

  • Awareness: Both objects are aware of the relationship between them. They know about each other, but they also know about the association class instance that represents their relationship.
  • Boolean existence: If the partners agree to end the relationship, it is dissolved completely. When an association class is involved, this means that either the association class instance exists and holds references to both partners, or the instance does not exist at all. You cannot have an association class instance that holds a reference to just one of the partners.
  • Agreement: Both objects have to agree with the relationship. This is similar to the normal association.
  • Cascaded deletion: If one of the partners is deleted, the link is dissolved as well. Using an association class this mean that the instance of the association class is deleted along with everything else.
  • USe of role names: An object may refer to its partner using the role name provided with the association: “my husband” or “my wife”. But the object may also refer to the instance of the association class that represents the relationship. Because the UML does not allow you to define your own names for doing this, the name of the association class is being used for this purpose.

Association Class Instances: One to One Links
An important thing to know about association classes is that an instance of an association class always relates one instance of the class at the one end with one instance of the class at the other end. No matter what the multiplicities at both ends, an association class instance represents a one-to-one link (see Figure 2).

Figure 2. Polygamous Marriage: We need two instances of the association class?one to collect the unique data of each union.

In Figure 2 Mary is married to both John and Bob. Both marriages could have taken place on a different day, in a different place, with a different preacher. Therefore, there should be two instances of the association class representing the two marriages, each with its own values for the attributes and associations. Beside, if one of the two marriages were to break up, only the one instance representing the failed marriage should be removed.

See also  AI's Impact on Banking: Customer Experience and Efficiency

Most association class instances can be uniquely identified by the instances of the two classes that they relate. The one marriage in the example is the unique link between John and Mary, and the two marriage uniquely relates Bob and Mary. There can be no two instances of Marriage that relate the same two objects. An exception to this is when both the ends are marked or . As you may remember from the last article this means that the type of the association end is a collection that may hold the same element more than once. In that case there may be more than one instance of Marriage that relates John and Mary. If you need to uniquely identify association class instances in this case, you need to add an attribute to the Marriage class that has a unique value over all Marriage instances. If there is no explicit need for unique identification, normal object identity will suffice.

Implementing Association Classes
Now that you know what an association class means and how its instances are characterized, we can have a look at how they should be implemented. Central in the implementation is that you want to use the association that has an association class attached, in the same way as a normal association. That is, in the one-to-many, or many-to-many situation you want to be able to add a husband to a Woman instance by calling the operation addToHusbands, whereas in the one-to-one situation you would like to use the operation setHusband. Adding a husband to a Woman instance implies the creation of an instance of the association class Marriage. Our implementation ensures that these instances are created only when one of the addToHusbands or setHusband operations is called. Creating an instance of an association class in a different manner would cause inconsistencies in the system.

In the previous article we differentiated between four configurations of association ends: one-to-one, one-to-many, many-to-many, and the one-way navigable association. The first three configurations also appear in combination with an association class. One-way navigable associations are not useful when an association class is attached, therefore we will not discuss this configuration.

The Association Class Itself
The implementation of the association class itself is the same for all three configurations. The implementation should hold two fields that are pointers to the two objects that are related through the association class instance. The type of the one field is the class at the one end and the type of the other field is the class at the other end. Each field should have a normal get operation, but there should not be a set operation. The fields get their value on creation of the association class instance, as shown in the following code.

public class Marriage {     ...     private Man f_husband = null;     private Woman f_wife = null;     public Marriage(Man a, Woman b) {          if ( a != null && b != null ) { // can't relate an object to null               this.f_husband = a;               a.z_internalAddToMarriage(this);               this.f_wife = b;               b.z_internalAddToMarriage(this);          }     }     public Man getMyHusband() {          return f_myHusband;     }          public Woman getMyWife() {          return f_myWife;     }     ...}

The constructor of the Marriage class takes cares of setting up the link between the two partners by calling the special z_internalAddToMarriage operations of both. This operation should only be called from the constructor of the Marriage class, which explains its obscure name. Internally the link between the two partners is implemented as a link from the Woman object to the Marriage object, which in turn holds a link to the Man object, and vice versa. In Figure 3, which shows the implementation, the arrows represent the pointers between the instances.

Figure 3. A Pointed Marriage: An implementation of the one-to-one association class is shown.

The Partner Classes in the One-To-One Situation
Both classes at the ends of the association can be implemented in almost the same way in the one-to-one situation. Each class should have a field, whose type should be the association class. There should be a get operation for this field (getMarriage), but no set operation. Instead the setMyWife and setMyHusband operations should be used. These operations are the most important ones, because they are responsible for making the new partner object aware of the link. Let’s have a look at the code for the Man class.

public class Man {     ...     private Marriage f_marriage = null;     public void setMyWife(Woman element) {          if ( this.f_marriage != null ) {                // this man is already married, remove the old marriage               ((Marriage)this.f_marriage).clean();          }          if ( element != null ) {               if ( element.getMarriage() != null ) {                    // the new wife is already married, remove the old marriage                    ((Marriage)element.getMarriage()).clean();               }               // create the new marriage               this.f_marriage = new Marriage(this, element);          } else {               // cannot marry a null object, so the marriage is set to null               this.f_marriage = null;          }     }     ...}

As in the case of the normal association, you first check whether this Man object is already married. If so, you delete the old marriage in favor of the new one. This is done by calling the clean operation of the Marriage class:

     public void clean() {          f_myHusband.z_internalRemoveFromMarriage(this);          f_myHusband = null;          f_myWife.z_internalRemoveFromMarriage(this);          f_myWife = null;     }

In effect this operation removes all of the arrows from Figure 3 that showed the implementation. The z_internalRemoveFromMarriage operations set the pointer to the marriage instance to null. You rely on the garbage collector to actually remove the Marriage instance. Note that this implementation upholds the ABACUS rules; when the relationship is dissolved, the association class instance is dissolved as well. If the association class instance is referenced by other objects, these references should be cleared as well, otherwise the garbage collector will not remove the marriage instance. This situation is not handled in the code shown. However, removing an instance while there are still references to it is a bad idea anyway, therefore you should take care that these references are explicitly cleaned up before deleting the association.

See also  Reasons to Invest in Legal Workflow Software

Next, check whether the new wife object is already married. If so, her old marriage is deleted. Finally, you create a new Marriage instance. This is the only line that is different for the setMyHusband and setMyWife operations. The Marriage constructor takes as parameters first a Man and second a Woman instance, therefore you need to switch the order of the actual parameters. The call to the constructor in setMyHusband is:

     this.f_marriage = new Marriage(element, this);

Any Man instance must be able to use the role name to refer to the Woman instance it is related to. Because the implementation does not provide a direct pointer to the Woman instance, we have to implement the getMyWife operation using the reference provided by the Marriage instance:

     public Woman getMyWife() {          if ( this.f_marriage != null ) {               return this.f_marriage.getMyWife();          } else {               return null;          }     }

The Partner Classes in the One-To-Many Situation
When implementing the classes at the ends of the association in the one-to-many situation, you must differentiate between the many end and the one end. At the many end things look more or less like they did in the one-to-one situation, but at the one end things are different. Let’s have a look at the many end first. The setMyWife operation in the Man class is implemented as follows.

     public void setMyWife(Woman element) {          if ( this.f_marriage != null ) {               // this man is already married, remove the old marriage               ((Marriage)this.f_marriage).clean();          }          if ( element != null ) {               // create the new marriage               this.f_marriage = new Marriage(this, element);          } else {               this.f_marriage = null;          }     }

You still need to check whether this man is already married, but you do not have to check anymore whether the new wife is married. She is allowed to be married multiple times.

On the many end, the implementation of the Woman class differs to a greater extent. First the type of the field that refers to the association class is no longer Marriage, instead it is java.util.Set. It holds a collection of Marriage instances. Second, as in the normal case, the implementation of the Woman class must have the following operations:

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

A closer look at the add and remove operations that take a single object as parameter shows that they are the most complex ones. They are implemented as follows:

     public void addToMyHusbands(Man element) {          if ( element != null ) {               if ( element.getMarriage() != null ) {                    ((Marriage)element.getMarriage()).clean();               }               new Marriage(element, this);          }     }          public void removeFromMyHusbands(Man element) {          if ( element != null ) {               if ( element.getMarriage() != null ) {                    ((Marriage)element.getMarriage()).clean();               }          }     }

In the addToMyHusbands operation an existing marriage of the new husband is deleted. In the removeFromMyHusbands operation the existing marriage of the removed husband?which is the one with this woman instance?is deleted. You simply reuse the existing clean operation in Marriage to take care of upholding the ABACUS rules.

Another operation that is worth examining is the one that enables a Woman object to use the role name myHusbands to refer to the set of Man instances it is related to. It is implemented as follows.

     public Set getMyHusbands() {          Set /*(Man)*/ result = new HashSet( /*Man*/);          Iterator it = this.f_marriage.iterator();          while ( it.hasNext() ) {               Marriage elem = (Marriage) it.next();               result.add( elem.getMyHusbands() );          }          return result;     }

As in the one-to-one situation, you use the f_marriage field to obtain the values that should be in the result set. Remember that this field is of type java.util.Set. Therefore you need to iterate over its elements and obtain the value of the getMyHusbands operation for every element. Note that the getMyHusbands operation in the Marriage class returns a single Man object, even though its name suggests otherwise. Its name is derived from the role name at the Man end, which is myHusbands.

The Partner Classes in the Many-To-Many Situation
As can be expected, the implementation of the classes at the ends of the association in the many-to-many situation looks very much like the implementation of the Woman class in the one-to-many situation. And, because of the symmetry, both classes can be implemented in the same manner. Both classes have a field that holds a set of Marriage instances, and a number of operations that implement the addition and removal of partner instances. Again, the most interesting operations are the addition and removal of a single element. We’ll have a look at the addToMyHusbands operation in the Woman class first.

     public void addToMyHusbands(Man element) {          boolean isPresent = false;          // ensure that the new husband is not allready present          Iterator it = f_marriage.iterator();          while ( it.hasNext() && !isPresent ) {               Marriage elem = (Marriage) it.next();               if ( elem.getMyHusbands() == element ) {                    isPresent = true;               }          }          if ( !isPresent ) {               f_marriage.add(new Marriage(element, this));          }     }

Most of the code in this operation is there because we need to ensure that the collection of husbands is a set and not a bag or sequence. For this we iterate over the set of Marriage instances of this woman. Only if the new husband is not present in the set of husbands a new Marriage instance is created.

Likewise, before removing an element from the set of husbands, you first need to find it. Therefore, we need to iterate over the set of Marriage instances of this woman in the removeFromMyHusbands operation as well. When you have found the corresponding element you can remove it, using the clean operation in the Marriage class.

     public void removeFromMyHusbands(Man element) {          Marriage foundElem = null;          Iterator it = f_marriage.iterator();          while ( it.hasNext() ) {               Marriage elem = (Marriage) it.next();               if ( elem.getMyHusbands() == element ) {                    foundElem = elem;               }          }          if ( foundElem != null ) {               ((Marriage)foundElem).clean();               this.f_marriage.remove(foundElem);          }     }

Finally …
Now you know how to implement associations with and without association classes. As we mentioned before, building a good implementation is not a trivial matter and is a good argument for generating code from a UML model. Just think of the situation where the modeler suddenly decides to upgrade an association to an association class. That would be a disaster for the programmer; he would need to start all over again. And, just as in the case for the normal associations, the association classes can all be implemented in the same manner. You only need to adjust the role names. We can conclude only this: We desperately need some better code generation tools on the market.

Mind Teasers
As we did last time, we would like to leave you with a few puzzles that will make you understand association classes better.

  1. Try to model the 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. The person has a mortgage from this bank, and the bank-client relationship should hold information on, for instance, loan sum, security, and monthly payment. The employee-employer relationship should hold information on, for instance, salary, start date, and function description.
  2. An association class is often compared with a link table in a database. The records in the link table hold the keys of the records in the other tables that it relates, but it may also have attributes of its own. See for yourself if you can agree with this comparison. Are there differences or not?
  3. Other people suggest that all associations are comparable with link tables. As a consequence of this you might choose to implement normal associations (without association classes) in the same way as association with association classes. What do you think are the advantages and disadvantages of this approach?

See also  Robotic Process Automation (RPA): Streamlining Business Operations with AI
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