IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C++ Discussion :

Fonction amie étant méthode d'une autre classe


Sujet :

C++

  1. #1
    Nouveau membre du Club
    Homme Profil pro
    Étudiant
    Inscrit en
    Octobre 2018
    Messages
    28
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 26
    Localisation : France, Isère (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Octobre 2018
    Messages : 28
    Points : 30
    Points
    30
    Par défaut Fonction amie étant méthode d'une autre classe
    Bonjour,

    Je suis en train d'écrire un petit programme pour entretenir le peu de niveau que j'ai en C++ et je suis bloqué.
    Avant d'expliquer, voici les fichiers que j'ai (.h et .cpp) :

    Interface.h :
    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
     
    #ifndef DEF_INTERFACE
    #define DEF_INTERFACE
     
    #include "Piece.h"
     
    class Interface{
    public:
    	void grosseMethode();
     
    private:
    	int trouverLigneAtterissage() const;
    	Piece* p_;
    };
     
    #endif
    Interface.cpp :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #include "Interface.h"
     
    void Interface::grosseMethode() {
    	// ...
    	// Fonction à ajouter
    }
     
    int Interface::trouverLigneAtterissage() const{
    	// Calculs
    	return ligneAtterissage;
    }
    Piece.h :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    #ifndef DEF_PIECE
    #define DEF_PIECE
     
    class Piece{
    protected:
    	int ligneOrigine_;
    };
     
    #endif
    Ce que je cherche à faire, c'est créer une méthode setAltitudeFinale que j’appelerai dans grosseMethode(), qui utilise trouverLigneAtterissage() et qui modifie ligneOrigine_ MAIS je voudrais éviter de créer une méthode publique setLigneOrigine() dans la classe pièce car je veux que seule cette méthode setAltitudeFinale puisse toucher à l'attribut ligneOrigine_ à l'extérieur de la classe Piece.

    J'ai donc pensé à utiliser les friend :
    J'ai ajouté la méthode void setAltitudeFinale(Piece* p); dans la classe Interface et je l'ai déclaré comme amie dans la classe Piece :

    Interface.h :
    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
    #ifndef DEF_INTERFACE
    #define DEF_INTERFACE
     
    #include "Piece.h"
     
    class Interface{
    public:
    	void grosseMethode();
     
    private:
    	int trouverLigneAtterissage() const;
    	void setAltitudeFinale(Piece* p);
    	Piece* p_;
    };
     
    #endif
    Interface.cpp :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #include "Interface.h"
     
    void Interface::grosseMethode() {
    	// ...
    	setAltitudeFinale(p_);
    }
     
    int Interface::trouverLigneAtterissage() const{
    	// Calculs
    	return ligneAtterissage;
    }
    Piece.h :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #ifndef DEF_PIECE
    #define DEF_PIECE
     
    class Piece{
    protected:
    	friend void Interface::setAltitudeFinale(Piece* p);
    	int ligneOrigine_;
    };
     
    #endif
    Alors déjà première question, mais je ne sais pas si je dois faire l'implémentation de la méthode setAltitudeFinale dans Interface.cpp ou dans Piece.cpp, j'aurais tendance à la faire dans Interface mais je suis pas sûr à 100% donc si vous pouviez me le confirmer ça serait génial ! Dans tous les cas, l'implémentation donne :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    void Interface::setAltitudeFinale(Piece* p) {
    	int ligneAtterissage = trouverLigneAtterissage();
    	p->ligneOrigine_ = ligneAtterissage;
    }
    Quand j'essaie de faire ça, si je mets l'implémentation dans Interface.cpp, Visual Studio me souligne ligneOrigine_ et me dit que ce membre est inaccessible.
    Si je déplace l'implémentation dans Piece.cpp, il ne reconnait pas la classe Interface (et il m'empêche d'utiliser ligneOrigine_ et trouverLigneAtterissage). Quand j'ajoute #include "Interface.h" dans Piece.h, Visual Studio ne me souligne plus rien mais quand je compile, j'ai plein d'erreurs qui viennent (selon moi) du fait que Piece.h inclus Interface.h et que Interface.h inclus Piece.h.

    J'avais pensé à faire l'inverse, à créer la méthode setAltitudeFinale() dans la classe Piece mais vu que cette méthode ne prendrait pas d'Interface en paramètre, je voyais pas comment appeler trouverLigneAtterissage().

    J'espère que j'ai été assez clair sur ce que j'ai envie de faire. Si vous pouvez m'aider ça serait vraiment super, j'espère au moins que ce que j'essaie de faire est possible.
    Si jamais vous voyez une meilleure façon de le faire (c'est à dire appeler une fonction/méthode dans grosseMethode() qui peut appeler trouverLigneAtterissage() et modifier ligneOrigine_), ou une autre façon de voir les choses au niveau conceptuel, je suis preneur !

    Merci d'avance !

  2. #2
    Expert éminent sénior
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 068
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : Février 2005
    Messages : 5 068
    Points : 12 111
    Points
    12 111
    Par défaut
    Le problème, c'est que votre fonction "friendlisé" par la classe Piece "setAltitudeFinale" est private :
    https://stackoverflow.com/questions/...-another-class

  3. #3
    Nouveau membre du Club
    Homme Profil pro
    Étudiant
    Inscrit en
    Octobre 2018
    Messages
    28
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 26
    Localisation : France, Isère (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Octobre 2018
    Messages : 28
    Points : 30
    Points
    30
    Par défaut
    D'accord je savais même pas que ça pouvait problème. Je pensais à tord que le fait de privatiser une méthode ne servait qu'à la rendre inaccessible en dehors de la classe.
    J'ai passé la méthode setAltitudeFinale(Piece* p) en public dans Interface mais j'ai toujours un problème inhérent au fait que dans Piece.h.

    J'ai regarder pour faire une déclaration anticipé dans la classe Interface dans la classe Piece mais ça ne fonctionne pas, il faut que je mette toutes les méthodes et au final elles entrent en conflit avec les "vraies" méthodes.
    Je me demande sérieusement, ce j'essaie de faire est vraiment réalisable ? Est ce que ma question est basique ? J'ai beau chercher je n'ai pas trouvé d'exemple similaire.

    J'aimerais vraiment aussi savoir où est ce que je suis censé écrire l'implémentation d'une méthode friend. Est ce que je dois l'écrire dans la classe d'origine ou dans la classe amie ?

    Merci !

  4. #4
    Expert éminent sénior
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 068
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : Février 2005
    Messages : 5 068
    Points : 12 111
    Points
    12 111
    Par défaut
    D'accord je savais même pas que ça pouvait problème.
    Moi non plus, mais si l'on suit la logique de "friend", ce n'est pas un moyen de contourner l'encapsulation contrairement à ce qu'indiquent certaines sources.
    C'est un moyen d'étendre "l'API" utilisable par le code client de la classe, faisant de la fonction amie quasiment une fonction de la classe.
    C'est donc assez logique que cette fonction amie soit "public".

    N'utilisez jamais "friend" pour contourner l'encapsulation car vous cacheriez une erreur de conception avec un outil à double-tranchant (friend), mais du mauvais côté du tranchant.

    Je pensais à tord que le fait de privatiser une méthode ne servait qu'à la rendre inaccessible en dehors de la classe.
    Tout à fait, d'où le manque de logique de faire d'une fonction amie une fonction "privée" d'une autre classe.

    Il faut faire en sorte de rendre les classes les plus indépendantes les unes des autres pour rendre leur utilisation la plus simple possible.

    J'ai passé la méthode setAltitudeFinale(Piece* p) en public dans Interface mais j'ai toujours un problème inhérent au fait que dans Piece.h.
    Je comprends pas trop le problème.
    Voici un code qui compile quand il est entièrement dans un cpp :
    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
    class Piece;
     
    class Interfaced {
    public:
    	void grosseMethode();
    	void setAltitudeFinale(Piece* p);
     
    private:
    	int trouverLigneAtterissage() const;
    	Piece* p_;
    };
     
    class Piece {
    protected:
    	int ligneOrigine_;
    	friend void Interfaced::setAltitudeFinale(Piece* p);
    };
     
     
     
    void Interfaced::grosseMethode() {
    	// ...
    	setAltitudeFinale(p_);
    }
     
    int Interfaced::trouverLigneAtterissage() const {
    	// Calculs
    	return 5;// ligneAtterissage;
    }
     
    void Interfaced::setAltitudeFinale(Piece* p) {
    	int ligneAtterissage = trouverLigneAtterissage();
    	p->ligneOrigine_ = ligneAtterissage;
    }
    Il y a donc un couplage important entre les classes, malheureusement, mais encore "soluble".

    Le code source SVP.

    Je me demande sérieusement, ce j'essaie de faire est vraiment réalisable ? Est ce que ma question est basique ? J'ai beau chercher je n'ai pas trouvé d'exemple similaire.
    Vous utilisez peut-être une mauvaise méthode pour résoudre un problème.
    Si vous nous présentiez le problème original, on pourrait plus efficacement vous aiguiller.

    J'aimerais vraiment aussi savoir où est ce que je suis censé écrire l'implémentation d'une méthode friend. Est ce que je dois l'écrire dans la classe d'origine ou dans la classe amie ?
    Le compilateur sans fout, mais il est plus logique de mettre l'implémentation d'une fonction d'instance/de classe dans le .cpp de cette classe (la classe "Interfaced" dans votre exemple).
    Mais c'est quand même assez rare qu'une fonction d'instance/de classe soit un bon choix pour une fonction amie, on préfère très souvent des fonctions libres.

  5. #5
    Nouveau membre du Club
    Homme Profil pro
    Étudiant
    Inscrit en
    Octobre 2018
    Messages
    28
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 26
    Localisation : France, Isère (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Octobre 2018
    Messages : 28
    Points : 30
    Points
    30
    Par défaut
    Citation Envoyé par bacelar Voir le message
    Vous utilisez peut-être une mauvaise méthode pour résoudre un problème.
    Si vous nous présentiez le problème original, on pourrait plus efficacement vous aiguiller.
    Alors en fait, j’essaie de coder un Tétris. Ma classe Interface correspond à tous les éléments qu'on voit, c'est à dire les pièces suivantes, les pièces mises de côté et la zone de jeu dans laquelle on fait tomber les pièces. Elle contient les méthodes qui vont agir sur ces pièces, vérifier les lignes pleines, vérifier que la partie n'est pas perdue, etc, et elle a pour attributs, entre autres, les différentes pièces (par agrégation et pas par composition), les coordonnées de la pièce active et des attributs relatifs à l'affichage.

    J'ai aussi une classe Piece qui est un classe abstraite et j'ai une classe pour chaque type de pièces qui hérite de la classe Piece. Dans cette classe Piece, je vérifie que je peux tourner et déplacer la pièce (en gros, les méthodes pour tourner et déplacer de la classe Interface appellent sur les pièces les méthodes pour tourner et déplacer de la classe Piece, c'est dans la classe pièce que je vérifie qu'on a le droit de se déplacer/tourner) et j'ai des attributs relatifs à la position de la pièce, à sa hauteur/largeur, ...

    Dans la classe Interface, j'ai une méthode qui me permet de "valider" une pièce :
    • elle calcule la ligne à laquelle je dois descendre la pièce : avec la méthode Interface::trouverLigneAtterissage()
    • elle la fait descendre à la bonne hauteur

    Puis d'autres méthodes prennent la relève, regarde si une ligne est complète, supprime les pièces quand il faut, compte les scores, ...

    Ce qui me pose problème, c'est que pour descendre ma pièce, il faut que j'accède aux attributs privés de la classe Piece car c'est ceux-là qui comptent vraiment. L'attribut relatif à la position de la pièce active dans la classe Interface ne sert que tant qu'elle est active, à partir du moment où elle est validée, je me sers des attributs de la classe Piece (par exemple, quand une ligne est pleine, je dois descendre toutes les pièces au-dessus de cette ligne).

    J'aimerais éviter de créer un setter pour la hauteur de la pièce, parce que pour moi aucune classe ne devrait toucher à la position de la pièce SAUF quand je viens de la valider.
    C'est pourquoi je voulais créer une méthode Interface::setAltitudeFinale(Piece* p) qui utiliserait la méthode Interface::trouverLigneAtterissage() puis qui modifierait directement les attributs de l'objet de la classe Piece. La seule solution que j'ai trouvé, c'est de définir cette méthode comme friend de la classe Piece, pour qu'elle ait accès au attributs privés de la classe Piece.

    Le fait est que quand j'essaie de faire comme ça, quand j'écris l'implémentation de Interface::setAltitudeFinale(Piece* p) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    void Interface::setAltitudeFinale(Piece* p) {
    	int ligneAtterissage = trouverLigneAtterissage();
    	p->ligneOrigine_ = ligneAtterissage;
    }
    ligneOrigine_ est soulignée en rouge et on me dit que le membre Piece::ligneOrigine_ est inaccessible, alors que j'étais persuadé que c'était justement tout l'intérêt des fonctions amies que d'avoir accès aux attributs privés de la classe.
    Et j'ai le problème dû au fait que, vu que je n'inclus pas Interface.h dans Piece.h (vu que j'inclus déjà Piece.h dans Interface.h), la classe Piece ne reconnait pas dans friend void Interface::setAltitudeFinale(Piece* p); le fait que Interface est une classe.

    Si y a besoin, je veux bien mettre tout mon code, mais c'est vrai qu'il est un peu long, donc si ça sert à rien, j'évite.

    Merci beaucoup de prendre le temps de m'aider, j'ai déjà appris beaucoup grâce à ce post (l'intérêt de la privatisation) et rien que pour ça, ça valait le coup.
    Merci encore !

  6. #6
    Expert éminent sénior
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 068
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : Février 2005
    Messages : 5 068
    Points : 12 111
    Points
    12 111
    Par défaut
    Ma classe Interface correspond à tous les éléments qu'on voit,
    Elle devrait alors se nommer "Visible", non ?

    Elle contient les méthodes qui vont agir sur ces pièces
    Alors soit elle porte mal son nom soit elle fait trop de chose.
    Afficher des choses et "agir" sur des choses ne devrait pas être dans la même classe.
    C'est des types de "services" qui n'ont rien à voir l'un avec l'autre.

    vérifier les lignes pleines
    Idem que pour le point précédent.
    Votre classe doit fournir un ensemble de services COHÉRENT, pas amalgamer tout ce qui traine.

    vérifier que la partie n'est pas perdue, etc,
    Bon, je pense que vous avez compris le message.

    et elle a pour attributs, entre autres, les différentes pièces (par agrégation et pas par composition), les coordonnées de la pièce active et des attributs relatifs à l'affichage.
    Bon, on va dire que c'est l'objet en charge de l'affichage de l'Interface Homme Machine du jeu (et uniquement de cela) => Gui est plus précis que "Interface", non ?

    Dans cette classe Piece, je vérifie que je peux tourner et déplacer la pièce (en gros, les méthodes pour tourner et
    Ouais, bof.
    C'est plus de la responsabilité d'une autre classe que de la classe pièce de savoir si elle peut tournée, non ?
    Elle ne connait ni les autres pièces sur le "terrain" ni les limite du terrain, etc...
    Je pencherais pour une classe "Game" pour implémenter ces fonctionnalités plutôt que dans "Piece".
    Quitte à implémenter dans "Piece" des fonctionnalités pour simplifier le travail de "Game".

    déplacer de la classe Interface appellent sur les pièces les méthodes pour tourner et déplacer de la classe Piece, c'est dans la classe pièce que je vérifie qu'on a le droit de se déplacer/tourner) et j'ai des attributs relatifs à la position de la pièce, à sa hauteur/largeur, ...

    Dans la classe Interface, j'ai une méthode qui me permet de "valider" une pièce :

    elle calcule la ligne à laquelle je dois descendre la pièce : avec la méthode Interface::trouverLigneAtterissage()
    elle la fait descendre à la bonne hauteur
    Je ne comprends pas toutes ces circonvolution.
    Moi, j'ai une classe Gui, qui gère l'IHM avec l'utilisateur, a une référence sur un objet Game lui même contenant la position "logique" de chaque pièce.
    Quand l'utilisateur appuie sur Espace, la Gui appel la méthode "Drop" du Game, Game calcule les nouvelles positions, notifie Gui que l'état du jeu a changé, Gui appelle une méthode de Game pour avoir l'état logique du jeu et converti cet état logique en une représentation graphique.

    Puis d'autres méthodes prennent la relève, regarde si une ligne est complète, supprime les pièces quand il faut, compte les scores, ...
    Que des trucs que la classe "Game" est à même de faire.

    Ce qui me pose problème, c'est que pour descendre ma pièce, il faut que j'accède aux attributs privés de la classe Piece
    Pourquoi ???
    Après la suppression de ligne, il y a des choses dans la grille de jeu qui ne sont associées à aucune "Piece". C'est plutôt l'objet Game qui doit gérer ces détails de descente.
    Game peut demander des trucs aux pièces, comme les coordonnées relative de leur base, mais rien de bien complexe.
    Je ne suis même pas sûr de l'utilité des classes "Piece", peut-être que la classe "Game" peut tous gérer elle-même.

    car c'est ceux-là qui comptent vraiment.
    Bin non, Game a bien plus de billes.

    L'attribut relatif à la position de la pièce active dans la classe Interface ne sert que tant qu'elle est active, à partir du moment où elle est validée, je me sers des attributs de la classe Piece
    Heu, vos vous retrouvez avec du code qui considère une variable "mutante" (qui change de type à la volée) ???
    Je pense pas que cela soit une manière simple de faire les choses.
    Je vous conseille de revoir votre code pour ne pas être contraint à de telles extrémités.

    (par exemple, quand une ligne est pleine, je dois descendre toutes les pièces au-dessus de cette ligne).
    Qui gère ça, si ce n'est une instance de "Game" ?

    J'aimerais éviter de créer un setter pour la hauteur de la pièce
    Les setters, c'est caca.

    parce que pour moi aucune classe ne devrait toucher à la position de la pièce SAUF quand je viens de la valider.
    Un "SAUF" temporelle, c'est chaud à faire en POO.
    Non, vous ne devriez jamais à faire en sorte qu'une API "s'invalide" dans le temps.
    Redesignez vos classes/conception pour ne pas avoir ce genre de contrainte.

    C'est pourquoi je voulais créer une méthode Interface::setAltitudeFinale(Piece* p) qui utiliserait la méthode Interface::trouverLigneAtterissage() puis qui modifierait directement les attributs de l'objet de la classe Piece.
    Que c'est compliqué.
    Pourquoi pas un simple :
    Gui->Game.Drop->Calcule par l'objet Game du résultat final au niveau de la grille ( en utilisant les services des objets "Piece" si besoin, mais il faut que les services des objets Piece soient en lien avec les pièces et pas avec les grilles ou les règles de rotations du jeu, etc...)

    La seule solution que j'ai trouvé, c'est de définir cette méthode comme friend de la classe Piece, pour qu'elle ait accès au attributs privés de la classe Piece.
    Quand c'est un choix par défaut, c'est clairement un mauvais choix.
    Comme déjà indiqué, simplifiez votre conception.

    En résumé, pourquoi n'avez vous pas une classe "Game" pour gérer les règles "business" du jeu ?

  7. #7
    Nouveau membre du Club
    Homme Profil pro
    Étudiant
    Inscrit en
    Octobre 2018
    Messages
    28
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 26
    Localisation : France, Isère (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Octobre 2018
    Messages : 28
    Points : 30
    Points
    30
    Par défaut
    Je me rends compte avec ces remarques qu'il y a un problème plus gros dans mon code et dans ma manière d'organiser mon Tétris que celui pour lequel je suis venu demander de l'aide.

    Alors soit elle porte mal son nom soit elle fait trop de chose.
    Je pense qu'elle porte très mal son nom et qu'elle fait un peu trop de choses, elle devrait s'appeler Game car au final, dans toutes les méthodes de la classe, une seule sert à l'affichage.
    Je vais sortir tout ce qui est relatif à l'affichage de cette classe pour mieux séparer les choses.

    C'est plus de la responsabilité d'une autre classe que de la classe pièce de savoir si elle peut tournée, non ?
    J'avais essayé de faire comme ça mais je n'ai pas trouvé comment faire tourner les pièces en générale, j'ai une méthode par type de pièce pour pouvoir les faire tourner.

    Ce qui me pose problème, c'est que pour descendre ma pièce, il faut que j'accède aux attributs privés de la classe Piece
    Pourquoi ???
    Effectivement, je vais changer ça, c'est problématique. La position de la pièce n'a rien à faire dans cette classe, c'est Game qui l'utilise.

    Je ne vais pas répondre à toutes les remarques mais elles ont été lu et vont être prises en compte.
    Je vais prendre le temps de tout remettre à plat et de revoir ma manière de faire.

    Merci beaucoup pour avoir pris le temps de répondre à chaque fois, ça m'a vraiment aidé.

  8. #8
    Expert éminent sénior
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 068
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : Février 2005
    Messages : 5 068
    Points : 12 111
    Points
    12 111
    Par défaut
    J'avais essayé de faire comme ça mais je n'ai pas trouvé comment faire tourner les pièces en générale, j'ai une méthode par type de pièce pour pouvoir les faire tourner.
    Vous pouvez très bien séparer le calcul de la pièce après rotation du fait d'accepter la rotation.
    Donc, dans pièce, vous calculez le résultat de la rotation (via la création d'une pièce après rotation, mais ghost car pas à afficher) et c'est Game qui validera la rotation.
    Mais avec un peu de réflexion, vous trouverez une manière de calculer le résultat de n'importe quelle pièce dans Game et pas dans chaque Pièce.
    De plus, vous pouvez très bien avoir une pièce "valide" avant rotation et après (fin de rotation) mais avec un obstacle interdisant la rotation quand même:
    C'est pour cela qu'il est largement préférable de calculer les possibilités de rotation dans Game et pas dans Pièce.

    La position de la pièce n'a rien à faire dans cette classe, c'est Game qui l'utilise.
    Je ne suis pas sûr que la position d'une pièce n'est pas à être dans une instance de pièce, car, sinon, je vois pas trop à quoi elle sert.
    Mais sa position n'a clairement pas à être "private".

    Faire une conception un peu réfléchi avant de coder est un très bon réflexe, mais ne tombez pas dans l’excès inverse de l'over-engineering.
    Une conception devrait être évolutive.

  9. #9
    Nouveau membre du Club
    Homme Profil pro
    Étudiant
    Inscrit en
    Octobre 2018
    Messages
    28
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 26
    Localisation : France, Isère (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Octobre 2018
    Messages : 28
    Points : 30
    Points
    30
    Par défaut
    Mais sa position n'a clairement pas à être "private".
    J'avais cru comprendre qu'il ne fallait jamais mettre un attribut en public, que même si c'était possible, c'était à éviter à tout prix.
    Du coup la position est en private et j'ai un couple de méthodes get/set qui sert à la y accéder.

    Faire une conception un peu réfléchi avant de coder est un très bon réflexe, mais ne tombez pas dans l’excès inverse de l'over-engineering.
    C'est mon premier projet perso, j'ai essayé de prévoir un maximum mais c'est assez difficile (en tout cas pour moi).
    Je vais essayé de revoir la conception, j'espère que ça ne me prendra pas trop de temps.

  10. #10
    Expert éminent sénior
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 068
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : Février 2005
    Messages : 5 068
    Points : 12 111
    Points
    12 111
    Par défaut
    J'avais cru comprendre qu'il ne fallait jamais mettre un attribut en public, que même si c'était possible, c'était à éviter à tout prix.
    Du coup la position est en private et j'ai un couple de méthodes get/set qui sert à la y accéder.
    Vieux mantra de JAVAïste.
    Toute conception est un compromis.
    Quand j'indique "n'a clairement pas à être private", "private" avec accesseur/mutateur (get/set) ou "public", c'est blanc bonnet et bonnet blanc.
    C'est pour cela que cette conception "JAVAïste" est souvent ridiculisée par les tenants d'autres langages.
    En C++, on n'est plus dans le "DRY" (Don't Repeat Yourself) que respecter des règles (d'autres langages qui plus est) qu'il faut savoir interpréter.
    Concrètement, appliquer bêtement cette règle JAVAïste, sans les outils JAVA, est une perte de temps en C++.
    Mais en C++, l'encapsulation de la POO est très importante.
    Mais il y a plusieurs types d'objets.
    Il y a les objets "classiques" qui offrent des services et qui doivent faire en sorte d'être facile d'emploi, donc en "cachant" leurs mécanismes internes (champs "internes" compris).
    Et les objets "boite blanche" (POCO/DTO) qui n'offrent pas plus de service que les structures du C, un simple conteneur de données, comme une paire (X,Y) des coordonnées d'un point dans un plan 2D.
    Dans le 2ème cas, l'encapsulation bébête à la JAVA n'a strictement aucun sens.

    Il est courant de commencer avec des champs de type "boite blanche" comme constituant d'autres classes et ensuite de les transformer en objet "carrossé", offrant des services, donc en cachant ces champs "internes".

    Ici, je ne suis pas sûr que les "Pièces" offrent des services, donc "méritent" un carrossage (pour l'instant).
    J'ai l'impression que c'est Game qui va faire tout le "boulot" car je ne vois aucun "services" que les pièces peuvent faire "simplement".

  11. #11
    Nouveau membre du Club
    Homme Profil pro
    Étudiant
    Inscrit en
    Octobre 2018
    Messages
    28
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 26
    Localisation : France, Isère (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Octobre 2018
    Messages : 28
    Points : 30
    Points
    30
    Par défaut
    Oui en effet ça ne semble pas judicieux de mettre des attributs privés si c'est pour ajouter un couple de get/set par la suite.

  12. #12
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 612
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 612
    Points : 30 612
    Points
    30 612
    Par défaut
    Salut,

    Pour aller plus loin, on peut même dire que, si un getXXX (un "accesseur") est parfois utile, parce qu'il correspond effectivement à un service que l'on peut attendre de la part de la classe dans laquelle il se trouve, il se peut déjà que ce ne soit pas le cas.

    Dans une classe Personne, par exemple, on peut effectivement s'attendre à ce qu'une instance de la classe soit capable de répondre à la question "quel est ton nom?".

    Peut importe la manière dont le nom est effectivement représenté au niveau de la classe, il sera donc "logique" d'avoir un accesseurstd::string const & name() const (ou std::string const & getName() const, si tu préfères ) qui renverra la valeur de cette information.

    D'un autre côté, imaginons une classe Voiture, dont on se doute bien que, d'une manière ou d'une autre, elle disposera d'une (ou d'un ensemble de) donnée(s) permettant de représenter la notion de Reservoir (à carburant). Car nous sommes tous bien conscients que l'essence ou le diesel ne va pas "flotter en l'air" comme par magie dans une bulle qui n'a aucun existence physique.

    L'utilisateur d'une instance de la voiture ne va jamais avoir besoin d'accéder directement à la notion de Reservoir: il va l'utiliser au travers interface de la voiture comme par exemple la jauge à carburant ou l'ordinateur de bord pour connaître la proportion entre la quantité maximale de carburant que le réservoir peut contenir et la quantité d'essence dont il dispose à un instant T, la trappe à carburants, pour y faire "entrer" au maximum la différence entre ces deux valeur de "carburant supplémentaire", ou que sais-je.

    Il n'y a donc absolument aucune raison cohérente de fournir un accesseur Reservoir tank() const (ou Reservoir getTank() const, si tu préfères), car l'utilisateur a beau savoir pertinemment qu'il y a un élément de type Reservoir dans la classe,il n'a absolument aucun besoin d'être en mesure d'y accéder, même si ce n'est que pour lui poser des questions .

    Vient ensuite le problème des mutateur (setXXX)...

    De manière générale, nous pouvons déjà exprimer le fait qu'ils présentent un très gros problème, car ils délèguent la responsabilité à l'utilisateur de s'assurer que la valeur qui sera utilisée pour modifier l'état de l'objet sera "valide et cohérente".

    Il faut savoir qu'une classe a souvent bien plus de valeur que ... la "simple" somme des données qui la composent

    Pour expliquer mon point de vue,je vais te présenter une notion que l'on utilise souvent : la classe Date. Typiquement, une date, c'est une structure qui sera composée de trois valeus entières :
    • un jour
    • un mois et
    • une année.

    Si la valeur représentant l'année ne présente aucune restriction (-200 correspond aussi bien à une année valide que 2019 dans un référentiel normal), la valeur que l'on peut donner au mois est déjà beaucoup plus restreinte, parce qu'il n'y a jamais que ... 12 mois, que l'on pourrait numéroter de 1 (pour le mois de janvier) à 12 (pour le mois de décembre). Si bien qu'il n'y a absolument aucun sens à accepter que l'utilisateur fournisse la valeur 13 ou 15 pour représenter le mois en cours

    Mais cela va encore plus loin pour la notion de jour, car cette notion dépend, à tout le moins du mois en cours qui est considéré. Pire encore : pour l'un des mois (le mois de février), le nombre de jours valides dépendra aussi ... de l'année qui est considérée.

    Ainsi, si on peut numéroter les jours de janvier entre 1 et 31, on ne peut numéroter les jours d'avril qu'entre 1 et 30, et, pour le mois de février, nous pourrons utiliser des valeurs comprises entre 1 et 28 pour les années non bissextiles et entre 1 et 29 pour les années bissextiles.

    Il faut te dire que, selon la loi empirique dite "de finagle", l'utilisateur est toujours ... un imbécile distrait : si tu lui laisses la moindre occasion de faire une connerie, tu ne dois même pas perdre ton temps à te demander SI il va la commettre, car cette loi te dit qu'il finira tôt ou tard par le faire. Tu peux directement te demander QUAND il le fera. Et cette loi te donne d'ailleurs aussi la réponse, car, selon elle, ce sera toujours ... au pire moment qui soit

    Or si tu laisses l'utilisateur décider
    • de la valeur de l'année, tu n'auras -- a priori -- aucun problème (pour autant qu'il ne choisisse pas 3046 au lieu de 2019 )
    • de la valeur du mois, il reste tout à fait libre de choisir ... n'importe quel valeur qui ne soit pas comprise entre 1 et 12 inclus
    • de la valeur du jour, il risque:
      • de manière générale, de choisir n'importe quelle valeur qui ne sera pas comprise entre 1 et 31 inclus (et donc de choisir une valeur qui est forcément invalide)
      • de choisir une valeur supérieure à 30 pour les mois composés de 30 jours
      • de choisir une valeur supérieure au nombre de jours qui composent le mois de février, en fonction de l'année considérée.
    Tu remarqueras que cela représente un nombre d'erreurs potentielles absolument considérable, et donc, qu'il y a "de très grandes chances" pour que l'utilisateur fasse une erreur à un moment ou à un autre, si tu le laisses calculer lui-même les différentes valeur

    Si tu veux pouvoir utiliser la notion de date de manière sécurisante (comprends : qui ne risque pas de te fournir des résultats totalement aberrants, dans le meilleur des cas), il faut impérativement que tu t'assures (au sein de la classe) que toutes les valeurs qui pourront être fournies par l'utilisateur pour représenter le mois et le jour soit valides et cohérentes, car tu ne peux pas partir du principe qu'il aura ... respecté toutes les restrictions imposées par la notion de date.

    Car, si on ajoute trois jours au 30 décembre 2018, nous n'obtenons pas la date du ... 33 décembre 2018, qui est une valeur totalement incoherente,, mais bien le ... 2 janvier 2019

    Mais, ignorons dans un premier temps -- pour les besoin de la réflexion -- la loi de finagle et le fait que l'utilisateur est un imbécile distrait, et voyons l'usage que l'on peut faire des mutateurs.

    Déjà, on se rend assez facilement compte que si l'on décide de ne pas fournir un accesseur (par exemple : sur la notion de réservoir de notre classe Voiture), il n'y a absolument aucune raison de fournir un mutateur

    Ensuite, on se rend compte que ce n'est pas parce que l'on fournit un accesseur, qui correspond à un service que l'on est en droit d'attendre de la part de notre classe (par exemple, la notion de nom pour notre classe Personne) qu'il est cohérent de permettre à l'utilisateur d'en changer la valeur car sauf exception, ton nom est défini à ta naissance et tu garderas le même jusqu'à ta mort.

    A moins de fournir une application de gestion de la population (pour l'état ou pour une commune), tu n'as ... absolument aucune raison de permettre la modification de cette valeur.

    Enfin, considérons "tous les autres cas", ce qui correspond à tous ces cas dans lesquels:
    • la valeur d'un des élément de la classe ne subit aucune contrainte
    • l'utilisateur est parfaitement en droit d'accéder en lecture à cet élément
    • l'utilisateur peut envisager de fournir n'importe quel autre valeur (de type adéquat, cela va de soi) pour l'élément

    Nous nous trouvons, de toutes évidences, dans une situation dans laquelle la notion complexe que l'on veut représenter a sémantique de valeur.

    Je vais me permettre une petite parenthèse concernant les classes ayant sémantique de valeur :

    Bien que certains de mes collègues ne soient pas tout à fait d'accord avec moi (ou du moins qu'ils soient d'accord sur le principe, mais pas sur l'application du principe, ce qui est parfaitement compréhensible ), j'ai tendance à considérer que ces classes devraient être considérées comme constante par défaut, dans la mesure où, si l'on vient à modifier de quelque manière que ce soit la valeur de l'un des éléments qui composent la donnée représentant l'instance de ces classes , nous obtenons "forcément" ... une donnée, une instance tout à fait différente de la classe.

    Pour expliquer mon point de vue, imaginons une structure point qui prendrait une forme proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    struct Point{
        /* le constructeur, qui prend une abscisse et une ordonnée */
        Point(int x, int y):x{x},y{y}{}
        int x;
        int y;
    };
    Si je crées un point sous la forme de
    et que je décide d'ajouter 4 à l'élément qui le compose nommé x, je vais obtenir un point {7,5} qui représente un point ... totalement du point d'origine (qui était {3,5}, rappelons le).

    Mais, refermons ici la parenthèse et revenons à nos moutons

    Le fait est que, si c'est pour quand même laisser à l'utilisateur un accès "plein et entier" à une des données qui composent la classe au travers d'un accesseur et d'un mutateur, dans une tentative de "pseudo encapsulation", (car il faut être clair sur le but de l'encapsulation: c'est de permettre à l'utilisateur d'ignorer la forme que prend une donnée interne, et de la manipuler au travers des services rendus par la classe) tout ce que nous faisons, c'est faire perdre du temps à tout le monde:

    A toi, en tant que développeur, car les six lignes de la structure Point que je montrais plus haut passeront à 19, sous la forme de
    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
    class Point{
    public:
        Point(int x, int y):x_{x}, y_{y}{}
        int getX() const{
            return x_;
        }
        int getY() const{
            return y_;
        }
        void setX(int newX){
            x_=newX;
        }
        void setY(int newY){
            y_=newY;
        }
    private:
        int x_;
        int y_;
    };
    mais aussi à l'utilisateur, car, là où il aurait pu se "contenter" d'un code proche de pour ajouter 3 à la valeur de x, il devra désormais avoir recours à un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    int x = p1.getX();
    x+=3;
    p1.setX(x);
    où, de manière plus concentrée (mais plus sujette à erreur) à un code proche de
    Je suis, personnellement (du fait d'une formation en dactylographie que j'ai suivie il y a plus de vingt ans), parmi ceux qui sont capables d'écrire du code le plus rapidement, si bien que cela me prend royalement trente secondes de plus d'écrire la deuxième version que la première .
    Et toi
    Quelle est ton habitude d'utilisation du clavier

    à combien de doigts écrits tu

    Utilises tu tes dix doigts ou n'en utilises-tu que deux
    as-tu besoin de chercher les lettres sur le clavier ou peux tu avoir le regard fixé sur "autre chose" en l'utilisant

    Et qu'en est il de l'utilisateur de ta classe, si tant est que cela ne soit pas toi
    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

Discussions similaires

  1. Réponses: 7
    Dernier message: 03/02/2012, 17h15
  2. Appel d'une méthode d'une autre classe à partir d'un actionListener
    Par bisouJava dans le forum Débuter avec Java
    Réponses: 4
    Dernier message: 31/10/2011, 09h05
  3. Exécuter une méthode d'une autre classe
    Par guillaume17 dans le forum Débuter avec Java
    Réponses: 15
    Dernier message: 25/06/2008, 12h15
  4. Réponses: 2
    Dernier message: 01/06/2007, 08h57
  5. Problème pour appeler une méthode d'une autre classe
    Par tse_tilky_moje_imja dans le forum Général Python
    Réponses: 7
    Dernier message: 03/03/2006, 13h33

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo