A UML class diagram visualizes the different classes of a system, their attributes, operations, and relationships to one another. When applied correctly, UML illustrates exactly how class diagrams can be translated into code. The core problem - and often the most difficult part - is interpreting the diverse class relationships correctly.

In the following, I explain the most important relationship types in UML class diagrams. Each relationship is briefly explained, an everyday example illustrates it, and a section with a C++ code example concludes the section.

The goal of this article is to clarify, even for code-oriented people - like myself - how a UML class diagram is translated into code. The article is aimed at developers with existing UML experience, but can also help beginners taking their first steps with UML.

Association

An association is a “knows-a” relationship; neither object is part or a member of the other. It is the weakest relationship that indicates ownership.

The following image illustrates the directed association “Foo knows a Bar.”

Association

If the arrow is omitted, it means that both classes know each other. For simplicity, I focus on the example of the directed association.

Everyday example: Doctor and Patient - A class “Doctor” and a class “Patient” can have a directed association. A doctor cares for his patients and knows their medical history, but a patient normally does not know all the details about his doctor. In this case, the association is directed from the doctor to the patient.

Example Implementation

class Bar {
  
}

class Foo {
  Foo(Bar *bar) : bar_ptr(bar) {}
 
  void SetBar(Bar *bar) { bar_ptr = bar; }
 
  void Func() { bar_ptr->FuncA(); }

  

  Bar *bar_ptr; // pointer
};

Aggregation

Aggregation is a special form of association and represents a “has-a” relationship. It means that I have an object that I have borrowed. I can continue to live even if this object no longer exists.

The following image depicts the aggregation “Foo has a Bar.”

Aggregation

An aggregation can occur when a class is a collection or container for other classes, but the contained classes are not strongly dependent on the container. That is, if the container is destroyed, its contents remain unaffected.

One could confuse aggregation with association, since the difference is merely logical: it depends on whether the object is part of the other or not.

Everyday example: Soccer team and players - A soccer team consists of many players. In this case, the soccer team is the whole and the players are the parts. A soccer team “has” players.

Example Implementation

As described above, the implementations of association and aggregation do not differ.

class Bar {
  
}

class Foo {
public:
  Foo(Bar *bar) : bar_ptr(bar) {}
 
  void SetBar(Bar *bar) { bar_ptr = bar; }
 
  void Func() { bar_ptr->FuncA(); }

  

  Bar *bar_ptr; // pointer
};

Composition

A composition is a special form of aggregation and represents an “owns-a” or “belongs-to” relationship. I own an object and am responsible for its lifetime. If I no longer existed, then the object would also no longer exist.

The following image depicts the composition “Foo owns a Bar.”

Composition

This is a stronger form of the “has-a” relationship, implying that the lifetime of the part object (the “possession”) is bound to that of the whole.

Everyday example: Human and Heart - A human owns a heart. A human cannot exist without his heart. If the heart ceases to exist, the human also dies. Here, the composition is from the class “Heart” to the class “Human.”

Example Implementation

// Example 1
class Foo {
public:
  Foo() : bar_ptr(nullptr) { bar_ptr = new Bar()}
  ~Foo() { delete bar_ptr; }
  
private:
  
  Bar *bar_ptr; // pointer
};

// Example 2
class Foo {
   
   Bar bar;
}

Generalization

Generalization is another term for inheritance. It represents “is-a” relationships. With this, we assign a generic term (category) to an object.

The following image depicts the generalization “Foo is a Bar.”

Generalization

Everyday example: Animal and Dog - A dog is an animal. In this case, “Animal” would be the base class and “Dog” would be a derived class.

Example Implementation

class Bar {
public:
  ~virtual Bar() {};

  virtual void FuncA() {  /* Bar::FuncA implementation */ }
  virtual void FuncB() {  /* Bar::FuncB implementation */ }
  
};

// Generalization of Bar
class Foo : public Bar {
public:
  virtual void FuncA() { Bar::FuncA();  /* Foo::FuncA implementation */}
  virtual void FuncB() { Bar::FuncB();  /* Foo::FuncB implementation */}
}

Dependency

Dependencies represent a “uses-a” relationship between two classes. Here, a change in one class may require changes in the dependent class.

The following image depicts the dependency “Foo uses a Bar.”

Dependency

Everyday example: Cook and Recipe - A cook depends on a recipe to prepare a dish. If the recipe is changed (e.g., the ingredients or the preparation instructions), the cook must change his method of preparing the dish accordingly.

Example Implementation

class Foo {
  ...
  void F1(Bar y) {; y.FuncA(); }
  void F2(Bar *y) {; y->FuncB(); }
  void F3(Bar &y) {; y.FuncC(); }
  void F4() { Bar y; y.FuncD(); }
  void F5() {; Y::StaticFunc(); }
  ...
};

Realization

A realization is a relationship between two classes in which one class implements or “realizes” the behavior defined by another class or, often, by an interface. One could say that it is a “fulfills-the” relationship.

The following image depicts the realization “Foo fulfills the IBar interface.”

Realization

Everyday example: It is difficult to find an everyday example, as this concept is hardly tangible in the real world. Roughly speaking, a class could implement an interface “Walkable” that defines a method “walk.” This class could be a “Dog,” a “Human,” or a “Robot” - anything that can “walk.”

Example Implementation

// Abstract interface
class IBar {
  ~virtual IBar() {};

  
  virtual void FuncA() = 0;
  virtual void FuncB() = 0;
};

// Realization of interface IBar
class Foo : public IBar{
  
  virtual void FuncA() {  /* FuncA implementation */}
  virtual void FuncB() {  /* FuncB implementation */}
}

Summary

This article illuminates the relationship types in UML class diagrams and their translation into C++ code. The most important relationship types are association, aggregation, composition, generalization, dependency, and realization. By comparing them with everyday examples and concrete C++ implementations, these concepts are presented in a clear and understandable manner.

Bibliography

  1. Sadique, Ali. UML Class Diagram Explained With C++ samples
  2. UML 2 Tutorial - Class Diagram
  3. C++ Mapping to UML
  4. Wikipedia - Class diagram