Citation:
Envoyé par
foetus
Je ne suis pas d'accord :mrgreen:
Lorsque je parle de "god object" (peut-être à tord) c'est l'objet principal que certaines bibliothèques (surtout IHM comme Qt, VCL) ont besoin pour lancer l'application (et initialiser la fenêtre principale).
Et je ne parle pas d'héritage, polymorphisme, héritage multiple, ou autre ... juste de l'utilisation de cette classe principale (*).
Ah, ben oui, tu as effectivement une classe (QApplication, dans le cas de Qt) qui va mettre tout le système en branle et s'assurer que tout sera correctement initialisé. Ce n'est pas une raison (et c'est l'un des reproche que je fais à Qt) pour connaitre cette classe pour pouvoir accéder aux différents services quelle regroupe. D'autant plus que les services en question sont TOUS modélisés sous la forme d'une classe spécifique.
Ceci dit, le domaine de l'IHM est -- justement -- le contre exemple flagrant de ce qu'il faut faire à bien des égards ;)
Citation:
Donc je trouve "étrange" que pour respecter les principes SOLID SRP OCP ou autres, on ne doit pas profiter de cette grosse classe (et de sa commodité).
Surtout qu'un de ses rôles c'est l'initialisation au lancement (entre autres comme thread d'affichage ou les gros traits/ lignes de la logique IHM)
Oui, bien sur : il est important de veiller à ce que tout ce qui doit être initialisé pour fournir les services que l'on attend de la part d'une IHM soit initialisé de manière simple (ex, comme c'est le cas de Qt, en ayant une classe spécifique dont l'instanciation s'occupera de créer tous les éléments nécessaires pour rendre ces services)
Citation:
Les managers sont d'ailleurs essentiellement initialisés au démarrage.
(je passe sur le problème qu'il peut y avoir à parler de "managers" ou de "gestionnaires"... je reviendrai dessus à la fin de mon intervention ;) )
Oui, et alors :question: Qu'est ce qui t'empêche d'avoir une classe proche de
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class MaClasseEntreeApplication{
/* ... */
private:
/* juste au cas où on voudrait l'instancier deux fois */
static bool alreadyCreated_;
ThreadStack threads_;
EventHandlerCollection events_;
/* ... */
};
/* pour ne pas devoir passer par MaClasseEntreeApplication */
EventHanderlCollection & events(){
/* peu importe comment on le récupère */
}
ThreadStack threads(){
/* peu importe comment on le récupère */
}
.... someStuffWeWantToGet() {
/* ... */
} |
La loi de Déméter dit que, si un objet de type A manipule en interne un objet de type B, l'utilisateur du type A ne doit pas avoir connaissance du type B pour manipuler son objet de type A.
Mais il ne faut pas non (et encore moins) plus obliger l'utilisateur du type B à connaitre le type A pour pouvoir accéder à son type B.
La plupart des bibliothèques d'IHM sont apparues à une époque où
- la philosophie était proche du "tout objet" (comprend : fonctions membres uniquement), en ignorant volontairement les possibilités multi paradigme du C++
- l'approche du paradigme orienté objet était encore bien plus orientée sur les données manipulées que sur les services rendus par les différentes fonctionnalités (il suffit de voir le nombre de mutateurs pour s'en convaincre)
Alors, bien sur, dans un tel contexte, il devient sans doute impossible de se passer de la notion de singleton. Mais, si tu accepte l'idée de créer des fonctions libres et que tu commences réellement à penser en termes de services et non en termes de données manipulées, tu te rendras compte qu'il n'y a rien qui soit fournit par ton singleton qui doive effectivement être disponible strictement partout et que, si tu as besoin de certaines fonctionnalités proposées par ton singleton à plusieurs endroits, tu n'as absolument aucun besoin de pouvoir accéder à l'ensemble de ces fonctionnalités.
Dés lors, si tu sépare l'ensemble des fonctionnalités proposées par ton singleton en autant de fonctionnalités distinctes et indépendantes, tu gagne la liberté de ne fournir que ce dont tu as réellement besoin à chaque moment et, surtout, tu gagne à ne pas être tenté de rajouter une dépendance dont tu aurais pu te passer.
Citation:
Et lorsque tu parles de MVC, je voulais te faire remarquer que cette grosse classe peut faire office de contrôleur et ton manager de modèle.
Après, pour différentes raisons (temps de démarrage, maintenance, évolution...), tu peux le faire évoluer en un vrai MVC.
La question que je te poserai à ce sujet est : pourquoi attendre pour décider d'utiliser quelque chose dont tu as la garantie que tu devra de toutes manières y avoir recours :question:
Il sera beaucoup plus facile pour toi de mettre directement un MVC correct en route que de commencer en regroupant les fonctionnalités propres au(x) contrôleur(s) et celles propres au(x différents) moèle(s) et de devoir séparer le tout "après coup" parce que tu te rend compte que cela devient ingérable ;)
Citation:
J'ai dû mal à voir un projet/ application avec lequel/ laquelle toutes les classes ont très peu de dépendances :whistle:
Déjà la classe principale (*) est un contre-exemple.
Ou alors un petit projet, une petite application.
Tu n'as alors jamais vu les projets sur lesquels j'ai travaillé...
Certains étaient de très gros projets, et on limitait pourtant les dépendances au stricte minimum. Et il y a parfaitement moyen de le faire ;)
Citation:
Parce que c'est bien joli d'avoir une armée de classes à 1 responsabilité, de pouvoir les tester individuellement (**), etc..., mais après il faut mettre en place tout le ciment pour créer/ détruire et aussi passer le bon objet. Sans parler de ton projet qui explose en terme de classes/ fichiers.
Le ciment à mettre en place pour gérer cette armada de petite classe est beaucoup plus simple que celui nécessaire pour éviter de faire tomber tout un tas de classes monolithiques, je peux te l'assurer ;)
Citation:
Ton exemple de classes: TextureFactory, TextureLoader, TextureHolder, SoundFactory, SoundLoader, SoundHolder, ... cela fait un peu peur :? :?
Pourquoi est ce qu'il te fait peur :question:
Citation:
Une mise en situation: tu codes un jeu et tu dois prendre en compte 3 types de textures (png, DXT je-ne-sais-pas-quoi, et autre chose) et 2 types de sons.
Au lieu d'avoir 5 grosses classes + 1 manager "classe déléguée" (mais il y a sûrement d'autres façons de coder) (et je signale au passage que si tu cherches un algo par rapport au mp3 ou au png, tu sais exactement où il est) tu vas avoir (3xX + 2xY) classes.
Sans parler du fait que rien pour gérer tout cela tu vas mettre en place de l'héritage (certes simpliste), mais un héritage quand même. Donc (3xX + X + 2xY + Y) classes
J'aurais surement un nombre de classes plus grand que 1 ou 2, ca je te l'accorde mais :
1 - Très peu d'héritage:
- peut être pour faire la distinction entre les textures "données" (issues de formats comme png ou jpeg) est les textures "fonctionnelles",
- peut être pour faire pareil au niveau de Sound (mais aucun lien entre Sound et Texture);
- Peut-être, au niveau des loader permettant de charger différents formats de textures ou différents formats de sons (mais, encore une fois, sans liens entre les SoundLoader et les TextureLoader, autres que leur interface, peut être)
2- J'aurais, effectivement, une TextureFactory et une SoundFactory qui me permettraient respectivement de "centraliser" la connaissances des différents types dérivés, ce qui me permettrait de n'avoir pas besoin de savoir autre chose que le fait que je manipule une texture ou un son partout ailleurs
3- j'aurais effectivement un TextureHolder et un SoundHolder, qui se contenteraient de maintenir les textures (respectivement les sons) en mémoire "tant qu'ils sont utilisés"
Citation:
Je prévois déjà que tu vas me répondre que ton IDE c'est le plus puissant de la terre (VIM, Visual, ...)
A vrai dire, à titre perso, je code généralement avec un truc aussi simple que gedit (notepad++) et en utilisant les autotools sous linux... On peut trouver assez facilement des solutions "plus puissantes" pour faire le travail ;)
Et pourtant, le nombre de fichiers / de classes ne me fait absolument pas peur ;). Par contre, ce qui m'énerve à chaque fois, c'est de voir une série de tests unitaires planter parce que j'ai eu "le malheur" de vouloir intégrer une évolution à mon projet ;)
Citation:
que tu t'en fiches tu gères X projets en même temps (donc tu n'es pas à quelques classes en plus)
J'ai effectivement, plusieurs projets perso en cours...
Mais ce n'est pas pour cela que je ne suis pas à "quelques classes de plus"... Si je ne suis pas à "quelque classes de plus", c'est parce que j'ai eu très largement l'occasion de me rendre compte de tous le bénéfice que l'on peut tirer à effectivement séparer clairement les différentes fonctionnalités ;)
Citation:
et que je n'ai jamais codé sur/ maintenu un gros projet monolithique.
Je ne te connais pas assez pour dire cela ;)
Citation:
Et je peux rajouter que je code que des applications pas des bibliothèques mais avec de la conception pour reprendre et réutiliser "une colonne vertébrale".
Et, si je ne veux récupérer qu'une vertèbre :question: je fais comment :question: je prend toute la colonne vertébrale, ou j'arrive à ne prendre que L4 sans trop me casser la tête :question:
Citation:
Évidement si tu ne te contrains pas par toi-même, tu vas avoir une armée de managers :mrgreen: :mrgreen:
On arrive bientot au bout... les explications vont venir ;)
Citation:
> les descriptions d'objets devrant être affichés (les points qui les composent et les vertices qui relient ces point) sont aussi des ressources :
Pourquoi des ressources? :koi: :koi: Ce sont en règle générale des classes avec une logique ultra-ultra réduite. Tellement réduite que tu peux faire une classe=une entête seulement.
Je voulais parler des données destinées à être envoyées à OpenGL que tu récupères en lisant un fichier .obj ou 3ds ;)
Citation:
Par exemple: un Point 2D: x, y - un élément dans une liste plate (
listview par exemple): texte ID - un utilisateur: ID, nom, prénom, société.
> une base de donnée, pareil
C'est ce que j'appelle des "
App Datas"
Pour moi, il y a 2 types de "
App Datas"
- Beaucoup d'attributs, quasi aucune logique. Un "gros panier" (très souvent en lecture seule) qu'on passe d'un objet à un autre.
- Très peu d'attributs, mais beaucoup de logique. Par exemple: une base de donnée. Tu vas avoir 2-3 "handles" et à côté toutes tes requêtes SQL (et éventuellement des procédures stockées et des triggers).
Tout dépend de la définition que tu fais du terme "ressources".
Le vrai problème, c'est que même si tu te limite aux seules "ressources informatiques" (en générale), la définition de ce terme est proche (selon wikipedia) de
Citation:
Envoyé par wikipedia
En informatique, les ressources sont des composants, matériels ou logiciels, connectés à un ordinateur. Tout composant de système interne est une ressource. Les ressources d'un système virtuel incluent les fichiers, les connexions au réseau, et les zones de mémoire.
En cela, ce que tu appelle App Datas ne sont rien d'autre que... des ressources ;)
Citation:
> Et le pire de tout, c'est sans doute la configuration
Pour moi, la configuration ce sont 2 méthodes load/ save + une structure :mrgreen:
Même une configuration comme Apache je ne pense pas que ce soit différent.
Non, les fonctions load et save ne sont que les parties visibles de l'iceberg...
La partie non visible, c'est que, même s'il ne s'agit que d'une structure "POD", c'est bel et bien une ressource (quelque chose qui utilise de la mémoire) et qui utilisé quasiment partout dans le code, parce que l'on y mélange allègrement les données de configuration de tout et de n'importe quoi (ex : taille et résolution d'affichage, volumes sonores divers, adresses des serveurs à contacter, nom d'utilisateur, ...)
Citation:
Et comme tu ne sauvegardes pas ta configuration souvent, ta grosse classe(*) peut la gérer toute seule.
Je ne dis pas que la classe qui sert de point d'entrée de l'application ne doit pas gérer ta configuration d'une manière ou d'une autre; je dis:
- que tu ne devrais pas avoir besoin de passer par ta classe "point d'entrée de l'applicaiton" pour accéder à la configuration
- que tu n'a jamais besoin de toutes les données que la configuration met à ta disposition :
- si tu est dans un contexte d'affichage, c'est la taille et la résolution de l'écran qui t'intéressent;
- si tu est dans un contexte de requête SQL / connexion à un serveur, ce sont les adresses du serveur, le nom d'utilisateur,... qui t'intéressent;
- si tu es dans un contexte sonore, ce sont les volumes sonores qui t'intéressent
- ...
Citation:
Surtout que la configuration est chargée au lancement.
Oui, elle sera sans doute chargée au lancement de l'application et sauvegardée (si modifiée) à la fermeture de l'application.
Citation:
Et au passage, tu n'as pas de Configuration Manager en mémoire pour rien.
Ben, je dis justement qu'on n'en a pas besoin... En tout sous la forme d'un "gestionnaire de configuration", et surtout pas si cela sous entend de maintenir la configuration des différents "modules" de notre application "mélangés" au sein d'une seule structure.
Je suis tout à fait d'accord pour dire qu'il doit "forcément" y avoir un endroit où l'on regroupera toutes les configurations de tous les modules (ne serait-ce que parce que la configuration de tous les modules peut se retrouver dans un seul et unique fichier de configuration).
Mais on n'a absolument aucun besoin de savoir comment sont traités les sons quand on essaye de se connecter à un serveur distant!
[/QUOTE]
C'est sûr que si tu fais un manager sans réfléchir cela va être compliqué par la suite :mrgreen:
Pour moi un manager c'est une "classe déléguée" vers des objets concrets qu'il contient. Une sorte de proxy, interface, ou patron de conception du même style.
Mais, ce que je fais, (<- Un gros mais), ton manager expose que des méthodes classiques et/ ou "en lecture seule".
Pour un Texture Manager: get_texture/ list_textures.
Pour un Sound Manager: get_sound/ list_sounds.
Ainsi, tu peux facilement échanger les classes concrètes: MP3_Sound_Manager, WAV_Sound_Manager.
[/QUOTE]Que le son soit au format mp3, au format wav ou au format whatTheFuck, tu t'en fous royalement... tout ce qui t'importe, c'est de pouvoir le jouer ;)
Et c'est justement parce que tu espère pouvoir profiter des comportements polymorphes pour tes sons ou tes textures (text.drawIt(context); pour une texture; sound.playIt(context); pour un son) qu'il est important de limiter la tentation de l'utilisateur de "celui qui récupère une texture / un son" de chercher à savoir "quel en est le type réel".
Et c'est pour cela qu'il est important de séparer clairement les différentes responsabilités (qui crée la texture ou le son, qui la/le charge, qui la/le maintient en mémoire), de manière à ce que l'utilisateur puisse recevoir une "référence" (qui sera en réalité un pointeur ou une référence C++) sur "une texture" ou sur "un son" et pour qu'il puisse savoir qu'il "doit faire avec", sans essayer de récupérer le "type réel" de sa texture ou de son son.
Citation:
Et à côté de cela, prévoir un système via ton Manager (***) pour récupérer un objet concret et avoir une interface spécifique étendue:
MP3_Sound_Manager::create_sound(), JPG_Texture_Manager::set_compression_level(), ...
Mais ca, c'est pas le gestionnaire qui doit s'en occuper...
la création d'un son est un mécanisme dont l'utilisateur n'a absolument rien à foutre et qui devrait être délégué à une fabrique (qui se décharge d'une partie du travail sur différents Loaders) et réglage du niveau de compression JPEG, ca se gère exclusivement au niveau de la sérialisation (chargement /sauvegarde) du fichier.
Alors, que tu aies une fonction add(filename) qui passe par la fabrique pour ajouter une texture ou un son (selon ce que tu manipules) est tout à fait cohérent, mais l'utilisateur de ce qui devient un TextureHolder ou un SoundHolder (une classe qui se contente de fournir les services relatifs au maintient des sons/des textures en mémoire) n'a absolument pas besoin de savoir comment le son / la texture ajoutée a été créée (et au paravant chargée).
Citation:
D'ailleurs, je ne pense pas que ces méthodes spécifiques soient utiles partout :mrgreen: :mrgreen:. Juste à des endroits précis.
Raison de plus pour ne pas les rendre accessibles partout parce que disponibles dans l'interface d'une classe qui, par nature, risque de se retrouver "n'importe où"...
Citation:
Toujours le même principe :whistle:: cela ne me dérange pas que ma classe Manager soit utilisée à pleins d'endroits.
Parce que 0) c'est un peu son rôle (comme un frigo dans une cuisine) 1) j'ai contrains un grand nombre de ses méthodes en lecture seule 2) effet "bottle neck" avec un point d'arrêt dans le manager 3) avec une recherche, je trouve toutes les occurrences
Tu obtiens exactement le même résultat mais en mieux en séparant ton manager "monolithique" en différentes classes aux responsabilités restreintes car:- C'est le role d'un TextureHolder de maintenir les textures en mémoire tant que tu en as besoin (mais c'est aussi celui d'un SoundHolder, sauf que tu n'auras que des sons ou des textures ;), un peu comme un frigo dans une cuisine
- tu mes un points d'arrêt sur un TextureHolder et tu es sur que chaque fois que tu passe sur ce point d'arrêt, c'est parce que tu manipule une texture. Même chose pour les sons et ton SoundHolder, et pour n'importe quel autre type de ressource
- la recherche de ton instance de TextureHolder ou de ton instance de SoundHolder est beaucoup plus précise que celle de ton Manager car tu n'en a rien à foutre des circonstances dans lesquelles tu manipules un son si tu cherche celles où tu manipule une texture (et vice versa)
Désolé, pour finir, je ferai mon spitch sur les "manager" demain ;)