- 1. Class Diagram
- 2. Class Relationships
- 2.1 Multiplicity in Relationships
- 2.2 Composition (
Has-a
)◆────────
- 2.3 Aggregation (
Has-a, but weaker
)◇────────
- 2.4 Association (
Knows-a, Uses-a
)────────
- 2.5 Dependency (
Uses temporarily
)- - - - - ->
- 2.6 Inheritance (
Is-a
)────────▷
- 2.7 Realization (
Implements an Interface
)- - - - - -▷
- 2.8 Class Template (
Generic Class
) - 3. Summary of Relationships
A UML Class Diagram is a structural diagram that represents the blueprint of a system by illustrating its classes, attributes, methods, and relationships. It is widely used in object-oriented design to model the static structure of software systems.
Each class in the diagram is depicted as a rectangle divided into three sections:
- Class Name (for example, a
Car
) - Attributes (for example,
color: string
,speed: int
) - Methods (for example,
startEngine()
,brake()
)
Classes interact through different types of relationships, including:
- Association: A connection between two classes (
Driver
↔Car
). - Inheritance (Generalization): A subclass inherits from a superclass (
ElectricCar
→Car
). - Aggregation & Composition: Whole-part relationships (
Library
containsBooks
).
Let's start with the following example:
class Animal
{
private:
std::string name;
protected:
int id;
public:
void setName(std::string name);
virtual void move();
static int count();
};
In UML, we typically represent a class using a rectangle divided into three sections:
- Class Name (Top section)
- Attributes (Member Variables) (Middle section)
- Methods (Member Functions) (Bottom section)
The class diagram for the Animal
class is shown below:
-
Access Specifiers:
-
(minus sign) → Private (e.g.,name
)#
(hash sign) → Protected (e.g.,id
)+
(plus sign) → Public (e.g.,setName()
,move()
,count()
)
-
Virtual Functions (
move()
):- In UML, virtual methods are written in italics →
move(): void
- In UML, virtual methods are written in italics →
-
Static Methods (
count()
):- In UML, static methods are underlined →
count(): int
- In UML, static methods are underlined →
-
Protected members are denoted with
#
So far, we have only dealt with classes containing primitive data types like int
, double
, and std::string
. However, in real-world applications, classes often relate to other classes, forming different types of relationships such as inheritance or composition.
The main types of relationships between classes are:
- Composition (Has-a)
◆────────
- Aggregation (Has-a, but weaker)
◇────────
- Association (Knows-a, Uses-a)
────────
- Dependency (Uses-a temporarily)
- - - - - ->
- Inheritance (Is-a)
────────▷
- Realization (Implements an Interface)
- - - - - -▷
- Class Template (Generic Type)
Multiplicity defines how many instances of one class can be associated with an instance of another class:
"0..1"
→ Zero or one instance (optional)"1"
→ Exactly one instance"0..*"
or"*"
→ Zero or more instances"1..*"
→ At least one instance
In real-world scenarios, complex objects are composed of smaller parts. For example, a car consists of an engine, tires, and a transmission. In C++, when you define a class, it can contain members of other classes, forming a composition.
Composition is a strong relationship where:
-
The contained object is an essential part of the container.
-
The contained object can only belong to one container at a time.
-
The container manages the lifecycle of the contained object.
-
The contained object has no independent existence outside the container.
-
Circle
andPoint
ACircle
has aPoint
representing its center.
class Point
{
};
class Circle
{
private:
Point center;
};
Person
and theirHeart
Another example is the relationship between aPerson
and theirHeart
. TheHeart
exists only as part of thePerson
:
class Heart
{
};
class Person
{
private:
Heart heart;
};
- Folder and Files
A folder contains multiple files, and if the folder is deleted, all files inside it are deleted too.
- Each
File
must be associated with aFolder
when it is created. - A
Folder
can have 0 or moreFile
objects.
class Folder; // Forward declaration
class File {
public:
explicit File(Folder& parent) : parentFolder(parent) {} // Enforce 1-to-1 relationship
private:
Folder& parentFolder; // Each File belongs to exactly one Folder
};
class Folder {
public:
std::vector<File> files; // 0..* composition
};
Aggregation is similar to composition but weaker. The key difference is that the contained object can exist independently of the container.
- The contained object can belong to multiple containers.
- The container does not manage the lifecycle of the contained object.
- The contained object can exist independently.
A Department
has a Professor
, but the Professor
can exist independently of the Department
.
class Professor
{
private:
std::string name;
public:
Professor(std::string name) : name(name) { }
std::string getName() { return name; }
};
class Department
{
private:
Professor *professor;
public:
Department(Professor *professor = nullptr) : professor(professor) {}
};
In an association, two objects have a relationship, but neither controls the other’s lifecycle. For example, a Teacher and a Student have an association: a teacher teaches multiple students, and a student learns from multiple teachers.
class Student;
class Teacher
{
private:
std::vector<std::reference_wrapper<const Student>> students;
public:
void addStudent(Student& student);
};
class Student
{
private:
std::vector<std::reference_wrapper<const Teacher>> teachers;
public:
void addTeacher(const Teacher& teacher)
{
teachers.push_back(teacher);
}
};
A dependency (---->
) occurs when a class temporarily depends on another class. The dependent class uses the other class as a function parameter or local variable, but does not store it as a member.
class Y
{
public:
void foo(){}
static void StaticFoo(){}
};
class X
{
void f1(Y y) { y.foo(); }
void f2(Y* y) { y->foo(); }
void f3(Y& y) { y.foo(); }
void f4() { Y y; y.foo(); }
void f5() { Y::StaticFoo(); }
};
In the example, X
depends on Y
in several ways:
f1(Y y)
:Y
is passed by value as a parameter.f2(Y* y)
:Y
is passed as a pointer parameter.f3(Y& y)
:Y
is passed as a reference parameter.f4()
:Y
is created as a local variable.f5()
:X
calls a static method ofY
.
In all these cases, X
depends on Y
temporarily, but Y
is not stored as a member of X
. This is a classic example of a dependency relationship.
-
Dependency Arrow (
..>
):- The dashed arrow (
..>
) indicates a dependency relationship. - It points from the dependent class (
X
) to the class it depends on (Y
).
- The dashed arrow (
-
Temporary Usage:
X
usesY
temporarily in its methods (as parameters, local variables, or static method calls), butY
is not a member ofX
.
-
No Ownership:
X
does not own or manage the lifecycle ofY
. The dependency is short-lived and limited to the scope of the methods.
Inheritance represents an "is-a" relationship, where a subclass inherits properties and behaviors from a base class.
class Shape
{
public:
virtual void draw();
virtual double getArea();
void erase() {} // Not virtual
};
class Circle : public Shape
{
void draw() override {}
double getArea() override {}
};
class Ellipse : public Shape
{
void draw() override {}
double getArea() override {}
};
A realization relationship is used when a class implements an interface.
class Player {
public:
virtual void play() = 0;
virtual void stop() = 0;
virtual void pause() = 0;
virtual void reverse() = 0;
};
A class template allows defining a class that can work with any data type.
template <class T>
class Foo { T item; };
Foo<int> fooInt;
Foo<double> fooDouble;
- Composition (
Has-a, owns
): Strong relationship; contained object is destroyed with the container. - Aggregation (
Has-a, but weaker
): Weak relationship; contained object can exist independently. - Association (
Knows-a
): Objects are related but don’t manage each other’s lifecycle. - Dependency (
Uses-a temporarily
): Short-term relationship via function parameters. - Inheritance (
Is-a
): A subclass inherits properties from a base class.