list of dots

User Manual    [Previous]   [Next]   

Associations in Traits

An association is a useful mechanism to specify relationships among instances of classifiers. An association implies the presence of certain variables and provided methods in both associated classifiers.

Associations in traits are defined in the same way they are defined for classes. Traits can make associations with interfaces, classes, and template parameters. If one end of the association is a template parameter, the binding type must be checked to make sure it is compatible with the type of the association. For example, if a trait has a bidirectional association with a template parameter, the binding value cannot be an interface and it must be a class. This is an extra constraint applied to template parameters.

When an association is defined, APIs (set of methods) are generated in the class to allow such actions as adding, deleting and querying links of associations. Traits may use these APIs inside provided methods to achieve their needed implementation. The first example below depicts a simple version of the observer pattern implemented based on traits. As can be seen in line 7, the concept of the subject in the observer pattern has been implemented as trait Subject, which gets its observer as a template parameter. A direct association has been defined in trait Subject (Line 8), which has a multiplicity of zero or one on the Subject side and zero or many on the Observer side. This association lets each subject have many observers and it also applies the case in which observers do not need to know the subject. The trait has encapsulated the association between the subject and observers and then applies it to proper elements when it is used by a client.

As each subject must have a notification mechanism to let observers know about changes, there is a provided method notifyObservers() for this. This method obtains access to all observers through the association. Two classes Dashboard and Sensor play the roles of observer and subject. Class Dashboard has a method named update(Sensor) (line 2) used by the future subject to update it. Class Sensor obtains the feature of being a subject through using trait Subject and binding Dashboard to parameter Observer

The second example below is a concrete example of the use of traits to incorporate functionality (in this case dated addresses) that is needed in otherwise unrelated classes (Persons and Businesses). The third examples shows the use of traits that have associations along with other features of Umple.

Example of the Observable pattern using traits with associations

/*
  Example 1: implementing observable pattern
  with traits and associations.
*/
class Dashboard{
  void update (Sensor sensor){ /* code */ }
}
class Sensor{
  isA Subject< Observer = Dashboard >;
}
trait Subject <Observer>{
  0..1  -> * Observer;
  void notifyObservers() { /* code */ }
}
      

Load the above code into UmpleOnline

 

Example showing traits with associations to manage addresses

class Address { street; city; postalCode; country; }

trait EntityWithValidityPeriod {
  Date startDate; Date endDate;
}

trait PurposedEntity {
  purpose; // e.g. home, work, mobile
}

class DatedAddress {
   isA EntityWithValidityPeriod, PurposedEntity, Address;
}

class DatedPhoneNumber {
  isA EntityWithValidityPeriod, PurposedEntity;
  number;
}

trait LocatableEntity {
  0..1 -> 1..* DatedAddress;
  0..1 -> 1..* DatedPhoneNumber;
}

class Person {  isA LocatableEntity;}
class Business {  isA LocatableEntity;}
      

Load the above code into UmpleOnline

 

Example that also includes state machines and mixsets

interface I1 {
  void m1();
}

trait SuperT {
  isA I1;
  mixset f5 {e;}
  void m1() {
    System.out.println("impl of m1");
  }
}

trait T1 {
  isA SuperT;
  Integer c;
  Integer d;
  mixset f6 {[c > d]}
  after setC {
     System.out.println("SetC has happened from T1");
  }
  mixset f1 {1 -- 1..* C4;}
}

trait T2 {
  after setC {
    System.out.println("setC has happened from T2");
  }
}

trait T3 {
  void m2() {
    System.out.println("M2 is executing");
  }
  sm1 {
    s1 {
      e -> s2;
    }
    s2 {
    }
  }
}

trait T4 {
  isA T3;
}

class C1 {
  isA T1, I1;
  mixset f1 {1 -- * C3;}
}

mixset f5 class C2 {
  mixset f4 {1 -- 1..* C3;}
  isA C1, T2;
  void m1() {
    System.out.println("Overriding impl of m1");
  }  
}

class C3 {
}

mixset f4 class C4 {
  isA T4;
  // Commented code bellow will cause an error when mixset f4 is used even when mixset f5 is not used.
  // This because inline mixsets does not support apsect.
  //mixset f5 after custom m2() {System.out.println("m2 occurred");}
  mixset f5 { after custom m2() {System.out.println("m2 occurred");} } 
  after e1 {System.out.printlin("event e1 occurred");}
}
      

Load the above code into UmpleOnline