Object-Oriented Design: Six Core Class Relationships in C++
UML Visibility Notations
In UML class diagrams, the accessibility of class members is denoted by specific symbols preceding the member name:
+: Public-: Private#: Protected
1. Generalization (Inheritance)
Generalization represents an "is-a" relationship, allowing a child class to inherit the attributes and behaviors of a parent class. In C++, this is implemanted through class inheritance.
class Vehicle {
protected:
double velocity;
int manufactureYear;
public:
Vehicle() : velocity(0.0), manufactureYear(2023) {}
virtual ~Vehicle() {}
};
class Truck : public Vehicle {
private:
double loadCapacity;
public:
Truck(double capacity) : loadCapacity(capacity) {}
};
2. Realization (Implementation)
Realization occurs when a class implements the behavior defined in an interface or an abstract base class. In C++, this typically involves overriding pure virtual functions.
class IShape {
public:
virtual void render() = 0; // Pure virtual function
virtual ~IShape() {}
};
class Circle : public IShape {
public:
void render() override {
// Implementation for drawing a circle
}
};
class Rectangle : public IShape {
public:
void render() override {
// Implementation for drawing a rectangle
}
};
3. Aggregation
Aggregation is a "part-of" relationship where the part can exist independently of the whole. It represents a weak ownership. In code, this is often represented by a class holding a pointer or reference to an object that was created eslewhere.
class Professor {
private:
std::string name;
public:
Professor(std::string n) : name(n) {}
};
class Department {
private:
Professor* lead; // The Professor can exist without the Department
public:
void assignLead(Professor* p) {
lead = p;
}
};
4. Composition
Composition is a strong "part-of" relationship where the part's lifecycle is strictly tied to the whole. If the container is destroyed, the contained objects are also destroyed. This is usually implemented by nesting objects directly as member variables.
class Processor {
public:
void execute() {}
};
class Computer {
private:
Processor cpu; // CPU is created and destroyed with the Computer
public:
void start() {
cpu.execute();
}
};
5. Dependency
Dependency is a "use-a" relationship. It is the weakest relationship, where one class depends on another because it uses it as a parameter in a method, a local variable, or a return type. A change in the supplier class may affect the client class.
class Document {
public:
void readData() {}
};
class Printer {
public:
// Printer depends on Document as a method parameter
void print(const Document& doc) {
// Printing logic
}
};
6. Association
Association represents a structural relationship between classes where objects of one class are connected to objects of another. Unlike dependency, which is temporary within a function scope, association implies a long-term link. It can be unidirectional, bidirectional, or self-referencing.
Unidirectional and Bidirectional Association
class Passport;
class Person {
private:
Passport* document; // Link to Passport
};
class Passport {
private:
Person* owner; // Bidirectional link back to Person
};
Self-Associasion
This occurs when a class has an association with itself, common in data structures like linked lists or trees.
class BinaryTreeNode {
public:
int data;
BinaryTreeNode* leftChild;
BinaryTreeNode* rightChild;
};