Association and Its Implementation

Learn about association, its importance, and its implementation using a real-life example.

What is association?

In contrast to a composition or aggregation, where a part is an integral component of the whole object, an association links two otherwise unrelated objects. Similar to aggregation, the associated object can belong to multiple objects simultaneously. However, unlike an aggregation, where the connection is always unidirectional, an association can be either unidirectional or bidirectional, where both objects are aware of each other.

Real-life example

The bond between doctors and patients is an example of an association. While a doctor has a connection with their patients, it is not a part-whole relationship conceptually. A doctor may see numerous patients in a day, and a patient may visit multiple doctors for a second opinion or to seek different types of medical attention. Neither object’s lifespan is linked to the other.

Press + to interact
Example of association between doctor and patient
Example of association between doctor and patient

Implementation of a real-life example

Let’s write the above relationship in C++:

Press + to interact
Patient.h
main.cpp
Doctor.h
#include <iostream>
using namespace std;
class Doctor; // forward declaration
class Patient {
private:
int id;
string name;
int age;
string gender;
static const int MAX_DOCTORS = 5;
Doctor* doctors[MAX_DOCTORS]; // static array of pointers to Doctor objects
int doctorSize;
public:
Patient(int id, string name, int age, string gender)
: id(id), name(name), age(age), gender(gender), doctorSize(0) {}
int get_id() const { return id; }
string get_name() const { return name; }
int get_age() const { return age; }
string get_gender() const { return gender; }
void add_doctor(Doctor* doctor)
{
if (doctorSize < MAX_DOCTORS)
{
doctors[doctorSize] = doctor;
doctorSize++;
// doctor->add_patient(this); This is dangerous now
}
else
{
cout << "Cannot add more doctors. Maximum capacity reached." << endl;
}
}
void remove_doctor(Doctor* doctor) {
for (int i = 0; i < doctorSize; i++) {
if (doctors[i] == doctor) {
for (int j = i; j < doctorSize - 1; j++) {
doctors[j] = doctors[j + 1];
}
doctorSize--;
return;
}
}
}
Doctor*const* get_doctors() const { return doctors; }
int get_doctorSize() const { return doctorSize; }
};

The code represents an association between doctors and patients in a simplified medical context. It allows for the management of associations between doctors and patients using static arrays of pointers.

  • The Patient class (in Patient.h) represents a patient and includes attributes such as id, name, age, and gender. It contains a static array doctors that can hold a maximum of 5 pointers to Doctor objects. The doctorSize variable keeps track of the number of doctors associated with the patient. Methods like add_doctor() and remove_doctor() handle the addition and removal of doctors from the patient’s list.

  • The Doctor class (in Doctor.h) represents a doctor and includes attributes such as id, name, and specialty. It also contains a static array patients that can hold a maximum of 10 pointers to Patient objects. The patientSize variable keeps track of the number of patients associated with the doctor. Methods like add_patient() and remove_patient() enable adding and removing patients from the doctor’s list.

  • In the main() function, doctors and patients are created, and associations are established by calling add_patient(). Notice that inside the add_patient() function, add_doctor() gets called for the patient pointer by passing this pointer on line 27 of Doctor.h.

  • The program then prints out the associations, displaying the doctors for each patient and the patients for each doctor.

This code provides a basic framework for managing associations between doctors and patients using static arrays.

Exercise: Adding two-way association

In the given implementation, we’ve added association, assuming that the doctor always invokes the add_doctor() function through the reference/pointer of the patient pointer. However, there could be a scenario where the first call happens on add_patient(), and then it should report to the doctor that the doctor should add this patient to its patient list (as commented on line 31 of Patient.h).

If we add the call of add_patient() after adding the doctor, it can lead to an infinite loop of calling each other, potentially causing a stack overflow (if there's no limit of patients and doctors) or filling the same doctor and patient by only adding one in their respective lists (because repeated calls to each other will fill the same pointer in their pointers list—the patient in the doctor's patient list and the$ doctor in the patient's doctor list).

To avoid this issue, we can implement a simple check at both ends. Before adding the associativity, it should check in the pointers list whether the pointer is already added to the list. If it is, we should do nothing to prevent the infinite adding. Your task now is to modify the above program to add this two-way associativity check, ensuring that this infinite calling problem is avoided. Make sure you uncomment line 31 in Patient.h to test your program.

If you have trouble getting to the solution, you can click the “Show Hint” button to see how to modify the above program.

Properties of object relationships

Some sources suggest that there is only one relationship when it comes to distinguishing between object relationships; composition and aggregation are just the implementation choices that programmers make during the coding in some specific language. They try to imagine these relations in the following figure.

Press + to interact
Object relationships
Object relationships

Here, composition represents a strong relationship where one object is composed of others, and the composed objects are dependent on the container object. It signifies ownership, and the lifecycle of the composed objects is tied to the container object. In contrast, aggregation signifies a weaker relationship where one object holds a reference to another, but the referenced object can exist independently and be shared among multiple containers. Similarly, the association is an even weaker relation where the two objects may not have a whole-part relationship but rather a kind of uses relationship.

The choice between composition, aggregation, and association depends on the specific requirements and semantics of the system being modeled. It is important to consider factors such as ownership, lifecycle management, and the level of dependence between objects when making this distinction. Ultimately, the goal is to accurately represent the relationships between objects in a way that aligns with the intended design and behavior of the system.

Properties of Object Relationships

Property

Composition

Aggregation

Association

Relationship Type

Whole/part

Whole/part

Unrelated

Members May Belong to Multiple Classes

No

Yes

Yes

Members Existence Managed by the Container Class

Yes

No

No

Directionality

Unidirectional

Unidirectional

Unidirectional or bidirectional

Relationship Verb

PART-OF

HAS-A

USES-A

Sidenote

Look at the following discussion in the UML (Unified Modeling Language)UML (Unified Modeling Language) is a standard notation for the modeling of real-world objects as a first step in developing an object-oriented design methodology. user guide, “UML REFERENCE MANUAL, 1999,” written by James Rumbaugh, who was one of the three authors of the UML.

Page 148, “Discussion: The distinction between aggregation and association is often a matter of taste rather than a difference in semantics. Keep in mind that aggregation is association. Aggregation conveys the thought that the aggregate is inherently the sum of its parts. In fact, the only real semantics that it adds to association is the constraint that chains of aggregate links may not form cycles, which is often important to know, however. Other constraints, such as existence dependency, are specified by the multiplicity, not the aggregation marker. In spite of the few semantics attached to aggregation, everybody thinks it is necessary (for different reasons). Think of it as a modeling placebo.”