Je commence à étudier OOAD et j'ai du mal à trouver un C++
exemple de code qui illustrerait comment Association
, Aggregation
et Composition
sont implémentés par programme. (Il y a plusieurs articles partout mais ils concernent C # ou Java). J'ai trouvé un exemple ou deux, mais ils sont tous en conflit avec les instructions de mon instructeur et je suis confus.
Ma compréhension est que dans:
Et voici comment je l'ai implémenté:
//ASSOCIATION
class Bar
{
Baz baz;
};
class Foo
{
Bar* bar;
void setBar(Bar* _bar)
{
bar=_bar;
}
};
//AGGREGATION
class Bar
{
Baz baz;
};
class Foo
{
Bar* bar;
void setBar(Bar* _bar)
{
bar = new Bar;
bar->baz=_bar->baz;
}
};
//COMPOSTION
class Bar
{
Baz baz;
};
class Foo
{
Bar bar;
Foo(Baz baz)
{
bar.baz=baz;
}
};
Est-ce correct? Sinon, comment faut-il procéder à la place? Ce serait apprécié si vous me fournissez également une référence d'un code d'un livre (afin que je puisse discuter avec mon instructeur)
Je vais ignorer l'agrégation. C'est pas un concept très clairement défini et à mon avis, cela crée plus de confusion qu'il n'en vaut la peine. La composition et l'association suffisent, Craig Larman me l'a dit. Ce n'est peut-être pas la réponse que votre instructeur recherchait, mais il est peu probable qu'elle soit implémentée différemment en C++.
Il n'y a pas une façon de mettre en œuvre la composition et l'association. La façon dont vous les mettrez en œuvre dépendra de vos besoins, par exemple la multiplicité de la relation.
La manière la plus simple d'implémenter la composition est d'utiliser une simple variable membre Bar
tout comme vous l'avez suggéré. La seule modification que j'apporterais serait d'initialiser le bar
dans la liste d'initialisation des membres constructeurs:
// COMPOSITION - with simple member variable
class Foo {
private:
Bar bar;
public:
Foo(int baz) : bar(baz) {}
};
C'est généralement une bonne idée d'initialiser les variables membres en utilisant la liste d'initialisation du constructeur, cela peut être plus rapide et dans certains cas, comme les variables membres const, c'est le seul moyen de les initialiser.
Il peut également y avoir une raison pour implémenter la composition à l'aide d'un pointeur. Par exemple, Bar
pourrait être un type polymorphe et vous ne connaissez pas le type concret au moment de la compilation. Ou peut-être que vous souhaitez transmettre déclarer Bar
pour minimiser les dépendances de compilation (voir idiome PIMPL ). Ou peut-être que la multiplicité de cette relation est de 1 à 0..1 et vous devez pouvoir avoir un _ Bar
nul. Bien sûr, car il s'agit de Composition Foo
devrait posséder le Bar
et dans le monde moderne de C++ 11/C++ 14, nous préférons utiliser des pointeurs intelligents au lieu de posséder des pointeurs bruts:
// COMPOSITION - with unique_ptr
class Foo {
private:
std::unique_ptr<Bar> bar;
public:
Foo(int baz) : bar(barFactory(baz)) {}
};
J'ai utilisé std::unique_ptr
ici car Foo
est l'unique propriétaire de Bar
mais vous voudrez peut-être utiliser std::shared_ptr
si un autre objet a besoin d'un std::weak_ptr
à Bar
.
L'association est généralement implémentée à l'aide d'un pointeur comme vous l'avez fait:
// Association - with non-owning raw pointer
class Foo {
private:
Bar* bar;
public:
void setBar(Bar* b) { bar = b; }
};
Bien sûr, vous devez être sûr que Bar
sera vivant pendant que Foo
l'utilisera sinon vous avez un pointeur pendant. Si la durée de vie de Bar
est moins claire, alors un std::weak_ptr
peut être plus approprié:
// 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";
}
};
Maintenant, Foo
peut utiliser le Bar
sans craindre de se retrouver avec un pointeur suspendu:
Foo foo;
{
auto bar = std::make_shared<Bar>(3);
foo.setBar(bar);
foo.useBar(); // ok
}
foo.useBar(); // bar has gone but it is ok
Dans certains cas, lorsque la propriété de Bar
n'est vraiment pas claire, une association pourrait être implémentée à l'aide d'un std::shared_ptr
mais je pense que cela devrait être un dernier recours.
Concernant votre implémentation de l'agrégation avec une copie complète d'un pointeur Bar
. Je n'aurais pas dit que c'était une implémentation typique mais, comme je l'ai dit, cela dépend de vos besoins. Vous devez vous assurer d'appeler delete
sur votre pointeur de membre bar
dans le destructeur Foo
, sinon vous avez une fuite de mémoire (ou utilisez un pointeur intelligent).