Classes can have data members of any type, including complex objects
For example
vector<int64_t>
as data membervector<int64_t>There are two different has-a relationships
called
aggregation and composition
In addition, there are two similar, but weaker
relationships called
dependency,
association
Besides "has-a" relationships there are also "is-a" relationships (inheritance)
Examples to illustrate these relationships are frequently highly artificial and have little to do with actual programming (eg. pets, cars or houses)
To have independent objects relate to each other in a stable system, we should (will) study design patterns
A dependency typically implies that an object accepts another object as a method parameter, instantiates, or uses another object
Association, aggregation and composition (see the following slides) imply a dependency
class Master ..> class Dependencyclass Dependency <.. class Master
A trivial example is a class that uses the output stream operator
What does this class depend on?
---
config:
class:
hideEmptyMembersBox: true
---
classDiagram
Master ..> ostream : Master depends on ostream
MasterAssociation is the most general type of relationship between classes
it simply indicates that there is a connection
Aggregation and composition are associations
class A <--> class Bclass Master --> class Usedclass Used <-- class Master
---
config:
class:
hideEmptyMembersBox: true
---
classDiagram
Course --> Student : Course has student(s)
---
config:
class:
hideEmptyMembersBox: true
---
classDiagram
Course <--> Student : Classes are associated
---
config:
class:
hideEmptyMembersBox: true
---
classDiagram
Course <-- Student : Student has course(s)
Course
and Student classesAggregation models a "has-a" relationship
A "uses" member B and
B exists independently from Astd::shared_ptr to contained objectMaster o-- Used
---
config:
class:
hideEmptyMembersBox: true
---
classDiagram
Master o-- Used : Master has one or more Used
School (master) has vector<Person>
(student members)Persons still exist when School is deleted
#include <iostream>
namespace draw {
struct Point {
Point(double x, double y) : x(x), y(y) {};
double x = 0.0;
double y = 0.0;
};
class Circle {
public:
Circle(Point& center, double radius) : center_(center), radius_(radius) {};
void draw() { std::cout << "drawing Circle(" << center_.x << "/"
<< center_.y << ", " << radius_ << ")" << std::endl; }
private:
Point& center_; // references cannot be reassigned; use Point* if needed
// assigning a new value to a reference would use copy assignment of the type
double radius_{1.0};
};
} // namespace draw
int main(void) {
draw::Point center{17, -2}; // devs have to ensure `center` stays in scope
draw::Circle c(center, 5);
draw::Circle c2(center, 12); // center can be shared
c.draw();
c2.draw();
}
#include <iostream>
namespace draw {
struct Point {
Point(double x, double y) : x(x), y(y) {};
double x = 0.0;
double y = 0.0;
};
class Circle {
public:
Circle(Point* center, double radius) : center_(center), radius_(radius) {};
void draw() { std::cout << "drawing Circle(" << center_->x << "/"
<< center_->y << ", " << radius_ << ")" << std::endl; }
void center(Point* c) { center_ = c; } // only works with pointers
private:
Point* center_{nullptr}; // pointer can be reassigned
double radius_{1.0};
};
} // namespace draw
int main(void) {
draw::Point center1{17, -2}; // devs have to ensure `center` stays in scope
draw::Point center2{-18, 12};
draw::Circle c(¢er1, 5);
c.draw();
c.center(¢er2);
c.draw();
}
Composition models a strong "has-a" association between two objects
A "owns" member B:
B does not exist in the system without AMaster *-- Used
---
config:
class:
hideEmptyMembersBox: true
---
classDiagram
Master *-- Used : Master has one or more Used
Directory (master) has
vector<File> (members)Files cannot exist without a containing
Directory
#include <iostream>
namespace draw {
struct Point {
Point(double x, double y) : x(x), y(y) {};
double x = 0.0;
double y = 0.0;
};
class Circle {
public:
Circle(Point center, double radius) : center_(center), radius_(radius) {};
void draw() { std::cout << "drawing Circle(" << center_.x << "/"
<< center_.y << ", " << radius_ << ")" << std::endl; }
private:
// as an alternative a Point* could be used (via new and delete in ctor);
// this would require a dtor and should only be done for large objects
Point center_{0, 0};
double radius_{1.0};
};
} // namespace draw
int main(void) {
draw::Point center{-2, -5};
draw::Circle c(center, 3);
c.draw();
}
| Type | Description |
|---|---|
<|-- | Inheritance |
*-- | Composition |
o-- | Aggregation |
--> | Association |
-- | Link (Solid) |
..> | Dependency |
..|> | Realization |
.. | Link (Dashed) |
classDiagram classA --|> classB : A inherits from B classC --* classD : Composition classE --o classF : Aggregation classG --> classH : Association classI -- classJ : Link(Solid) classK ..> classL : Dependency classM ..|> classN : Realization classO .. classP : Link(Dashed) classQ <--> classR : Association classA: +int boo classA: +foo()
Implement the simplest relationship that meets your program's needs,
not what seems right in real-life.
Model in the easiest way possible with your programming language
Prefer simpler models to real life models (in C++, use composition when possible and aggregation when needed)
Don't over-model class diagrams