Partage
  • Partager sur Facebook
  • Partager sur Twitter

utiliser la fonction d'une classe dans une autre

28 août 2018 à 21:44:32

Hello la communauté,

Je me posais quelques questions en revoyant un de mes codes pour l'améliorer, notamment virer tous les getters des mes class.

Déjà, comment utiliser la méthode d'une class dans une autre? Pour l'instant j'ai fais un truc comme ça (ce qui marche bien) :

class Auteur{
private:
    string nom_;
public:
...
  /* Methodes, 
  oui je sais il faudra que je mette un operator<< (vous pas taper moi svp..!)*/
  void afficher() const
  {
    cout<<nom_;
  }
};
class Oeuvre{
private:
    string titre_;
    const Auteur&auteur_;
    string langue_;
public:
...
    /*destructeur*/
~Oeuvre()
{
    cout << "L oeuvre"<<titre_<<",";
    auteur_.afficher();
    cout <<",en"<<langue_<<"n est plus disponible."<<endl;
}

ça m'affiche bien le nom de l'auteur. Là où je trouve cette solution ultra pourrie c'est quand on veut chercher le nom de l'auteur quand on est sur une class encore au dessus. Par exemple Bibliothèque qui contient plusieurs oeuvre.

Je me vois mal créer une fonction dans la class Oeuvre comme cela :

void affiche_nom(){
auteur_.afficher();
}



pour enfin pouvoir appeler cette fonction afficher_nom() dans la classe bibliothèque et avoir le/les noms des auteurs, car là avec 3 class ça peut encore passer mais quand on en a 15..

Est-ce que je fais fausse route pour appeler des méthodes de class dans des class ou est-ce que c'est ça mais avec quelques améliorations?

Merci d'avance pour votre aide

Bonne soirée!

-
Edité par Irtis 29 août 2018 à 11:04:30

  • Partager sur Facebook
  • Partager sur Twitter
29 août 2018 à 1:43:07

Salut,

Avant toute chose:

Tu ne dois pas t'inquiéter outre mesure des différents accesseurs que tu place dans une classe, car, ce qui importe, c'est que chacune de tes classes fournissent les services que tu es en droit d'attendre de sa part.

Par exemple, les services que tu es en droit d'attendre de la part de la classe Autheur sont

  • de connaître son nom;
  • de connaître son prénom;
  • (éventuellement) de connaître la période à laquelle il a vécu (année de naissance - année de décès )

Tu n'as même pas besoin de prévoir une fonction membre affiche pour cette classe, car, avec ces trois services, tu peux provoquer l'affichage quand tu le voudra.

Par contre, il est aussi vrai qu'avoir l'opérateur << qui permettra de transmettre ces trois informations à un flux de sortie peut s'avérer utile.  Mais, soyons clairs:

  • cet opérateur fait partie de "l'interface étendue" de ta classe (dans le sens où tu peux tout à fait t'en passer)
  • on parle bel et bien de n'importe quel flux de sortie (et pas uniquement de la console)

De la même manière, les services que tu dois pouvoir attendre de la part de ta classe Oeuvre seront sans doute

  • de connaître le titre
  • de te donner accès à l'auteur
  • (éventuellement) de connaître l'année de parution
  • code ISBN
  • N° d'édition

Et encore une fois: tu n'as même pas besoin de prévoir une fonction membre affiche (pour les même raisons).  Par contre, il est tout aussi vrai que tu peux prévoir un opérateur << adapté à ... n'importe quel flux de sortie.

La seule chose, c'est que cet opérateur ne devrait transmettre à un flux de sortie qui ne peut être accessible qu'à partir de ta classe Oeuvre.  Il ne doit donc fournir aucun information sur l'auteur de ton oeuvre, vu que tu pourras décider de les afficher si tu en as besoin.

Partant de là, ta classe Auteur ressemblera donc sans doute alors à quelque chose comme

class Auteur{
public:
    Autheur(std::string const & name, 
            std::string const & firstname,
            int birth, int death = 0):
        name_{name}, firstName_{firstname},
        birth_{birth}, death_{death}{
    }
    std::string const & name() const{
        return name_;
    }
    std::string const & firstName() const{
        return firstName_;
    }
    int birth() const{
        return birth_;
    }
    int death() const{
        return death_;
    }
private:
    std::string name_;
    std::string firstName_;
    int birth_;
    int death_;
}

Et, si tu y tiens, tu pourras définir l'opérateur << pour les flux sous la forme de

ostream& operator<<(ostream & ofs, Auteur const & aut){
    ofs<<aut.name()<<", "<<aut.firstName()
       <<" ( "<<auth.birth()<<" - "
       <<aut.death()== 0 ? " " : aut.death()<<" )";
}

et ta classe Oeuvre ressemblera sans doute à quelque chose comme

class Oeuvre{
public:
    Oeuvre(std::string const & title,
           int year, int edition, int isbn,
           Autor const & autor):
        title_{title},year_{year}, edition_{edition},
        isbn_{isbn}, autor_{autor}{
    }
    std::string const & title() const{
        return title_;
    }
    int year() const{
        return year_;
    }
    int edition() const{
        return edition_;
    }
    int isbn() const{
        return isbn;
    }
    Auteur const & auteur(){
        return autor_;
    }
private:
    std::string title_;
    int year_;
    int edition_;
    int isbn_;
    Auteur const & autor_;
};

Et, de la même manière, si tu y tiens, l'opérateur << pourra prendre une forme proche de

std::ostream & operator<<(std::ostream & ofs, Oeuvre const & o){
    ofs<<o.title()<<"\n"
       <<"year "<<o.year()<<" edition "<<o.edition()
       <<"\nISBN "<<o.isbn();
}

De cette manière, tu ne seras pas forcé quant aux informations que devras afficher lorsque tu parcourras les éléments de ta bibliothèque.

Par exemple, si tu veux obtenir tous les livres (avec leur auteurs, mais sans leurs informations de naissance et de mort), tu pourras avoir une fonction 

void printAllBooks() const{
    /* on va passer tous les livres en revue */
    for(auto const & b : books_){
        std::cout<<b;
        auto const & autor = b.autor();
        std::cout<<b.name()<<", "<<b.firstName()<<"\n";
    }
}

Mais si tu veux n'avoir que les oeuvre écrites par un auteur quelconque, tu t'en fous des informations concernant l'auteur.  Cela pourrait donc prendre la forme d'une fonction proche de

void printFromAutor(std::string const & name, std::string const & firstname){
    for(auto const & b : books_){
        auto const & autor=b.autor();
        if(autor.name() == name && autor.firstName() ==firstname){
            std::cout<<b
        }
    }
}

bien sur, il serait sans doute intéressant d'avoir un identifiant unique pour les auteurs (ainsi d'ailleurs que pour les livres), pour éviter les éventuelles fautes de frappe dans le nom et / ou le prénom, mais c'est une autre histoire ;)

Ce qu'il faut retenir de cette intervention:

1- L'interface publique "directe" (comprends : composée des fonctions membres) d'une classe doit être réduite au stricte minimum

2- S'il est possible de créer une fonction libre (comme c'est le cas pour l'opérateur <<) qui profitera de l'interface publique "directe", il est préférable de choisir cette solution

3- A de très rare exceptions près, s'il est possible d'avoir une fonction "générique" (comme l'opérateur << adapté à n'importe quel flux de sortie) au lieu d'une fonction trop "spécifique" (comme la fonction affiche() ), il est préférable d'utiliser la solution "générique"

4- A de rares exceptions près, tes classes ne doivent s'occuper que des informations qu'elles sont les seules à pouvoir te donner: ce n'est pas  parce qu'une oeuvre aura -- forcément -- été écrite par un auteur particulier que tu dois faire en sorte ... d'afficher les informations concernant l'auteur en question quand tu souhaite faire afficher les informations de l'oeuvre ;)

