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

Langage C++ Discussion :

Collection & callbacks


Sujet :

Langage C++

  1. #21
    Membre émérite
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Points : 2 799
    Points
    2 799
    Par défaut
    Merci pour tes éclaircissements.

    Quand tu parles de l’utilisateur, c’est bien l’utilisateur de ta librairie de callbacks et pas l’utilisateur de l’application. Du coup, tout est connu à la compilation.

    Ta démarche qui vise à transposer purement le code JS en C++ n’est pas mauvaise en soi, mais elle ne tire pas parti de la puissance que peut t’offrir C++. Je te propose, si tu en as le temps (je ne sais pas quelles sont les contraintes de ton projet) de plutôt t’orienter vers une légère réécriture de ton code qui vise à utiliser boost::signal plutôt.

    L’idée va être :
    - les objets qui auparavant appelaient ton gestionnaire de callback avec un nom d’évènement vont désormais simplement se contenter d’émettre un signal.
    - les objets qui auparavant s’enregistraient auprès de ton gestionnaire de callback vont désormais déclarer des slots (en fait, de simples fonctions tout à fait normales).
    - une petite glue va être chargée d’établir des connexions entre tes signaux et tes slots.

    L’avantage que tu auras, c’est que tu seras dans l’impossibilité de connecter un signal à un slot de type incompatible. Ça ne compilera pas.

    Tu pourrais y voir une perte de flexibilité : en effet, il va falloir que quelqu’un connaisse à la fois l’émetteur du signal et la callback pour établir la connexion, ce qui n’est pas le cas dans ton système. Néanmoins, ce n’est que très rarement un problème. Si ça l’est, il y a moyen de contourner cela.

    Note : il n’y a *plus* de gestionnaire de callback dans cette approche. C’est totalement transparent.

    Pour reprendre ton code, il ressemblerait plutôt à :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     
    boost::signal<void (std::string)> success;
    boost::signal<void (int, std::string)> error;
     
    …
     
    if(code_retour = 200) {
       success(texte_retour)
    }
    else {
       error(status, error_messages)
    }

  2. #22
    Expert confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2012
    Messages
    1 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2012
    Messages : 1 711
    Points : 4 442
    Points
    4 442
    Par défaut
    Citation Envoyé par fanfouer Voir le message
    Je vois qu'on aime les topics à rebondissement ici
    Vui, et c'est probablement de ma faute, j'aime coder, et ça m'arrive souvent de commencer à coder avant d'avoir bien saisi le sujet. J'vais essayer d'être plus patient. :/

    Sinon la solution des signaux / slot est une bonne idée (et je ne saurais que trop te conseiller d'utiliser boost plutôt que Qt; réussi à exprimer ma haine envers Qt calmement, yeah o/)

  3. #23
    Membre régulier
    Profil pro
    Étudiant
    Inscrit en
    Janvier 2008
    Messages
    253
    Détails du profil
    Informations personnelles :
    Localisation : France, Haute Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Janvier 2008
    Messages : 253
    Points : 84
    Points
    84
    Par défaut
    Citation Envoyé par white_tentacle Voir le message
    Quand tu parles de l’utilisateur, c’est bien l’utilisateur de ta librairie de callbacks et pas l’utilisateur de l’application. Du coup, tout est connu à la compilation.
    Oui, nous sommes d'accord.

    Ta démarche qui vise à transposer purement le code JS en C++ n’est pas mauvaise en soi, mais elle ne tire pas parti de la puissance que peut t’offrir C++. Je te propose, si tu en as le temps (je ne sais pas quelles sont les contraintes de ton projet) de plutôt t’orienter vers une légère réécriture de ton code qui vise à utiliser boost::signal plutôt.
    C'est sur que ça aura au moins l’avantage d'être maintenu, en plus de m'éviter de l'implémenter.
    On va tenter l'expérience.

    - les objets qui auparavant s’enregistraient auprès de ton gestionnaire de callback vont désormais déclarer des slots (en fait, de simples fonctions tout à fait normales).
    Le lancement des signaux me semble très simple, c'est un bon point.
    Comment se passe la déclaration des slots?

    Je l'utilise en JS et en PHP de la même manière.
    Une des choses que j'avais l'habitude de faire, c'est de compléter le contenu des callbacks au fur et à mesure que l'on traversait les composants de ma libraire.
    Un exemple, toujours avec une requête HTTP.
    On peut imaginer maintenant avoir un client de service, utilisant des requêtes HTTP et appliquant un traitement (une dé-serialisation JSON, obviously) avant de passer les données au client.
    L'utilisateur du client HTTP va fournir des callbacks pour traiter les données obtenues par le client (un objet JSON dé-sérialisé), traiter des erreurs fonctionnelles. Ce client va mettre en place ses propres callbacks pour traiter les données de la requête (une chaine JSON) et des erreurs de protocole. Le callback de succès du client va vérifier que l'utilisateur a déclaré un callback de succès et va lui-même l'appeler de telle sorte à ce que quand la requête HTTP aboutit, on déclenche toute une chaine jusqu'à l'utilisateur, le client ayant une valeur ajoutée au milieu. Je ne sais pas si je suis compréhensible.

    Techniquement, ça implique que le callback client HTTP ai accès aux callbacks fournis par l'utilisateur, autrement dit au scope de la méthode du client HTTP.
    Avec des langages qui supportent les closures c'est natif et sa se fait sans problème. En c++, peut-être devrais-je utiliser des lambdas comme cela a été évoqué pour le this (qui serait ici le this du client HTTP).
    Là, avec les signaux, je ne vois pas du tout comment faire. Parce que c'est le callback client HTTP qui va émettre le signal, pas le client lui-même. Or l'utilisateur du client ne voit que le client et pas ses callbacks...

    Tu pourrais y voir une perte de flexibilité : en effet, il va falloir que quelqu’un connaisse à la fois l’émetteur du signal et la callback pour établir la connexion, ce qui n’est pas le cas dans ton système. Néanmoins, ce n’est que très rarement un problème. Si ça l’est, il y a moyen de contourner cela.
    Visiblement boost s'en charge non?

    Citation Envoyé par Iradrille Voir le message
    Vui, et c'est probablement de ma faute, j'aime coder, et ça m'arrive souvent de commencer à coder avant d'avoir bien saisi le sujet. J'vais essayer d'être plus patient. :/
    Pas de soucis, je suis là pour apprendre et une discussion de cet ordre et toujours plaisante.

    Sinon la solution des signaux / slot est une bonne idée (et je ne saurais que trop te conseiller d'utiliser boost plutôt que Qt; réussi à exprimer ma haine envers Qt calmement, yeah o/)
    On est d'accord là dessus. Je n'ai pas prévu d'utiliser Qt vu que je n'ai aucune fonctionnalité de GUI prévue. Boost me va très bien.

  4. #24
    Membre émérite
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Points : 2 799
    Points
    2 799
    Par défaut
    Les slots sont… des fonctions tout à fait classiques. Bon, il manque un tutoriel boost::signal sur dvp, on trouve d’autres ressources sur le net. Si tu n’as pas peur de l’anglais, la doc boost elle-même est un bon endroit où commencer.

    Par contre, j’ai beaucoup de mal à visualiser ton architecture. J’ai l’impression qu’il y a trop de dépendances .

    Si je te suis, il y a :
    - une requête HTTP. Celle-ci va émettre un signal quand une requête réussit ou échoue.
    - un client HTTP. Celui-ci va récupérer le signal émis par la requête, faire ses traitements (par exemple, désérialiser le json). Et bien, ensuite, il va émettre un signal lui aussi. Tu peux tout à fait émettre un signal alors que tu es dans un slot).
    - l’utilisateur du client. Lui va s’abonner (ou un tiers, la glu dont je parlais, va l’abonner) auprès du client HTTP (c’est à dire que certaines de ses méthodes seront connectées au signaux émis par le client HTTP).

    Ensuite, la remontée de ta requête va être la suivante :
    - la requête lève le signal succes ou erreur, en fonction. Le signal contient, au choix :
    - l’objet requête (pas terrible),
    - les informations nécessaires et suffisantes au traitement de la requête (mieux ! inutile de donner trop d’informations : cela fige ton interface)
    - le slot du client HTTP est exécuté. Celui-ci traite la requête et fait son boulot. Ensuite, il émet un signal (là encore, avec les informations nécessaires et suffisantes uniquement. L’objet requête n’a clairement plus sa place ici).
    - le slot du client est exécuté.

    À aucun moment :
    - la requête n’a connaissance du client HTTP
    - le client HTTP n’a connaissance du client

    Et éventuellement, le client peut n’avoir aucune connaissance du client HTTP, tant qu’il y a un petit malin qui connaît les deux pour les mettre en relation.

  5. #25
    Membre régulier
    Profil pro
    Étudiant
    Inscrit en
    Janvier 2008
    Messages
    253
    Détails du profil
    Informations personnelles :
    Localisation : France, Haute Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Janvier 2008
    Messages : 253
    Points : 84
    Points
    84
    Par défaut
    Bonsoir,

    En fait, ce que tu me conseille est exactement ce que je fais avec mes callbacks.
    Le client HTTP ne fourni pas la requête HTTP à l'utilisateur qui ne la voit donc jamais. Il lui donne le JSON désérialisé de mon exemple.

    Mon problème d'imbrication peut se résoudre en utilisant des signaux différents à chaque niveau de la chaine qui s'apellent en cascade. Je vois à peu près comment faire.

    Je viens de regarder un peu la documentation boost.
    Il s'avère que la connexion d'un slot à un signal doit se faire en ayant accès au signal lui-même auquel on voudrait se connecter. Le signal est donc normalement un attribut privé de classe.
    Il faut donc fournir à la routine, les callbacks qu'on souhaite connecter à ses différent signaux.
    Pour peu que j'ai 100 signaux, j'aurais 100 arguments à fournir quelque part. C'est précisément ce que mon système propose d'éviter.

  6. #26
    Membre émérite
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Points : 2 799
    Points
    2 799
    Par défaut
    Citation Envoyé par fanfouer Voir le message
    Il faut donc fournir à la routine, les callbacks qu'on souhaite connecter à ses différent signaux.
    Pour peu que j'ai 100 signaux, j'aurais 100 arguments à fournir quelque part. C'est précisément ce que mon système propose d'éviter.
    Là je ne te suis pas. Plusieurs choses :
    - la personne qui connecte le signal au slot n’est sûrement pas la personne qui déclare le signal. Donc je ne comprends pas trop pourquoi tu dis qu’on doit fournir à la routine les callbacks qu’on connecte à ses signaux.
    - tes signaux sont typés. Les arguments de tes signaux sont dépendants de ceux-ci, et il n’y a sûrement pas 100 arguments à mettre.

    Dans l’idée, tu vas avoir quelque chose comme (à la syntaxe près) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    HttpClient client;
    connect(client.requestSuccess, processRequestResult);
    connect(client.requestFailed, processError);
    client.executeRequest()
    Je ne vois pas où tu vois des centaines d’arguments à mettre. Il y a en revanche pas mal de connexions à établir, mais c’est pareil dans ton système : les callbacks s’enregistrent.

  7. #27
    Membre régulier
    Profil pro
    Étudiant
    Inscrit en
    Janvier 2008
    Messages
    253
    Détails du profil
    Informations personnelles :
    Localisation : France, Haute Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Janvier 2008
    Messages : 253
    Points : 84
    Points
    84
    Par défaut
    Citation Envoyé par white_tentacle Voir le message
    Là je ne te suis pas. Plusieurs choses :
    - la personne qui connecte le signal au slot n’est sûrement pas la personne qui déclare le signal. Donc je ne comprends pas trop pourquoi tu dis qu’on doit fournir à la routine les callbacks qu’on connecte à ses signaux.
    Parce que les signaux en question ne sauraient être des attributs publics de la classe. Je ne rends jamais un attribut public (encapsulation).
    Du coup si on ne peut pas amener le signal jusqu'au callback, le callback doit aller jusqu'au signal pour s'y connecter. Autrement dit, je vois le moment arriver où je vais avoir 5 signaux donc 5 callbacks à fournir à mes méthodes.

    - tes signaux sont typés. Les arguments de tes signaux sont dépendants de ceux-ci, et il n’y a sûrement pas 100 arguments à mettre.
    Non mais autant de fonctions à donner que de signaux.

    Dans l’idée, tu vas avoir quelque chose comme (à la syntaxe près) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    HttpClient client;
    connect(client.requestSuccess, processRequestResult);
    connect(client.requestFailed, processError);
    client.executeRequest()
    Je suppose donc qu'on se situe en dehors du scope d'une méthode de la classe HttpClient. Donc requestSuccess et requestFailed semblent être public. Je ne peux vraiment pas faire ça. Ma religion me l'interdit formellement.
    D'ou vient la fonction connect? Est-elle fournie par boost? Doit-on la créer nous même?

    Je ne vois pas où tu vois des centaines d’arguments à mettre. Il y a en revanche pas mal de connexions à établir, mais c’est pareil dans ton système : les callbacks s’enregistrent.
    Les callbacks s'enregistre à conditions que le signal et le callbacks se situent dans le même scope au moment de l'enregistrement. Sinon il faut soit que l'un soit attribut public, soit que l'autre soit introduit dans le scope de la méthode où ils vont se connecter. D'accord?

  8. #28
    Membre émérite
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Points : 2 799
    Points
    2 799
    Par défaut
    Citation Envoyé par fanfouer Voir le message
    Parce que les signaux en question ne sauraient être des attributs publics de la classe. Je ne rends jamais un attribut public (encapsulation).
    Les signaux font partie de l’interface de la classe. N’importe qui doit pouvoir s’y connecter. En revanche, seul le propriétaire peut émettre le signal.

    En qt, les signaux sont vraiment à part. En boost, tu mets le signal en privé ou protégé, mais tu fournis la fonction pour se connecter au signal.

    Non mais autant de fonctions à donner que de signaux.
    Tu peux connecter plusieurs signaux à la même fonction (et inversement, auquel cas ils sont généralement appelés dans l’ordre d’enregistrement).

    Donc requestSuccess et requestFailed semblent être public. Je ne peux vraiment pas faire ça. Ma religion me l'interdit formellement.
    Mauvaise religion, changer religion . Plus sérieusement, est-ce que cette syntaxe te gêne ?
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    client.connectRequestSuccess(processRequestResult);
    client.connectRequestFailed(processError);
    D'ou vient la fonction connect? Est-elle fournie par boost? Doit-on la créer nous même?
    C’est un raccourci abusif de ma part, lié aux signaux qt. En boost, on utilise l’objet signal et sa méthode connect pour établir la connexion. En qt, on utilise une fonction libre connect.

    Les callbacks s'enregistre à conditions que le signal et le callbacks se situent dans le même scope au moment de l'enregistrement. Sinon il faut soit que l'un soit attribut public, soit que l'autre soit introduit dans le scope de la méthode où ils vont se connecter. D'accord?
    Le tutoriel boost te fournit un exemple :
    http://www.boost.org/doc/libs/1_52_0...html#id3287344

    Tu exposes la méthode qui permet de te connecter à un signal, de t’y déconnecter, mais pas le signal lui-même (seule la classe peut émettre le signal)

  9. #29
    Membre régulier
    Profil pro
    Étudiant
    Inscrit en
    Janvier 2008
    Messages
    253
    Détails du profil
    Informations personnelles :
    Localisation : France, Haute Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Janvier 2008
    Messages : 253
    Points : 84
    Points
    84
    Par défaut
    Citation Envoyé par white_tentacle Voir le message
    Les signaux font partie de l’interface de la classe. N’importe qui doit pouvoir s’y connecter. En revanche, seul le propriétaire peut émettre le signal.

    En qt, les signaux sont vraiment à part. En boost, tu mets le signal en privé ou protégé, mais tu fournis la fonction pour se connecter au signal.
    C'est un peu un problème dans ce cas.
    Je n'aime pas trop modifier mes interfaces publics de cette façons, sachant que les signaux/slots ne sont pas implémentables dans d'autres langages.

    Si c'est la seule façon, on sera bien obligé. Nonobstant la remarque en fin de post.

    Tu peux connecter plusieurs signaux à la même fonction (et inversement, auquel cas ils sont généralement appelés dans l’ordre d’enregistrement).
    J'aurais du dire "autant de fonction que de signaux différents". Si tu veux tous les écouter tu dois t'y connecter avec des fonctions potentiellement différentes, à moins que plusieurs signaux lancent tous les même traitements.

    Mauvaise religion, changer religion . Plus sérieusement, est-ce que cette syntaxe te gêne ?
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    client.connectRequestSuccess(processRequestResult);
    client.connectRequestFailed(processError);
    Elle ne me gêne pas outre mesure dans ce cas précis. Le fait d'avoir à modifier mon interface pour un truc particulier du C++ me gêne. Les interfaces, plus que les implémentation, répondent à une réification implémentée dans plusieurs langages.

    Le tutoriel boost te fournit un exemple :
    http://www.boost.org/doc/libs/1_52_0...html#id3287344

    Tu exposes la méthode qui permet de te connecter à un signal, de t’y déconnecter, mais pas le signal lui-même (seule la classe peut émettre le signal)
    Là me vient à l'idée un cas qui me laisse perplexe. Je vais l'exposer dans l'ordre logique du raisonnement :

    Le client HTTP de mes exemples précédent possède 4 méthodes qui permettent d'établir un CRUD avec 4 méthodes HTTP que sont GET, POST, PUT & DELETE.
    A chaque requête je vois deux événements : success & error. Je vais donc créer deux signaux au niveau du client lui-même.
    Ca n'est pas une idée car il ne sera pas possible de distinguer un success GET ou un success DELETE.
    Donc j'ai meilleur temps de créer 8 signaux success_get, success_post,...
    Ca n'est pas encore parfait car si je lance deux requêtes asynchrones en même temps, le même callback sera appelé pour le success des deux requête alors que je peux vouloir appeler deux callbacks différents pour chacune des deux requêtes.

    Mon gestionnaire actuel me permet de fournir des jeux de callbacks lors de l'appel qui lance chaque requête. On peut fournir des instances différentes, avec des callbacks différents, à chaque appel. Si les signaux font partie de l'interface public de mon client, on est détaché de tout contexte de requête.
    De plus, pouvant identifier un nombre théoriquement infini de callbacks dans ce gestionnaire, je n'ai plus qu'un seul argument nécessaire pour fournir les callbacks lors de l'appel et non plusieurs.

    Comme suit :
    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
     
    // Gestionnaire des callbacks
    GestionnaireClbk clbk_1, clbk_2;
     
    // Enregistrement des fonctions (déclarées précédemment, disons)
    clbk_1.put("success", clbk_success_1);
    clbk_1.put("error", clbk_error_1);
    clbk_2.put("success", clbk_success_2);
    clbk_2.put("error", clbk_error_2);
     
    // Requetes
    HTTPClient client;
     
    client.getRequest("pomme", 1, clbk_1); // Informations sur la pomme #1
    client.getRequest("poire", 23, clbk_2); // Informations sur la poire #23
    Existe-t-il un moyen de surmonter ça avec des signaux Boost?

  10. #30
    Expert confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2012
    Messages
    1 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2012
    Messages : 1 711
    Points : 4 442
    Points
    4 442
    Par défaut
    Citation Envoyé par fanfouer Voir le message
    C'est un peu un problème dans ce cas.
    Je n'aime pas trop modifier mes interfaces publics de cette façons, sachant que les signaux/slots ne sont pas implémentables dans d'autres langages.
    Au final c'est une implémentation du pattern observateur, donc faisable dans "tous" les langages, mais tu devras peut être refaire la méchanique interne des signaux/slots en fonction du langage.

  11. #31
    Membre régulier
    Profil pro
    Étudiant
    Inscrit en
    Janvier 2008
    Messages
    253
    Détails du profil
    Informations personnelles :
    Localisation : France, Haute Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Janvier 2008
    Messages : 253
    Points : 84
    Points
    84
    Par défaut
    Très bien merci.

    En effet c'est transposable, utilisé et pas trop compliqué encore.

    Il ne reste plus que le cas que je soulève concernant mon client HTTP.
    Je n'arrive pas à le conformer à ce design là.

    Bonne soirée.

  12. #32
    Membre émérite
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Points : 2 799
    Points
    2 799
    Par défaut
    Ca n'est pas encore parfait car si je lance deux requêtes asynchrones en même temps, le même callback sera appelé pour le success des deux requête alors que je peux vouloir appeler deux callbacks différents pour chacune des deux requêtes.
    En fait, tu connectes au niveau des instances. Donc si tu as deux requêtes différentes, tu peux les connecter à deux callbacks différentes.

    Par exemple (à la syntaxe près, je fais ça de mémoire et ça fait longtemps que je n’ai plus touché aux signaux boost) :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    HTTPRequest request;
    request.connectSuccess(boost::bind(this, &HTTPClient::getSuccessHandler));
    HTTPRequest autreRequest;
    autreRequest.connectSuccess(boost::bind(this, &HTTPClient::postSuccessHandler));
    …
    Il y a un truc que je ne comprends pas dans ton architecture : c’est quoi le rôle du client exactement ? C’est une factory de request ? C’est un client spécialisé ?

    Parce que de la réponse à ces questions dépend la solution .

  13. #33
    Membre régulier
    Profil pro
    Étudiant
    Inscrit en
    Janvier 2008
    Messages
    253
    Détails du profil
    Informations personnelles :
    Localisation : France, Haute Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Janvier 2008
    Messages : 253
    Points : 84
    Points
    84
    Par défaut
    Citation Envoyé par white_tentacle Voir le message
    En fait, tu connectes au niveau des instances. Donc si tu as deux requêtes différentes, tu peux les connecter à deux callbacks différentes.

    Par exemple (à la syntaxe près, je fais ça de mémoire et ça fait longtemps que je n’ai plus touché aux signaux boost) :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    HTTPRequest request;
    request.connectSuccess(boost::bind(this, &HTTPClient::getSuccessHandler));
    HTTPRequest autreRequest;
    autreRequest.connectSuccess(boost::bind(this, &HTTPClient::postSuccessHandler));
    …
    Si les requêtes sont asynchrones (et elles le seront) cela ne va pas fonctionner pour des raisons évidentes de chronologie.

    Il y a un truc que je ne comprends pas dans ton architecture : c’est quoi le rôle du client exactement ? C’est une factory de request ? C’est un client spécialisé ?
    L'objet HTTPRequest n'est qu'une aide interne pour le client HTTP, les instances de cette classe ne sont pas accessibles depuis l'extérieur de ce client. C'est pour ça d'ailleurs qu'elle n'apparait pas dans le code d'exemple de mon précédent post.

    Le rôle du client est de fournir aux utilisateurs qui en ont besoin des informations sur des ressources HTTP distantes.
    On peut très bien demander le contenu de deux ressources différentes depuis la même instance du client et demander à ce que deux callbacks différents soient activés.
    Lors de chacun des deux appels, une instance différente de HTTPRequest sera créée, interne à la méthode du client appelée.

    Et là je donne un cas simple. Ma classe de base HTTPClient est régulièrement étendue pas d'autres pour formaliser des échanges complexes sur plusieurs API. Il faut donc que les callbacks fixés par l'utilisateur du client traversent ces différents niveaux en cas de surcharge des méthodes et d'appel à Super.
    Ma solution est ici tout à fait adaptée puisque là encore il suffit de transmettre le gestionnaire de callback plutôt que toutes les fonctions séparement.

  14. #34
    Membre émérite
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Points : 2 799
    Points
    2 799
    Par défaut
    Citation Envoyé par fanfouer Voir le message
    Si les requêtes sont asynchrones (et elles le seront) cela ne va pas fonctionner pour des raisons évidentes de chronologie.
    Je ne te suis pas du tout là-dessus. L’asynchronisme n’a rien à voir là-dedans, peu importe la chronologie. Ce qui importe, c’est que les traitements effectués par les callbacks soient indépendant (sinon, oui, tu auras potentiellement un problème).

    L'objet HTTPRequest n'est qu'une aide interne pour le client HTTP, les instances de cette classe ne sont pas accessibles depuis l'extérieur de ce client. C'est pour ça d'ailleurs qu'elle n'apparait pas dans le code d'exemple de mon précédent post.

    Le rôle du client est de fournir aux utilisateurs qui en ont besoin des informations sur des ressources HTTP distantes.
    On peut très bien demander le contenu de deux ressources différentes depuis la même instance du client et demander à ce que deux callbacks différents soient activés.
    Lors de chacun des deux appels, une instance différente de HTTPRequest sera créée, interne à la méthode du client appelée.

    Et là je donne un cas simple. Ma classe de base HTTPClient est régulièrement étendue pas d'autres pour formaliser des échanges complexes sur plusieurs API. Il faut donc que les callbacks fixés par l'utilisateur du client traversent ces différents niveaux en cas de surcharge des méthodes et d'appel à Super.
    Ma solution est ici tout à fait adaptée puisque là encore il suffit de transmettre le gestionnaire de callback plutôt que toutes les fonctions séparement.
    Change « traversent les différents niveaux » par « connaissent la soupe interne », et tu verras le problème que j’ai avec cette architecture (et pourquoi il y a effectivement des problèmes à la transposer telle quelle, je n’avais pas compris ça au départ et du coup t’ai donné une direction différente, qui supposait une plus grande séparation).

    Un schéma classique, c’est d’avoir effectivement une factory de requêtes. Ensuite, tu enregistres tes handlers au niveau de la requête, et tu envoies la sauce. Là, tu voudrais tout faire en une étape, pour tout type de requête, et c’est là que le bât blesse : comment veux-tu garantir, avec ce système, que les handlers que tu passes sont compatibles avec les signaux émis ? Ça impose de connaître l’objet requête, mais toi tu voudrais le garder asqué (mais tout de même connu, puisque tu sais quels handlers lui passer…).

  15. #35
    Membre régulier
    Profil pro
    Étudiant
    Inscrit en
    Janvier 2008
    Messages
    253
    Détails du profil
    Informations personnelles :
    Localisation : France, Haute Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Janvier 2008
    Messages : 253
    Points : 84
    Points
    84
    Par défaut
    Citation Envoyé par white_tentacle Voir le message
    Je ne te suis pas du tout là-dessus. L’asynchronisme n’a rien à voir là-dedans, peu importe la chronologie. Ce qui importe, c’est que les traitements effectués par les callbacks soient indépendant (sinon, oui, tu auras potentiellement un problème).
    Je ne suis pas de cet avis, mais j'ai en plus mal compris ton exemple.
    Pour moi, la requête est lancée dès que tu appelles getXXXXFromServer() sur le client. Si tu connectes x callbacks, tu ne sais pas quand le retour va avoir lieu donc tu vas potentiellement en appeler plusieurs alors que je ne veux en appeler d'un seul.

    En effet avec une factory, puis un "send()" ca marche, je m'incline et je suis d'accord avec la fin de ton post.

    Néanmoins, comment faire quand le client a aussi une valeur ajoutée dans le traitement des données?
    La factory ne fait que préparer la requête et la renvoi à l'utilisateur qui va écouter ses signaux. La plupart du temps j'utilise ces clients pour factoriser mes traitements.

    Merci pour ces indications, ca progresse!

  16. #36
    Membre émérite
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Points : 2 799
    Points
    2 799
    Par défaut
    En fait, plus que des observateurs, tes callbacks sont aussi des visiteurs, voire des politiques.

    Ça ne change pas grand chose dans le principe, si ce n’est que tes signaux vont devoir avoir comme paramètre un objet qui permet derrière d’intervenir sur la requête.

    Les signaux servent à deux choses :
    - alerter d’un évènement
    - passer des données

    Comme les données peuvent être une référence vers quelque chose, tu peux t’en servir pour effectuer des traitements. Les signaux ne sont pas asynchrones : ton client ne fait donc rien tant que le client du signal n’a pas terminé son travail : il peut donc tout à fait altérer des données qui seront ensuite utilisées dans la requête.

    Prenons un exemple : ta requête échoue. Elle pourrait réessayer, lever une exception, renvoyer une erreur… Disons que par défaut, elle lève une erreur. Mais avant cela, elle envoie un signal(bool& abortRequest, …). Dans le handler, tu vas pouvoir :
    - compter le nombre d’échecs, et arrêter au bout de 3
    - afficher une fenêtre à l’utilisateur pour lui demander s’il faut réessayer
    - écrire un log…
    du moment que tu renseignes derrière le paramètre abortRequest, ta classe requête va savoir quel comportement adopter.

  17. #37
    Membre régulier
    Profil pro
    Étudiant
    Inscrit en
    Janvier 2008
    Messages
    253
    Détails du profil
    Informations personnelles :
    Localisation : France, Haute Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Janvier 2008
    Messages : 253
    Points : 84
    Points
    84
    Par défaut
    Je suis tout à fait d'accord et c'est en quelque sorte ce que je souhaite faire.

    Sauf que là, je suis obligé de copier/coller ça partout, à chaque utilisation.
    Ce qui serait bien c'est de le mettre dans la factory de la requête.

    Si je renvoie la requête sans l'avoir lancée, comment faire?

  18. #38
    Membre émérite
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Points : 2 799
    Points
    2 799
    Par défaut
    Tu peux passer le handler directement dans la factory, comme n’importe quel paramètre. boost te fournit les types qui vont bien. De plus, les paramètres sont nullables (attention toutefois à tester la nullité avant de connecter, sig.connect(NULL) fonctionne mais te donnera une erreur à l’appel de sig()).

    Après, comment fonctionne ta factory ?

    Parce que je suppose qu’en fonction de l’appel que tu fais, l’objet renvoyé est différent. Et que tu fais des appels différents pour chaque type de requête.

    Du coup, tu peux effectivement rajouter à chaque méthode la liste des signaux connectables en paramètre, mais tu vas avoir une liste potentiellement longue. L’inconvénient majeur étant alors que chaque ajout d’un signal va t’amener à revoir ta factory (pas tout à fait : il y a une solution à base de variadic template pour éviter cela). Il me semble que c’est ce que tu veux éviter, d’où l’idée de renvoyer un objet et de faire les connexions en dehors de la factory.

    Tu peux poster l’interface (ou juste un exemple) de ta factory, que je me fasse une idée précise ?

  19. #39
    Membre régulier
    Profil pro
    Étudiant
    Inscrit en
    Janvier 2008
    Messages
    253
    Détails du profil
    Informations personnelles :
    Localisation : France, Haute Savoie (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Janvier 2008
    Messages : 253
    Points : 84
    Points
    84
    Par défaut
    Bonsoir,

    Tu as bien cerné le problème : je ne veux pas mettre d'handler dans mes signatures pour ne pas avoir à tout modifier en cas d'ajout d'un signal.

    Les méthodes du client qui géraient avant toute la requête vont donc être converties en factory plus dignes de ce nom.
    Elles vont créer l'instance de la requête, la paramétrer puis la renvoyer à l'extérieur. Le métier qui servait à pré-traiter la réponse du serveur va migrer dans un handler.

    Le soucis c'est que j'aimerais que le handler de pré-traitement, déclaré par le client lui-même altère la réponse du serveur avant que celui déclaré par l'utilisateur en prenne connaissance (sinon on fait pas de pré-traitement).


    Voici un code qui présente ce à quoi mon client va ressembler après le changement d'architecture:
    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
     
    class HTTPClient {
    	// ------------------------------------
    	// Attributs
    	// ------------------------------------
    	// Constantes
    	public: const static std::string TYPE_SYNC;
    	public: const static std::string TYPE_ASYNC;
     
    	// Privées
    	private: stc_commons::config::ConfigManager *_CFG;
    	private: std::string _type;
     
    	// ------------------------------------
    	// Getters / Setters
    	// ------------------------------------
    	public: const stc_commons::config::ConfigManager *getCFG() const;
    	public: void setCFG(stc_commons::config::ConfigManager *cfg);
    	public: const std::string &getType () const;
    	public: void setType (const std::string &std);
     
    	// ------------------------------------
    	// Méthodes
    	// ------------------------------------
    	public: HTTPClient (stc_commons::config::ConfigManager *cfg, const std::string &type);
    	public: HTTPClient(cont HTTPClient &other);
    	public: stc_commons::net::URL getServiceURL (const std::string &url_name, const std::map<std::string, std::string> &data) const;
     
    	public: HTTPRequest *getRecordData (const std::string &id, const std::map<std::string, std::string> &url_fields, const std::map<std::string, std::string> &headers, const std::map<std::string, std::string> &data);
    	public: HTTPRequest *search (const std::string &q, const std::map<std::string, std::string> &url_fields, const std::map<std::string, std::string> &headers, const std::map<std::string, std::string> &data, bool catchDeleted);
    	public: HTTPRequest *publish (const std::string &id, const std::string &record_mime, const std::string &record, const std::map<std::string, std::string> &url_fields, const std::map<std::string, std::string> &headers, const std::map<std::string, std::string> &data);
    	public: HTTPRequest *deleteRecord (const std::string &id, const std::map<std::string, std::string> &url_fields, const std::map<std::string, std::string> &headers, const std::map<std::string, std::string> &data);
    };
    Quelques indications:
    • Les 4 méthodes renvoient un pointeur à cause du polymorphisme. J'ai aussi HTTPSRequest et le protocole indiqué dans l'URL défini quel type doit être instancié par le client.
    • url_fields contient des paires de clés/valeur pour compléter des champs variables dans l'URL. Typiquement : http://www.example.com/crud/%_CLASS_%/%_ID_% sera complété par {"_CLASS_":"test", "_ID_":"1"} et donnera l'URL http://www.example.com/crud/test/1 pour faire cette requête.
    • data contient le même type de structure, sauf que les paires sont envoyées au serveur. Si la méthode HTTP est GET ou DELETE, ces paires seront ajoutées dans le query string de l'URL ou si POST et PUT elles seront ajoutée au corps de la requête avec l'en-tête Content-type: application/x-www-form-urlencoded
    • headers contient des paires ajoutées directement aux en-tetes de la requête.
    • L'URL est connue du client suivant la configuration communiquée au constructeur par le paramètre *cfg


    Pour faire donc une requête GET sur un document JSON je pense donc m'y prendre comme ça:
    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
     
    // Instanciation du client
    HTTPClient client (&cfg, HTTPClient::TYPE_SYNC); // Client HTTP configuré et en mode synchrone
     
    // Données pour la requete
    std::map<std::string, std::string> hdrs;
    std::map<std::string, std::string> url_fields;
    std::map<std::string, std::string> data;
     
    // Factory requete
    HTTPRequest *req = client.getRecordData ("1", url_fields, hdrs, data);
     
    // Attachement des signaux
    req.onSuccess(...);
    req.onError(...);
     
    // Envoi requete
    req.send();
    Cela parait-il mieux?

  20. #40
    Membre émérite
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Points : 2 799
    Points
    2 799
    Par défaut
    Ton code me va, à quelques remarque près :

    - quand tu renvoies un pointeur, préfère toujours un pointeur intelligent à un pointeur nu. Là se pose la question de la responsabilité de la requête ? Qui doit se charger de la libérér ? (oui, la question ne se posait pas en javascript, mais ici tu n’as pas le choix)

    - ton système est hybride entre une couche bas niveau (transport) et une couche service. Quel est le sens, dans une requête publish, de tripatouiller les en-têtes ? Personnellement, j’empilerai trois couches (parce que c’est comme ça que ça se passe dans la vraie vie) :
    - une première couche purement HTTP. En fait, pour celle-ci il est probable que tu aies déjà une librairie pour ça.
    - une deuxième couche qui s’occupe uniquement du json, et qui utilise la première.
    - une troisième couche applicative, qui mappe les services exposés par ton json.

    À chacune de ces couches, il y a des signaux émis, et des handlers exécutés. Chaque handler va à son tour émettre un nouveau signal qui sera géré par la couche supérieure.

    Au final, le client du service n’a même pas à savoir que le service est en http / json : ce n’est pas son soucis. Son soucis est que le service se conforme à l’interface.

    Je ne suis pas sûr que ça réponde à ta question . Mais ça me semble plus sain de segmenter les responsabilités.

+ Répondre à la discussion
Cette discussion est résolue.
Page 2 sur 3 PremièrePremière 123 DernièreDernière

Discussions similaires

  1. [VB6] Sauvegarder une collection d'objets
    Par Sayagh dans le forum VB 6 et antérieur
    Réponses: 7
    Dernier message: 19/09/2003, 11h58
  2. [VB6] la collection controls
    Par tomnie dans le forum VB 6 et antérieur
    Réponses: 3
    Dernier message: 30/04/2003, 17h03
  3. Comment créér une collection sous Delphi
    Par PsyKroPack dans le forum Langage
    Réponses: 6
    Dernier message: 11/02/2003, 13h20
  4. [VB6] Modifier la clé d'un élément d'une collection
    Par Ricou13 dans le forum VB 6 et antérieur
    Réponses: 3
    Dernier message: 21/11/2002, 14h49

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