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 :

Architecture: Isolation ou transitivité des dépendances


Sujet :

C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre éprouvé
    Avatar de benjani13
    Homme Profil pro
    Consultant en sécurité
    Inscrit en
    Février 2010
    Messages
    616
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Consultant en sécurité

    Informations forums :
    Inscription : Février 2010
    Messages : 616
    Par défaut Architecture: Isolation ou transitivité des dépendances
    Bonjour,

    Je réalise une application C++ dans laquelle j’expérimente quelques concepts et bonnes pratiques d'architecture. L'application permet d'afficher des objets 3D et de les manipuler au travers de divers GUIs. J'ai découpé mon application en trois gros packages que je souhaite les plus indépendants les uns des autres, notamment au niveau des dépendances. Voici un schéma présentant mon découpage:

    Nom : schema.png
Affichages : 322
Taille : 15,7 Ko

    On retrouve d'un coté un package de rendu 3D qui tire sur OpenGL et GLM (lib de mathématiques pour la 3D), et de l'autre un package qui fournit divers classes permettant d'organiser simplement la GUI, tirant sur la bibliothèque de fenêtrage GLFW et la bibliothèque de GUI Dear IMGUI.

    L'application utilise donc ces deux packages. Le package applicatif définit les divers éléments de la GUI et la logique applicative. Je développe ça tout seul mais je fais comme si ces packages étaient développés par des équipes différentes, avec les contraintes et la rigueur que cela apporte.

    Ma réflexion porte sur la localité des dépendances. Est-ce que chaque dépendances (les classes et fonctions qu'elles fournissent) sont purement locales aux packages et ne devraient pas être utilisé en dehors, ou si on considère que les dépendances sont héritées par le package applicatif. Évidemment, j'ai tout de suite tendance à penser que le choix des dépendances fait partie de l'implémentation de ces packages et que donc cela ne regarde que le package en question, d'autant que cela permet à un package donné de changer son implémentation, et même de libs, tant qu'il remplit son contrat vis à vis des autres packages.

    J'ai cependant deux exemples qui me pose des difficultés.

    Le premier: le package GUI Helpers tire sur la lib de GUI Dear IMGUI. En gros j'ai créer des factories qui permettent à l'application de créer facilement des fenêtres et des widgets. Cependant, le package applicatif doit définir le contenu de ces widgets, et donc doit utiliser directement la lib Dear IMGUI (pour créer un bouton, des labels, des zones d'édtions, etc). Doit on considérer que les deux packages (Application et GUI Helpers), dépendent chacun de leur côté de Dear IMGUI (comme je l'ai représenté sur le schéma), ou que le package Application dépend de Dear IMGUI par transitivité?

    Le second: Dans le package 3D Rendering, j'utilise des types de données fournit par la lib mathématiques GLM, par exemple glm::vec3 qui est un vecteur 3D, pour représenter la position d'un objet. Dans ma GUI je souhaite que l'utilisateur puisse définir la position d'un objet. J'ai donc un panel avec trois valeur éditables (X, Y et Z). Jusque là j'utilise le type glm::vec3 au sein même de l'application, et je map bêtement les valeurs éditables aux composantes X, Y et Z d'un glm::vec3. Quand une valeur est modifiée, je passe ce vecteur au package Rendering via la méthode adéquate pour mettre à jour la position de l'objet. Problème, si dans le package Rendering je décide de passer à une autre lib mathématique, l'application sera cassée et devra être en partie réécrite. Vaut il mieux selon vous, dans la partie applicative, utiliser des types de données génériques (trois float, un tableau de float, ...) que le package Rendering sera chargé de convertir dans le bon type de donnée utilisé en interne?

    Je vous remercie pour votre aide dans ma réflexion.

  2. #2
    Membre Expert
    Avatar de Pyramidev
    Homme Profil pro
    Tech Lead
    Inscrit en
    Avril 2016
    Messages
    1 513
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Tech Lead

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 513
    Par défaut
    Bonjour,

    Citation Envoyé par benjani13 Voir le message
    Vaut il mieux selon vous, dans la partie applicative, utiliser des types de données génériques (trois float, un tableau de float, ...) que le package Rendering sera chargé de convertir dans le bon type de donnée utilisé en interne?
    Oui.

    Normalement, il y a une partie du code qui ne sait pas comment les données sont affichées. Elle ne sait pas si les données sont affichées dans une console, dans une fenêtre graphique, dans un navigateur ou dans un hologramme comme dans Star Wars. Du coup, le jour où les données seront affichées autrement, cette partie du code n'aura pas besoin d'être changée et les tests unitaires associés n'auront pas besoin d'être réécrits non plus.
    Donc la partie de ton code qui ne sait pas comment les données sont affichées ne doit pas avoir une dépendance forte envers OpenGL. Par exemple, elle ne doit pas directement utiliser les fonctions publiques de glm::vec3.

    Citation Envoyé par benjani13 Voir le message
    Cependant, le package applicatif doit définir le contenu de ces widgets, et donc doit utiliser directement la lib Dear IMGUI (pour créer un bouton, des labels, des zones d'édtions, etc).
    Je ne sais pas à quel point tu veux permettre à l'utilisateur de personnaliser l'affichage, donc je ne sais pas si cela va correspondre à ton besoin, mais voici la technique du Presenter : extrait du livre Clean Architecture (Robert Cecil Martin) :
    The Presenter is the testable object. Its job is to accept data from the application and format it for presentation so that the View can simply move it to the screen. For example, if the application wants a date displayed in a field, it will hand the Presenter a Date object. The Presenter will then format that data into an appropriate string and place it in a simple data structure called the View Model, where the View can find it.
    If the application wants to display money on the screen, it might pass a Currency object to the Presenter. The Presenter will format that object with the appropriate decimal places and currency markers, creating a string that it can place in the View Model. If that currency value should be turned red if it is negative, then a simple boolean flag in the View model will be set appropriately.
    Every button on the screen will have a name. That name will be a string in the View Model, placed there by the presenter. If those buttons should be grayed out, the Presenter will set an appropriate boolean flag in the View model. Every menu item name is a string in the View model, loaded by the Presenter. The names for every radio button, check box, and text field are loaded, by the Presenter, into appropriate strings and booleans in the View model. Tables of numbers that should be displayed on the screen are loaded, by the Presenter, into tables of properly formatted strings in the View model.
    Anything and everything that appears on the screen, and that the application has some kind of control over, is represented in the View Model as a string, or a boolean, or an enum. Nothing is left for the View to do other than to load the data from the View Model into the screen.
    Le Presenter génère un View Model qui sait à peu près comment les données seront affichées, mais pas complètement. Il connaît les chaînes de caractères qui peuvent changer d'une exécution à l'autre. Dans un tableau, il sait quelles chaînes seront affichées dans chaque ligne et chaque colonne. Dans une boîte combinée, il sait quels choix seront affichés et dans quel ordre. Mais il ne contient aucun composant graphique et ne sait d'ailleurs même pas quelle bibliothèque graphique est utilisée. On peut alors écrire des tests unitaires sur des règles d'affichage indépendamment de la bibliothèque graphique.
    Du coup, le code par dessus qui connaît la bibliothèque graphique est minimal et, le jour où on souhaitera changer de bibliothèque graphique, ce changement sera possible.

  3. #3
    Membre éprouvé
    Avatar de benjani13
    Homme Profil pro
    Consultant en sécurité
    Inscrit en
    Février 2010
    Messages
    616
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Consultant en sécurité

    Informations forums :
    Inscription : Février 2010
    Messages : 616
    Par défaut
    Merci pour ta réponse.

    Citation Envoyé par Pyramidev Voir le message
    Je ne sais pas à quel point tu veux permettre à l'utilisateur de personnaliser l'affichage, donc je ne sais pas si cela va correspondre à ton besoin, mais voici la technique du Presenter : extrait du livre Clean Architecture (Robert Cecil Martin) :

    Le Presenter génère un View Model qui sait à peu près comment les données seront affichées, mais pas complètement. Il connaît les chaînes de caractères qui peuvent changer d'une exécution à l'autre. Dans un tableau, il sait quelles chaînes seront affichées dans chaque ligne et chaque colonne. Dans une boîte combinée, il sait quels choix seront affichés et dans quel ordre. Mais il ne contient aucun composant graphique et ne sait d'ailleurs même pas quelle bibliothèque graphique est utilisée. On peut alors écrire des tests unitaires sur des règles d'affichage indépendamment de la bibliothèque graphique.
    Du coup, le code par dessus qui connaît la bibliothèque graphique est minimal et, le jour où on souhaitera changer de bibliothèque graphique, ce changement sera possible.
    Et bien justement j’expérimente des architectures basées sur un Presenter et un View Model ou dans ces idées là. Après avoir étudié divers variantes (MVP, MVVM, etc), je suis parti sur Presentation Model. Comme tu le dis j'ai un "Presentation Model" qui contient la logique et l'état de la vue, la vue étant vraiment relégué au minimal. Cela me permet en effet de tester presque tous le code sans même instancier l'UI, d'avoir des vues alternative ou bien de changer facilement le lib de GUI effectivement.

    Cela ne résout pas la partie GUI cependant, je vais expliquer plus clairement cette partie.

    J'utilise la lib Dear IMGUI qui est une lib en mode immédiat, c'est à dire qu'il n'y a pas de classes représentant les éléments de l'UI, pas de structure gardée en mémoire. Mon package "GUI Helpers" ajoute une légère surcouche, définissant des classes Window, Widget, Panel. La classe Panel est abstraite et contient simplement une méthode Display() à surcharger. Un Panel peut être accroché à un Widget ou à un autre Panel, le Widget lui même pouvant être accroché à une Window. Donc ce sont juste des conteneurs génériques me permettant de manipuler facilement les éléments de l'UI (ouvrir une fenêtre, y insérer des Widgets, faire passer un Widget d'une fenêtre à l'autre, etc). Ces conteneurs appel des fonctions de Dear IMGUI (charger/initialiser la lib, démarrer/terminer la définition d'une fenêtre, d'un widget, d'un panel, etc). La boucle d'affichage de la GUI, gérée par ce package, va simplement boucler sur chaque Window, qui bouclera elle même sur chaque Widget, qui appellera la méthode Display() de chaque Panel.

    Dans l'application elle même, je créer donc des classes héritant de Panel et implémentant chacun la méthode Display() avec les appels adéquats à Dear IMGUI (afficher un bouton, un label, etc). Chaque Panel (donc chaque Vue) fait le lien avec avec le Presentation Model (l'équivalent du View Model dans le pattern que tu présente), qui se trouve au sein du package applicatif aussi. En appelant mon framework GUI Helpers je créer une fenêtre, je créer un widget. J'accroche mes panels customs au widget, j'accroche le widget à la fenêtre, et c'est tout bon. Le framework GUI Helpers proposent aussi des interfaces à implémenter en plus par les conteneurs pour recevoir des évènements (souris, clavier, etc).

    Le package GUI Helper est donc juste une surcouche qui fournit des conteneurs et des outils, mais en aucun cas il ne définit le contenu de l'UI ou la logique applicative sous-jacente. Ce package doit d'ailleurs être réutilisable pour d'autres applications. Il permet en gros à l'application de ne pas se préoccuper de comment marche la GUI. Tout est délégué au package GUI Helper (chargement des libs de fenêtrage et de GUI, création des fenêtres, mécanismes de gestion des évènements).

    Le problème est qu'il reste forcément une chose, la création du contenu de l'UI, qui est forcément faite par l'application, et qui l'oblige donc à appeler des fonctions de la lib de GUI. Je pourrais un peu tricher en faisant un wrapper à chaque fonction de création d'élément d'UI (ImGui::Button(...), ImGui::Text(...), ImGui::Checkbox(...)), ce qui libèrerai l'Application de tous liens direct avec la lib de GUI. Cependant je trouve cela un peu overkill et je ne suis pas sur que cela donne à l'Application une vrai indépendance vis à vis de la lib de GUI utilisée. Les paramètres des wrapper serait calqués sur les fonctions de DearIMGUI. De fait le changement pour une autre bibliothèque me forcerait surement à changer les signatures des wrapper, et donc à modifier l'Application. Peut être est-ce justement ce que dit de faire la définition de Presenter que tu a cité? Utiliser des objets génériques (Date, Currency, etc) plutôt que des appels directs à des éléments d'UI?

    Donc c'est le fait d'avoir un package Application qui dépend à la fois directement de DearImGui (définition du contenu de l'UI) et à la fois indirectement (via la surcouche proposée par le package GUI Helpers) qui me laisse dubitatif.

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

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

    Informations forums :
    Inscription : Février 2005
    Messages : 5 488
    Par défaut
    Ça manque de couche tout ça.

    Je trouve qu'il y a une énorme faille dans votre conception : le manque de niveau d'abstraction.

    Ce qui engendre le fait que votre module "Application" fait bien trop de chose.

    Vous devez clairement le scindé en au moins 2 autres modules.

    La couche business implémentant les règles métier et qui ne doit pas changer quand on change de manière d'interagir avec (Gui, console, socket, etc...) ne doit pas dépendre de "Dear IMGUI" ou tous autres bidules à base de fenêtre ou bling-bling clignotant.

    Donc, la majorité de votre code "utile" sera dans un module ne dépendant de rien (ou de Win32, mais rien en rapport avec le GDI ou tout autre API d'IHM ou de console).

    Le reste du code, en charge de la création "générique" de l'IHM sera dans son propre module, quitte à dépendre de "Dear IMGUI" et à devoir le migrer en cas de portage.

Discussions similaires

  1. [Metrics] isoler un package dans le graphe des dépendances
    Par igor24 dans le forum Eclipse Java
    Réponses: 0
    Dernier message: 16/06/2008, 11h31
  2. Architecture de bases : Ensemble des objets...
    Par JeremieT dans le forum Access
    Réponses: 3
    Dernier message: 28/10/2005, 10h41
  3. conception : table des dépendances
    Par gregolak dans le forum Langage SQL
    Réponses: 12
    Dernier message: 09/10/2005, 16h10
  4. Réponses: 17
    Dernier message: 04/02/2005, 12h05
  5. Recherche des dépendances des modules
    Par slowpoke dans le forum Mandriva / Mageia
    Réponses: 9
    Dernier message: 11/12/2003, 08h49

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