  • Partager sur Facebook
  • Partager sur Twitter
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
29 août 2018 à 10:41:57

Merci Koala,

Je répondrai à tous tes conseils/infos un peu plus tard (le temps de rerelire encore plusieurs fois tes explications (et de les tester) pour bien tout saisir :) )

Par contre là ou je suis perdu, c'est avec la discussion qui j'ai eu avec gb qui me disait justement d'éviter d'utiliser des accesseurs et autres dans mes classes. https://openclassrooms.com/forum/sujet/auteur-const-getauteur-return-auteur#message-92600257

Pour gagner en maintenabilité et respecter le principe de Demeter, donc ne pas exposer les éléments interne de ta class en dehors d'elle même. à moins que j'ai pas tout saisie. (ton point 4 répond déjà un peu à la problématique, mais je ne suis pas sûr de bien tout saisir)

Du coup je suis un peu perdu sur ce point là..

-
Edité par Irtis 30 août 2018 à 10:25:24

  • Partager sur Facebook
  • Partager sur Twitter
29 août 2018 à 17:25:50

De manière générale, tu dois te dire que les accesseurs peuvent apparaître  si (et seulement si) il correspondent à un service auquel tu t'attend de disposer pour ta classe.

C'est toute la différence entre le nom d'un auteur (ou le titre d'un livre) et le réservoir à essence d'une voiture!

Si tu as un auteur dont tu ne peux pas connaître le nom ou un livre dont tu ne peu pas connaître le titre, tu ne sais absolument rien faire avec ton auteur (ou ton livre).

Par contre, toute personne "un peu sensée" aura forcément bien conscience qu'une voiture doit disposer d'un réservoir à carburant: tout le monde se rend en effet bien compte que le carburant ne pas pas être maintenu "par magie" "quelque part entre le sièges passager et la route" !

Et pourtant, aucun utilisateur ne devra jamais accéder directement au réservoir: tous le utilisateurs utiliseront l'interface que la voiture expose en rapport avec l'existence (présumée ???) du réservoir à  carburant:

  • la soute à carburant,
  • la jauge à carburant
  • l'affichage de l'autonomie restante présumée
  • ...

Déméter refusera donc un accesseur getReservoir, parce que... l'utilisateur de la voiture n'a absolument aucun besoin de connaître le réservoir.

Pour être clair: si l'accesseur auquel tu penses correspond effectivement à une question que tu veux pouvoir poser à ta classe, tu n'auras pas vraiment d'autre choix que de fournir l'accesseur en question ;)

Et, à partir du moment où tu dispose d'un accesseurs (ou, pour faire simple, de n'importe quelle fonction qui te permet d'interroger ou de donner un ordre à un élément du type indiqué), c'est -- forcément pour te donner la possibilité d'y faire appel "quand bon de semble" :D

Après, cela devient un jeu de légos: plus tu as de fonctions simples, plus tu es libre de la manière dont tu pourras (envisager) de les assembler.  Plus  tes fonctions deviendront complexes (entre autre, en appelant un nombre important de fonctions simples), plus l'usage que tu pourras en faire deviendra spécifique, et plus les circonstances devront être semblables pour que leur utilisation puisse "correspondre" effectivement au besoin réel auquel tu es soumis ;)

  • Partager sur Facebook
  • Partager sur Twitter
Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
29 août 2018 à 17:56:17

top!

Je comprends beaucoup mieux les accesseurs. Je vais tester et réfléchir à tout ça ce soir.

Merci pour ton aide koala.

  • Partager sur Facebook
  • Partager sur Twitter