I'm beginning to study OOAD and I'm having difficulty finding a C++
code example that'd illustrate how Association
, Aggregation
and Composition
are implemented programmatically. (There are several posts everywhere but they relate to C# or java). I did find an example or two, but they all conflict with my instructor's instructions and I'm confused.
My understanding is that in:
And this is how I've implemented it:
class Bar { Baz baz; }; //ASSOCIATION (with Bar) class Foo { Bar* bar; void setBar(Bar* _bar) { bar = _bar; } }; //AGGREGATION (with Bar) class Foo { Bar* bar; void setBar(Bar* _bar) { bar = new Bar; bar->baz = _bar->baz; } }; //COMPOSTION (with Bar) class Foo { Bar bar; Foo(Baz baz) { bar.baz = baz; } };
Is this correct? If not, then how should it be done instead? It'd be appreciated if you also give me a reference of a code from a book (so that I can discuss with my instructor)
Aggregation is a weak Association. Composition is a strong Association. Aggregation means one object is the owner of another object. Composition means one object is contained in another object. The direction of a relation is a requirement in both Composition and Aggregation.
Definition. Association refers to "has a" relationship between two classes which use each other. Aggregation refers to "has a"+ relationship between two classes where one contains the collection of other class objects.
Association relationship is represented using an arrow. Aggregation relationship is represented by a straight line with an empty diamond at one end. The composition relationship is represented by a straight line with a black diamond at one end. In UML, it can exist between two or more classes.
A composition association relationship represents a whole–part relationship and is a form of aggregation. A composition association relationship specifies that the lifetime of the part classifier is dependent on the lifetime of the whole classifier.
I'm going to ignore Aggregation. It is not a very clearly defined concept and in my opinion it causes more confusion than it is worth. Composition and Association are quite enough, Craig Larman told me so. It might not be the answer your instructor was looking for but it is unlikely to be implemented in C++ any differently to Association anyway.
There is not one way of implementing Composition and Association. How you implement them will depend on your requirements, for example the multiplicity of the relationship.
The simplest way of implementing composition is using a simple Bar
member variable much like you suggested. The only change I would make is to initialize the bar
in the constructor member initializer list:
// COMPOSITION - with simple member variable class Foo { private: Bar bar; public: Foo(int baz) : bar(baz) {} };
It is generally a good idea to initialize member variables using the constructor initialization list, it can be quicker and in some cases like const member variables it is the only way to initialize them.
There might also be a reason to implement composition using a pointer. For example Bar
could be a polymorphic type and you don't know the concrete type at compile time. Or perhaps you want to forward declare Bar
to minimize compilation dependencies (see PIMPL idiom). Or perhaps the multiplicity of this relationship is 1 to 0..1 and you need to be able to have a null Bar
. Of course because this is Composition Foo
should own the Bar
and in the modern world of C++11/C++14 we prefer to use smart pointers instead of owning raw pointers:
// COMPOSITION - with unique_ptr class Foo { private: std::unique_ptr<Bar> bar; public: Foo(int baz) : bar(barFactory(baz)) {} };
I've used std::unique_ptr
here because Foo
is the sole owner of Bar
but you might want to use std::shared_ptr
if some other object needs a std::weak_ptr
to Bar
.
Association would usually be implemented using a pointer as you have done:
// Association - with non-owning raw pointer class Foo { private: Bar* bar; public: void setBar(Bar* b) { bar = b; } };
Of course you need to be confident that Bar
will be alive while Foo
is using it otherwise you have a dangling pointer. If the lifetime of Bar
is less clear then a std::weak_ptr
may be more appropriate:
// Association - with weak pointer class Foo { private: std::weak_ptr<Bar> bar; public: void setBar(std::weak_ptr<Bar> b) { bar = std::move(b); } void useBar() { auto b = bar.lock(); if (b) std::cout << b->baz << "\n"; } };
Now Foo
can use the Bar
without fear of being left with a dangling pointer:
Foo foo; { auto bar = std::make_shared<Bar>(3); foo.setBar(bar); foo.useBar(); // ok } foo.useBar(); // bar has gone but it is ok
In some cases where ownership of Bar
is really unclear an Association could be implemented using a std::shared_ptr
but I think that should be a last resort.
Regarding your implementation of Aggregation with a deep copy of a Bar
pointer. I wouldn't have said that was a typical implementation but, as I said, it depends on your requirements. You do need to make sure you call delete
on your bar
member pointer in the Foo
destructor though otherwise you have a memory leak (or use a smart pointer).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With