Dans la définition de la classe
Après la définition de la classe
Ça dépend…
Surtout pas dans le fichier d'en-tête !!!
C'est inutile, un typedef
A méditer: La solution la plus simple est toujours la moins compliquée
Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
Compiler Gcc sous windows avec MinGW
Coder efficacement en C++ : dans les bacs le 17 février 2014
mon tout nouveau blog
Ah là là…
Franchement tu me déçois…
Blague à part, je me rends compte que j'ai une fois de plus oublié ce fichu principe de responsabilité unique.
J'ai beau en entendre parler régulièrement, j'ai encore du mal à l'appliquer.
Sinon, dans l'exemple de koala01, je n'ai pas compris pourquoi à la fois les classes « Fabrique » et « Manager » possèdent un conteneur des objets « Base »…
Ça fait double emploi, non ?
Et puis ça rajoute une responsabilité à la fabrique, non ?
Revenons sur mon exemple « conceptuellement insensé »…
On dispose d'une hiérarchie de classes (ou d'une classe seule) qui ont une sémantique de valeur.
Mais, pour des raisons que je ne vais pas exposer, on va leur donner une sémantique d'entité ; ceci permettra entres autres de faire des comparaisons sur des adresses plutôt que sur des objets.
Du coup, on ne peut plus laisser l'utilisateur créer des objets comme il le veut (ni les détruire).
Il nous faut donc une interface qui, en fonction des paramètres qu'on lui passe, crée l'objet correspondant s'il n'existe pas déjà, et retourne l'adresse de l'unique instance.
Une sorte de mix entre une « fabrique » et un « singleton », si l'on veut…
Bien entendu, la manière de gérer tout ça doit être transparente pour l'utilisateur.
Alors si j'ai bien compris ce que vous m'avez dit, je devrais faire quelque chose comme ce qui suit (sur le principe) ?
En passant, c'est indispensable que la classe de base soit abstraite ?
Sinon, les classes internes sont-elles susceptibles de remettre en question le principe de responsabilité unique ?
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127 struct deleter { void operator () (void* p) { delete p; } }; // struct deleter //*****************************************************// class base { friend class base_generator; protected: base(...) {...} virtual ~base() {...} private: base(const base&); base& operator = (const base&); (...) }; // class base class base_generator { friend class base_manager; private: static base* create(...) { (...) return new base(...); } base_generator(); base_generator(const base_generator&); ~base_generator(); base_generator& operator = (base_generator&); }; // class base_generator class base_manager { private: typedef std::container<base*> container; typedef container::iterator iterator; static base_manager collection_; container instances_; static void insert(base* obj) { return collection_.insert(obj); } public: static bool contains(...) { return __contains__(collection_, ...); } static base* find(...) { return __find__(collection_, ...); } static void clear() { return collection_.clear(); } static base* instance(...) { if contains(...) return find(...); base* tmp = base_generator::create(...); insert(tmp); return tmp; } private: base_manager() : instances_() {} ~base_manager() { clear(); } base_manager(const base_manager&); base_manager& operator = (const base_manager&); iterator begin() { return instances_.begin(); } iterator end() { return instances_.end(); } void insert(base* obj) { instances_.__insert__(obj); } bool contains(...) { return __contains__(instances_, ...); } base* find(...) { return __find__(instances_, ...); } void clear() { std::for_each(begin(), end(), deleter()); return instances_.clear(); } }; // class base_manager
C'est vrai que si on modifie la classe A::B, en quelque sorte on modifie également (indirectement) la classe A, mais pour autant les modifications de A::B n'ont a priori aucune répercussion sur A.
Je me trompe peut-être, mais j'ai tendance à voir la classe A comme l'espace de nom de la classe A::B, si ce n'est qu'il est possible de définir sa visibilité hors de cet espace de nom.
Je demandais ça pour savoir s'il y a un moyen de ne pas exposer « base_generator » et « base_manager ».
La question est de savoir si ces classes méritent d'être définies de cette manière. Mieux, la véritable question sous-jacente est : qu'est-ce qu'un manager ?
D'après mes réflexions propres, c'est une entité qui n'a pas de raison d'exister. Si j'étais un Grand Sage, je vous dirais de me prouver que j'ai raison. Pour cela, vous allez devoir trouver des arguments solide qui étayent cette thèse, en mixant les approches sémantiques, comportementales et tout ça.
Je ne suis pas un Grand Sage, mais c'est avec plaisir que je vous verrais vous plier à cet exercice (peut-être dans un autre thread).
[FAQ des forums][FAQ Développement 2D, 3D et Jeux][Si vous ne savez pas ou vous en êtes...]
Essayez d'écrire clairement (c'est à dire avec des mots français complets). SMS est votre ennemi.
Evitez les arguments inutiles - DirectMachin vs. OpenTruc ou G++ vs. Café. C'est dépassé tout ça.
Et si vous êtes sages, vous aurez peut être vous aussi la chance de passer à la télé. Ou pas.
Ce site contient un forum d'entraide gratuit. Il ne s'use que si l'on ne s'en sert pas.
Pas vraiment:
La (seule) responsabilité de la fabrique est (ou du moins devrait être)... de construire des objets, et ce devrait être la seule classe susceptible de le faire.
Tu peux envisager d'avoir une fonction proche de
Le problème, c'est que cela devient rapidement ingérable.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8 Base* Fabrique::create(std::string const & str) { if(str=="derivee 1") return new Derivee1; if(str=="derivee 2") return new Derivee2; /*...*/ }
L'idée est donc de rendre tes objets "clonable" (raison de la fonction virtuelle clone), et de gérer une collection d'objet que l'on peut cloner.
L'idéal est donc d'utiliser une std::map<string, Base*> dans laquelle nous enregistrerons la pair "nom de l'objet"/"pointeur sur l'objet à cloner" qui nous permet d'appliquer une logique simple ressemblant à
Note que, s'il est possible de trouver un moyen d'identifier le type d'objet à créer de manière unique, n'importe quelle autre collection pour laquelle il est possible d'implémenter un find sur ce type d'information pourrait parfaitement convenir (c'est la raison pour laquelle j'ai utilisé some_collection )
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8 Base * Fabrique::create(std::string const & str) { const_iterator it=items.find(str); // recherche de l'élément grace à son nom if(it==items.end()) // si l'objet n'est pas enregistré return NULL; //on pourrait lancer une exception, ou que sais-je //sinon on renvoie l'objet cloné return it->second->clone(); }
La seule reposnabilité du gestionnaire d'objet est, quand à lui, de gérer les différents objets créés au fur et à mesure de l'exécution de l'application.
L'un des services que l'on est en droit d'attendre de la part du gestionnaire dans le cadre de cette responsabilité, c'est de veiller à la destruction correcte des objets "en temps utiles".
Mis à part pour la fabrique (qui doit pouvoir, au moment de sa destruction, détruire les éléments qu'elle contient), le gestionnaire devrait en outre être le seul type susceptible de détruire les différents éléments dont on a demandé la création à la fabrique.
Mon code manque donc sans doute de précision, dans le sens où il est tout à fait possible:
- que le constructeur de la classe de base soit protégé (pour que seules les classes dérivée puissent y accéder)
- que le destructeur de la classe de base soit virtuel et protégé, pour les même raisons
- que le destructeur des classes dérivées soit privé, pour éviter à l'utilisateur la tentation de l'invoquer
- que la classe Fabrique soit déclarée amie de toutes les classes dérivées (pour lui permettre de construire l'objet lors de l'enregistrement)
- que la classe Gestionnaire soit déclarée amie de la classe de base (pour lui permettre d'invoquer le destructeur et donc de détruire tous les objets par polymorphisme)
Absolument pas, cela ne fait que lui donner les moyens de remplir sa propre responsabilité, en évitant d'avoir trop de code à modifier en cas d'évolutionEt puis ça rajoute une responsabilité à la fabrique, non ?
A méditer: La solution la plus simple est toujours la moins compliquée
Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
Compiler Gcc sous windows avec MinGW
Coder efficacement en C++ : dans les bacs le 17 février 2014
mon tout nouveau blog
Ça va être rapide : « C'est koala01 qu'a commencé ! »
Et puis, juste après quelqu'un (je ne sais plus exactement qui, d'ailleurs… ) a surenchéri en disant qu'il fallait séparer les différentes fonctionnalité attendues.
Je ne fais que reprendre des idées qui m'ont été suggérées ici-même…
@koala01 Ok, j'ai bien compris ton exemple à présent.
Merci.
Ah, non...
Je n'ai utilisé le terme manager que parce qu'il fallait que je trouve un nom pour une classe, et que LeConteneurDeMaClass faisait trop long
De plus, j'ai toujours été bien clair sur le fait qu'il est plus que nécessaire de séparer la responsabilité de la gestion de la durée de vie des différentes instances d'une classe (même s'il faut s'assurer qu'il n'y ait qu'une seule instance... le meilleur moyen est encore de... faire en sorte qu'une seule instance soit créée ) et la responsabilité que l'on veut donner à cette clase .
Je n'admets qu'une exception: le DP composite, parce que, effectivement, le noeud doit pouvoir contenir plusieurs objet du type de base (les noeuds et les feuilles étant potentiellement mélangées ).
Absolument pas...Je ne fais que reprendre des idées qui m'ont été suggérées ici-même…
Tous les exemples tendent à t'inciter à séparer clairement les différentes responsabilités.
Or, ce que toi tu propose, c'est de mixer les différentes classes de manière horribles.
Éventuellement, nous pourrions (bien que ce soit déjà un peu limite, mais malgré tout justifiable) envisager de mixer la fabrique avec le conteneur d'objets (pour éviter le terme "manager"), en cachant le fait que ce dernier agit comme une fabrique car nous pourrions considérer que le coté "fabrique" serait en fait un détail d'implémentation du contneur lui permettant d'obtenir de nouveaux objets .
A méditer: La solution la plus simple est toujours la moins compliquée
Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
Compiler Gcc sous windows avec MinGW
Coder efficacement en C++ : dans les bacs le 17 février 2014
mon tout nouveau blog
Alors là, je ne comprends plus rien.
On me rappelle que chaque classe doit avoir une et une seule responsabilité, alors j'essaie de séparer mon problème :
- une classe pour gérer les propriétés des objets ;
- une classe pour « générer » les objets adéquats ;
- une classe pour gérer la durée de vie des objets.
Ensuite on me dit que la dernière classe n'a pas de raison d'être, puis que je ne tiens pas compte des conseils qu'on me donne et que je cherche simplement à faire une bouillie immonde de toutes les parties du problème.
Où ai-je raté des épisodes ?
Et combien ?
À part ça, je ne vois pas pourquoi l'utilisateur devrait avoir connaissance de toutes ces classes, en particulier des deux dernières.
Il a seulement besoin d'avoir accès à une fonction qui, en fonction des paramètres qu'il lui fournit, lui renvoie un objet adéquat, peu importe s'il existait déjà auparavant ou s'il a été créé par suite à l'appel de cette fonction, ainsi qu'aux propriétés de cet objet.
Je cherche simplement un moyen de mettre ceci en œuvre.
Est-ce un mal ?
[FAQ des forums][FAQ Développement 2D, 3D et Jeux][Si vous ne savez pas ou vous en êtes...]
Essayez d'écrire clairement (c'est à dire avec des mots français complets). SMS est votre ennemi.
Evitez les arguments inutiles - DirectMachin vs. OpenTruc ou G++ vs. Café. C'est dépassé tout ça.
Et si vous êtes sages, vous aurez peut être vous aussi la chance de passer à la télé. Ou pas.
Ce site contient un forum d'entraide gratuit. Il ne s'use que si l'on ne s'en sert pas.
Peut-être que « manager » n'est pas approprié.
C'est bien ce que je m'étais dit quand j'ai vu ce nom, mais il me paraissait pas trop mal convenir, alors je l'ai repris.
Serait-ce plus exact de choisir un nom comme « gestionnaire » ?
Ah non, c'est la traduction de « manager »…
Bon, et si on repart sur « stockeur », « conteneur », « collection », « liste », etc., ça te conviendrait mieux ?
Vous avez un bloqueur de publicités installé.
Le Club Developpez.com n'affiche que des publicités IT, discrètes et non intrusives.
Afin que nous puissions continuer à vous fournir gratuitement du contenu de qualité, merci de nous soutenir en désactivant votre bloqueur de publicités sur Developpez.com.
Partager