Mon problème est simple :
J'ai 3 classes static A, B et C.
A include B qui include C et j'aimerais que dans le code de A je ne puisse pas appeler C::foo().
Comment faire ?
Est-ce que l'utilisation de namespace est une bonne solution ?
Version imprimable
Mon problème est simple :
J'ai 3 classes static A, B et C.
A include B qui include C et j'aimerais que dans le code de A je ne puisse pas appeler C::foo().
Comment faire ?
Est-ce que l'utilisation de namespace est une bonne solution ?
- 3 classes static ? :weird:
- si A inclue B qui inclue C, A inclue C
- si A inclue C, A a accès à C
- les namespace sont là pour délimiter/ranger le code
Ton truc ressemble plus à un foutoir d'architecture.
- pourquoi A inclue B ?
- pourquoi B inclue C ?
- si A manipule un B, il n'en a rien à faire de C
- que B manipule un C en interne, n'intéresse pas A
- que fait C::foo public s'il doit pas être appelé ?
- pourquoi ne pas simplement éviter d'appeler C::foo ?
Bah C::foo() est en public pour que B puisse y accéder.
En gros C c'est ma classe Database, A c'est ma classe Algorithm et B ma class Database_manager.
Avant ma classe Algothim utilisait directement ma Database::write() mais ce n'est pas bon et c'est pour cela que je passe par ma classe Database_manager pour évite les appels à la base de données n'importe comment.
Mais là, vu que Database_manager include Database, je peux toujours directement appeler les fonctions de Database dans la classe Algothim et je voudrais que cela ne soit pas possible.
Toi, t'as pas vécu les années Dorothée et la "Force de l'amitié". :aie::ptdr:Citation:
Bah C::foo() est en public pour que B puisse y accéder.
Déjà, si tout n'était pas statique, t'aurais beaucoup moins problèmes.
Si C n'est pas "statique", et bin A pourrait se brosser pour accéder au membre "C" de la classe "B".
"Database_manager", c'est un nom pourri, ça veut rien dire.
Utilisez des noms corrects, ça simplifiera grandement la maintenance.
Si vous faites une "vraie" DAL, vous faites ça dans une librairie à part, donc avec un seul .h public qui ne déclare que les choses utilisables par le code client de la librairie.
Le problème n'est pas technique, mais c'est un problème de conception/architecture.
Tu veux pas plutôt dire DLL ? Et puis de toute façon je vais pas créer plusieurs libraire alors qu'il s'agit du même projet avec du code non-réutilisable.Citation:
Envoyé par bacelar
J'ai pas besoin de créer d'objet donc pourquoi ne pas même en static ? Je vais pas mettre des singletons partout ! Et puis un singleton ne règlerais pas le problème car je pourrais quand même faire C::instance()->foo()Citation:
Envoyé par bacelar
Il s'agit de ma classe qui s'occupe d'effectuer les requête SQL grâce à la classe Database et de retourné directement les variables ou objets au reste du code. Je savais pas comment la nommé.Citation:
Envoyé par bacelar
Mais partout ou j'ai accès à Database_manager j'ai aussi accès à Database et c'est pas bon.
Bah, je vais créer un namespace private_database que je n'appellerais que dans database_manager et ça sera très bien comme ça :mrgreen:
https://en.wikipedia.org/wiki/Data_access_layer
Et tu pourras toujours faire private_database::C::foo() donc déplacer le pb te suffit ? :weird:
Pas besoin de créer un objet database ? Quid de la connection ? T'en crées une nouvelle pour chaque requête ? :calim2:
Pourquoi pas faire une DAL dans une DLL, cela renforcera le réutilibilité, la facilité d'évolution et de testabilité.
Vous pouvez aussi en faire une simple LIB pour la réutilibilité.
Avec cette attitude, vous ne réutiliserez jamais rien.
Si vous n'êtes pas capable de le faire dans des cas simples, n'essayez même pas dans des "vrais cas".
Vous faites une usine à gaz (des static de classe dans tout les coins) pour faire "comme les vrais" mais quand on dit que c'est pas comme ça, vous dite, "je vais pas me prendre la tête, je garde mon usine à gaz", c'est débile, supprimez directement cette usine à gaz.
Pas besoin de faire des statiques/globales déguisées, quand un simple champ dans une classe fait largement l'affaire.
Les singletons, c'est pas pour faire des globales déguisées à la con, si pas besoin, on n'utilise pas. Et on en a rarement "juste besoin", c'est toute une architecture (IoC, mocking, etc...) qui peuvent ÉVENTUELLEMENT en avoir besoin.
C'est un argumentaire "homme de paille", faudrait pas nous prendre pour des perdreaux de l'année.
C'est pas clair, et ça n'a rien à voir avec une "gestion" de base de données.
Quand une classe n'a pas de rôle PRÉCIS, elle dégage.
Preuve que vous n'avez pas assez réfléchi votre conception.
Vous avez accès à "Database" parce que "Database" est publiquement déclarer dans un .h, c'est pas arrivé par hasard, bordel.
Si vous n'en voulez pas, comment se fait-il que vous avez sciemment foutu cette classe dans un .h qui est là pour ne donner que le strict nécessaire.
C'est ubuesque !!!
C'est bien, les namespaces sont là pour structurer le code, mais faudrait quand même réfléchir 2 minutes à l'ensemble des namespaces, à leur fonction, et donc à leur noms.
Parce que "private_database", comme nom, c'est tout pourri.
Si vos classes ne servent qu'à rendre votre code imbitable, SUPPRIMEZ-LES !!!
Bah je suis bien obligé de déclarer ma classe en public dans database.h sinon je ne peux pas l'utiliser dans database_magager.h ?Citation:
Envoyé par bacelar
Un exemple sera plus parlant :
database.h
database_manager.hCode:
1
2
3
4
5
6
7
8
9
10 public class Database { private : //... public : static string execute_request_sql(string request); //... }
Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14 #inculde <database.h> public class Database_manager { private : //... public : static obj get_obj(string request) { return Obj.string_to_obj(database::execute_request_sql("SELECT * FROM obj")); } //... }
- Si la classe n'a que des méthodes static, autant en faire un namespace
- Si la classe n'a pas d'instance non dynamique, l'inclure dans l'en-tête ne sert à rien
- Implémenter dans le .h c'est... moyen :weird:
On tourne en rond sur ton architecture bancale, mais tant que tu voudras pas l'entendre et restera caché derrière, ça mène à rien.
C'est pas faux, même si d'un point de vu POO c'est pas mieux de faire une classe static ?Citation:
Envoyé par Bousk
Hein ? Si je mets pas #inculde <database.h> j'aurais pas accès à ma class Database. Désolé je comprends pas ce que tu dis :koi:Citation:
Envoyé par Bousk
Oui, non pardon en faite c'est bien implémenter dans mon .cpp, juste j'avais la flemme de tout écrire.Citation:
Envoyé par Bousk
Donc du coup, je fais quoi ? J'organise mon code comment ?Citation:
Envoyé par Bousk
Surement pas.Citation:
C'est pas faux, même si d'un point de vu POO c'est pas mieux de faire une classe static ?
On utilise l'outil qui correspond à la situation.
Si vous avez regroupé un capharnaüm de fonctions qui n'ont pas de lien entre elles, c'est très moyen, voir complètement dégueu.
Mais un namespace est moins contraignant qu'une classe statique.
Et si c'est un capharnaüm, c'est pas une classe, point barre.
Faites un peu de conception, nom d'un chien.
Votre "Database" mérite largement une classe, et pas une classe statique du tout.
Jusqu'à preuve du contraire, votre "database_magager" ne sert à rien.
Utilisez CORRECTEMENT une classe avant d'en ajouter une juste pour faire "beau".
Donc DÉGAGEZ "database_magager".
Vous n'aurez pas accès à Database DANS le fichier d'en-tête, c'est bien, c'est ce qu'on veut, parce que l'implémentation est dans le fichier .cpp.Citation:
Hein ? Si je mets pas #inculde <database.h> j'aurais pas accès à ma class Database. Désolé je comprends pas ce que tu dis
Si vous utilisez un champ de type référence sur "Database" ou pointeur sur "Database", ou que vous en passez un dans la signature des méthodes, une simple "forward definition" de la classe suffit.
Mais comme je l'ai dit, DÉGAGEZ "database_magager" à la con.
Si vous ne voulez pas que "Algothim" puisse appeler "Database::write()", bin, vous déclarez la méthode "Database::write()" comme privée, c'est tout, basta.
Vous devriez utiliser la même flemme pour ne pas inventer des classes superfétatoires à la noix.Citation:
Oui, non pardon en faite c'est bien implémenter dans mon .cpp, juste j'avais la flemme de tout écrire.
Et mettre le code dans un .h, c'est pas du tout la même chose que le mettre dans le .cpp.
SimplementCitation:
Donc du coup, je fais quoi ? J'organise mon code comment ?
Code:
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 void Algothim::methodeA2Balles() { ... Database db{tousPleinDeParametresALaCon}, auto resultat = db.laMethodePublicQuiFaitleJob(parametreALaCon1,parametreALaCon2); ... /* on fait mumuse avec resultat */ ... } ----------- Dans Database.h ----------- class Database { write(); public : Database(TypeTousPleinDeParametresALaCon tousPleinDeParametresALaCon); TypeValeurRetourDeLaMethodePublicQuiFaitleJob laMethodePublicQuiFaitleJob(TypeParametreALaCon1 parametreALaCon1, TypeParametreALaCon2 parametreALaCon2); }
D'acoord, merci je comprends mieux. En gros, je dois mettre mon #include dans le .cpp plutôt que dans le .h pour ne pas y avoir accès depuis d'autre fichier qui include mon .h. Merci, c'est la répose que j'attendait.
Pour ce qui est de mon architecture, j'y est réfléchie et ce que je vais faire c'est garder ma classe database tel quel (en enlevant static) et la mettre dans une librairie car elle peut être réutilisé pour tout mes futures projet C++ et je fais faire une nouvel class comme ça :
Et ma méthode write je la passe en protected.Code:
1
2
3
4
5
6 public DatabaseController : private Database { public : TypeValeurRetourDeLaMethodePublicQuiFaitleJob laMethodePublicQuiFaitleJob(TypeParametreALaCon1 parametreALaCon1, TypeParametreALaCon2 parametreALaCon2); //... }
Je pense que comme ça c'est vraiment beaucoup mieux.
Merci à tous :mrgreen:
Oui.Citation:
En gros, je dois mettre mon #include dans le .cpp plutôt que dans le .h pour ne pas y avoir accès depuis d'autre fichier qui include mon .h.
Mais attention à bien mettre les includes nécessaires dans le .h pour que d'autres cpp qui UTILISENT la classe puissent s'en servir s'en être obligé d'inclure d'autres .h avant celui de la classe.
OkCitation:
je vais faire c'est garder ma classe database tel quel (en enlevant static)
C'est très bien.Citation:
et la mettre dans une librairie car elle peut être réutilisé pour tout mes futures projet C++
Ne multipliez pas les classes inutilement.Citation:
et je fais faire une nouvel class comme ça :
C'est quoi la valeur ajoutée de la classe "DatabaseController" ??? (Controller / Manager, c'est tout aussi flou).
Si une méthode n'a pas été conçu pour être "overridable", on la met en privée, pas en protected.Citation:
Et ma méthode write je la passe en protected.
Oui ;)Citation:
Je pense que comme ça c'est vraiment beaucoup mieux.
Bah dans Database il n'y a que des méthodes indépendantes au projet (write(), read(), reset(), etc...) et dans DatabaseController il y des méthodes dependantes au projet tel que :Citation:
Envoyé par bacelar
La fonction read me return une liste de string qui est le résultat de ma requête SQL.Code:
1
2
3
4 string DatabaseController::GetNameOfObj(int id) { return this.read("SELECT name FROM obj WHERE id =" + to_string(id));[0] // la method read provient de Database }
Erreur patente de conception.
"write(), read(), reset()" est une API générique qui est bonne pour planquer des détails de conception interne des composants.
Là, vous vous en servez juste pour obscurcir inutilement l'API de la classe DataBase.
Quitte à savoir qu'il y a un mécanisme de "CRUD" (CREATE READ UPDATE DELETE) autant que cela soit explicite dans l'API de DataBase.
J'ai l'impression que vous êtes dans la stratégie de "je bricole un truc sans trop réfléchir et on fait une conception autour de ce bricolage", mais c'est l'inverse qu'il faut faire.
Vous faites une conception qui facilite l'utilisation des composants par leur code client, puis vous bricolez une implémentation de l'API conçu AVANT.
Fait à l'arrache, avec encore des dépendances à une source de données tabulaire dans la classe "Obj" de votre projet "client" mais on n'est déjà plus obligé d'avoir une source SQL pour implémenter IDataBase.Code:
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 class Obj { constexpr std::string tablaName = "obj"; constexpr std::string colonnesStr = "id, name"; int id; std::string name; Obj(int id_, std::string name_):id(id_),name(name_){} public : static std::list<Obj> getObjs(IDataBase database, std::map<std::string,std::string> criteresAlpha, std::map<std::string,int> criteresNumerique); std::string GetName(); } string Obj::GetName() { return name; } std::list<Obj> Obj::getObjs(IDataBase database, std::map<std::string,std::string> criteresAlpha, std::map<std::string,int> criteresNumerique) { std::list<Obj> result; auto res = database.select(colonnesStr, tablaName, criteresAlpha, criteresNumerique); for(auto& line : res) { result.add(Obj(StringToInt(line[0]),line[1]); } return result; } std::vector<std::vector<std::string>> DataBase::select(std::string colonnes, std::string tableName, std::map<std::string,std::string> criteresAlpha, std::map<std::string,int> criteresNumerique) { /*construction de la clause WHERE avec criteresAlpha et criteresNumerique*/ std::string request = "SELECT " +colonnes + " FROM " + tableName " WHERE " + clauseWhereCalculer + (clauseWhereCalculer=="" ? " AND" ! "") +" (1=1)" ) ... return result; }
C'est quoi la valeur ajoutée de la classe "DatabaseController" ??? (Controller / Manager, c'est tout aussi flou). (BIS) :weird::aie:
De plus, c'est le genre de truc qu'on laisse à un ORM pour pondre ce type de code au kilomètre.
Ok, je sais que je fais de la merde mais c'est plus simple pour moi si toutes les requêtes SQL sont dans le même fichier de code car cela me permet de pourvoir charger en rame toutes les requêtes puis de ensuite pouvoir juste appeler le résultat sans avoir à effectuer la requête qui prend trop de temps.
Dans mon cas "DataBaseManager" (nom encore à définir) va appeler beaucoup de requêtes SQL à l'initialisation et comme ça quand je demande une valeur de ma base je la prends directement dans "DataBaseManager" sans appel à la base ce qui accélère beaucoup mon temps de calcul. En gros, database manager est un buffer du résultat de toutes mes requêtes SQL.
Je sais que je devrais faire mes appels à la database à l'instantiation de mes objets en passant une instance de Database dans le constructeur mais pour moi c'est moins lisible et puis ça me force à refaire tout mon code et c'est chiant, j'ai plus de 5000 lignes de code, je peux pas tout refaire à la main.
Évidemment que c'est comme ça, à la base mon projet devait prends moins d'1 an à développer et là j'en suis quasiment à 3 et il n'est toujours pas fini car je rajoute toujours de nouvelles features. Au début il n'y avait même pas de database, les éléments se rajoute au fur et à mesure du projet.Citation:
Envoyé par bacelar
Et j'espère que tu comprends que je ne peux pas refactorisé la totalité de mon code à chaque fois que je rajoute quelque chose, c'est pour ça que mon projet est comme ça. Je sais que j'aurais dû réfléchir à l'architecture du projet depuis le début mais au début il n'y avait que 3-4 classes et c'était bien puis j'ai rajouté le multi-threading, puis j'ai eu plusieurs fenêtre, j'ai ajouté la base de données puis j'ai passé du code en librairie puis j'ai commencé à recoder mes algo en GPU, etc...
Du coup je ne vais pas passer 6 mois à tout recorder mais je veux juste un peu mieux ségréguer mon code et séparer la partie affichage de la partie environnement (database et récupération de données diverse) de la partie algorithmique.
Vous avez la tête dans le guidon et vous allez vous prendre le mur, tôt ou tard.
Ne soyez pas victime de biais cognitif "aversion à la perte".
Si votre code est difficilement maintenable et qu'une autre approche réduit drastiquement la charge de travail et simplifie la maintenance, VIREZ LE VIEUX CODE, il a fait son temps.
Sinon, il va être votre boulet, c'est de la dette technique.
Si votre code n'est pas trop dégueulasse, le passage d'une approche à une autre sera de simple copier/coller, vous un petit programme de parsing/génération de code.
Il faut aussi éviter l'approche "big bang" de tout refaire, mais vous pouvez introduire rapidement la nouvelle approche sur de l'extension de l'application, vous avez un avantage direct.
Ne vous enfermez pas dans une mauvaise solution. :weird:
Si vous voulez continuez avec votre bricolage sur ce que vous avez déjà, faites, mais mettez un truc carré pour le nouveaux code.
La mise en place d'un ORM ou d'une approche comme la mienne, c'est quelques dizaines de minutes. N'hypothèques pas la maintenance du nouveau code pour si peu.
On ne nait pas avec la science infuse.Citation:
Ok, je sais que je fais de la merde
L'erreur est humaine, persévérer est diabolique.
Ouais, bon, c'est un Cache, pas un "Manager".Citation:
Dans mon cas "DataBaseManager" (nom encore à définir) va appeler beaucoup de requêtes SQL à l'initialisation et comme ça quand je demande une valeur de ma base je la prends directement dans "DataBaseManager" sans appel à la base ce qui accélère beaucoup mon temps de calcul. En gros, database manager est un buffer du résultat de toutes mes requêtes SQL.
Et comme c'est juste un cache, il n'a pas à s'emmerder avec la base de données.
Vous utilisez une classe générique de Cache et basta.
Avec MON implémentation, le cache, c'est trivial :
Avec ObjRef , Obj2Ref et Obj3Ref des champs de MonCache de type std::list<Obj> std::list<Obj2> et std::list<Obj3>.Code:
1
2
3
4
5
6
7
8 MonCache::MonCache(IDataBase database) { ObjRef = Obj::getObjs(database) Obj2Ref = Obj2::getObj2s(database) Obj3Ref = Obj3::getObj3s(database) } {
et juste changer un peu la signature de "select" de DataBase :
La synthèse des requêtes SQL n'est que dans DataBase, la gestion des colonnes de la table n'est que dans la classe correspondante à cette table.Code:DataBase::select(std::string colonnes, std::string tableName, std::map<std::string,std::string> criteresAlpha = std::map<std::string,std::string>(), std::map<std::string,int> criteresNumerique = td::map<std::string,int>())
Simple, facile, univoque et très facile à générer AUTOMATIQUEMENT.
5000 lignes, c'est ridiculement petit !!!Citation:
, j'ai plus de 5000 lignes de code, je peux pas tout refaire à la main.
Vous avez largement assez peu de code pour faire un "big bang" !!!
Tout le code "à la con" est facilement automatiquement générable. En les ORM ont des outils pour le faire pour vous.Citation:
je peux pas tout refaire à la main.
Il ne faut pas accumuler la dette technique et faire régulièrement du refactoring, donc souvent supprimer de vieux code.Citation:
, les éléments se rajoute au fur et à mesure du projet.
Pas la peine si la conception est modulaire, de base.Citation:
Et j'espère que tu comprends que je ne peux pas refactorisé la totalité de mon code à chaque fois
Si elle ne l'ai pas, il faut y tendre assez rapidement pour ne pas galérer de plus en plus.
Mettez les choses à plat, et avec 5000 lignes, franchement, c'est "big bang" directe, c'est peanuts.
Mais vous pouvez toujours faire un truc carré pour les nouvelles features et modifier à minima le code existant.
Mais pas l'inverse, ne faites pas un truc tout tordu juste pour ne pas modifier quelques lignes dans du vieux code tout moisi.
La première architecture n'est jamais bonne, les attentes du client change, etc...
Mais il faut prendre le temps de refactorer le code quand c'est nécessaire.
Il faut que l'architecture soit simple pour quel soient facilement refactorable.
Vous devez faire simple, et vos classes "Manager" et autres variables globales planqués en champ statiques sont des cordes pour vous pendre.
Non, quelques heures pour voir où couper le vieux code pour le remplacer par du code qui sera majoritairement auto-générable en quelques secondes par un ORM ou une moulinette de quelques lignes.Citation:
Du coup je ne vais pas passer 6 mois à tout recorder
C'est de très bon objectifs, mais faut le faire correctement, donc plus de manager à la con, de variables globales toutes moisies dans des champs statiques, etc...Citation:
mais je veux juste un peu mieux ségréguer mon code et séparer la partie affichage de la partie environnement (database et récupération de données diverse) de la partie algorithmique.
Le code des classes que je vous propose est tellement bête qu'on peut le faire en ayant la tête dans le cul. Preuve que c'est un bon code. :mouarf: