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

avec Java Discussion :

Ma manière de développer


Sujet :

avec Java

  1. #1
    Nouveau membre du Club
    Homme Profil pro
    Apprenti
    Inscrit en
    Octobre 2014
    Messages
    70
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 29
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Apprenti

    Informations forums :
    Inscription : Octobre 2014
    Messages : 70
    Points : 35
    Points
    35
    Par défaut Ma manière de développer
    Bonjour,

    Je termine actuellement mon BTS informatique option développement et pour mon projet de fin d'année j'ai réalisé une application de facturation en Java. Celle-ci permet la gestion de factures et de prestations via une IHM et l'utilisation de tableaux grâce à des modèles redéfinis. Voilà pour l'aperçu de l'application.

    Si vous aviez le temps et la gentillesse de regarder mon code j'aimerais avoir des retours afin de savoir si mon code est tout de même propre, si j'ai bien assimilé la notion d'objet car je constate que je fais souvent directement et très rarement
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
     Classe maClasse = new Classe();
    ce qui me pousse à me demander si j'ai bien compris la notion de langage orienté objet.

    D'un autre côté, est-ce que mes variables sont bien encapsulées et est-ce que, à force de vouloir découper, je ne découpe peut être pas trop en classes ?

    Voilà mes principales interrogations, si vous avez la moindre remarque à faire n'hésitez pas, mon but est de comprendre mes erreurs afin de ne plus les refaire.

    La pièce jointe contient le projet Java, la base de données SQL ainsi que les procédures et triggers. Je n'ai pas mis l'API utilisée pour la gestion des PDF car cela faisait un fichier trop volumineux.

    Merci pour votre aide
    Fichiers attachés Fichiers attachés

  2. #2
    Membre chevronné
    Avatar de eulbobo
    Homme Profil pro
    Développeur Java
    Inscrit en
    Novembre 2003
    Messages
    786
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur Java

    Informations forums :
    Inscription : Novembre 2003
    Messages : 786
    Points : 1 993
    Points
    1 993
    Par défaut
    J'ai regardé un peu, et effectivement, ça ressemble plus à de la programmation séquentielle/évenementielle qu'à de la programmation orientée objet. Chaque fois que tu fais un new (Erreur, Menu, etc...), chaque instance ainsi créée n'a aucun lien avec les autres (sauf pour les éléments statiques et publiques, ce qui est dangereux)!
    Chaque classe gère elle-même son propre affichage.

    Un objet, c'est ce que tu vois devant toi.
    Une tasse, c'est un Objet. La tasse a des propriétés, comme la taille, le poids, la couleur... La contenance parce que c'est un Contenant (qui peut faire l'objet d'une classe générique ou d'une interface).
    Un téléphone c'est un objet, avec des propriétés comme la taille, le poids, la couleur... Il a des Touches (qui peuvent aussi faire l'objet d'une classe définissant, la forme, la couleur, l'effet...)
    Une facture papier, c'est un objet, avec des valeurs renseignées comme la date, le numéro, et des tableaux contenant des Lignes, et pour chaque Ligne il y a des propriétés spécifiques


    En soit, ta solution, si elle marche c'est tant mieux mais tu perds la notion de responsabilité qui est une des bases de la programmation orientée objet : quand tu crées une classe, tu dois tendre à ce qu'elle n'ait qu'une seule responsabilité. Ici, chacune gère l'affichage, la logique applicative, la gestion des erreurs et la navigation (ou les évènements), le requétage...

    Par exemple, ta Classe "Requete" a l'air d'avoir en charge toutes les requêtes de ton application, avec des duplications de codes. Si ta classe s'appelle Requete, qu'elle se charge effectivement d'exécuter les requêtes... Et uniquement ça ! Pas des requêtes spécifiques à des parties du "métier"




    Essayes de redécouper ton application en 3 "couches".
    Une première couche à laquelle tu dédie le requêtage/récupération de données depuis ta base de données
    Une deuxième dans laquelle tu gère la logique applicative
    Une troisième dans laquelle tu ne gère QUE l'affichage.
    Pour faire court sur ce qu'il faut que tu fasse :
    - Dans facture, découpe la partie affichage de la partie récupération de données. Au passage, essayes de créer un Objet qui représente une facture (à savoir, un numéro, des lignes de factures, ...), un Objet qui représente UNE Ligne de facture (vu que tu as besoin d'une liste de lignes de factures), ... Bref, crée des structures qui correspondent à ce que tu manipules comme si tu avais du papier. Tu ne manipules pas que des morceaux de papiers, tu manipules une grande feuille sur laquelle il y a des informations
    - Utilise tes objets imbriqués les uns avec les autres pour gérer l'affichage (avec une classe qui se chargera d'afficher spécifiquement une facture... Ou une liste de factures...)

    Sinon, petits trucs :
    - une connexion à une base de données, ça se ferme toujours ! Ou il faut utiliser un pool de connexion. Si tu laisses des connexions ouverte, tu risques de ne plus pouvoir te connecter à ta base au bout d'un moment.
    - ton objet connexion est un faux singleton: si quelqu'un refait un new ConnexionBDD à un moment, tu perds tout (vu que ton lien vers la connexion est déclaré en statique, donc partagée par toutes les instances de connexion) ! Si tu ne veux qu'une seule connexion, il faut que tu implémentes correctement ton singleton.
    - si un élément dépend d'un autre (le menu par rapport au panel principal), il doit avoir une référence pour y être lié. Un menu n'a pas de sens s'il n'est pas lié à un Panel.
    - de la même façon, si c'est le panel principal qui doit gérer les erreurs, c'est lui qui doit les récupérer : faire une méthode afficherErreur(String text) dans ton panel principal, et appeler cette méthode partout quand tu as une erreur (en passant le bon message d'erreur)
    - MySQLIntegrityConstraintViolationException : on ne doit pas gérer les cas testables en utilisant les Exceptions. Les exceptions sont des défauts non prévus/prévisible qui arrivent à l'exécution. Un doublon en base, c'est prévisible avec une simple requête ! (accessoirement, si tu passes à Postgres derrière, ton code ne marchera plus)

  3. #3
    Nouveau membre du Club
    Homme Profil pro
    Apprenti
    Inscrit en
    Octobre 2014
    Messages
    70
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 29
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Apprenti

    Informations forums :
    Inscription : Octobre 2014
    Messages : 70
    Points : 35
    Points
    35
    Par défaut
    Tout d'abord, merci pour ta longue réponse !

    J'ai regardé un peu, et effectivement, ça ressemble plus à de la programmation séquentielle/évenementielle qu'à de la programmation orientée objet. Chaque fois que tu fais un new (Erreur, Menu, etc...), chaque instance ainsi créée n'a aucun lien avec les autres (sauf pour les éléments statiques et publiques, ce qui est dangereux)!
    Il me semblait bien que je n'exploitais pas bien les possibilités offertes par le langage objet en faisant simplement des "New Classe()". Je vais donc essayer de créer des objets et de retravailler dessus sans faire de New à chaque fois.

    En soit, ta solution, si elle marche c'est tant mieux mais tu perds la notion de responsabilité qui est une des bases de la programmation orientée objet : quand tu crées une classe, tu dois tendre à ce qu'elle n'ait qu'une seule responsabilité. Ici, chacune gère l'affichage, la logique applicative, la gestion des erreurs et la navigation (ou les évènements), le requétage...
    J'ai essayé justement de découper chacune des "pages" de mon application dans une classe spécifique. L'affichage de chaque "page" est gérée dans la classe en question et pour tout ce qui est requête je fais ensuite appel à un objet de type "Requête" depuis lequel j'appelle la méthode à qui je transmets la requête à effectuer. Si j'ai bien compris tu me dis donc que la solution que j'ai utilisé est inadaptée alors que j'ai justement essayé de faire ce que tu dis ici
    Par exemple, ta Classe "Requete" a l'air d'avoir en charge toutes les requêtes de ton application, avec des duplications de codes. Si ta classe s'appelle Requete, qu'elle se charge effectivement d'exécuter les requêtes... Et uniquement ça ! Pas des requêtes spécifiques à des parties du "métier"
    Ai-je donc mal compris ce que tu m'as expliqué ?

    Dans facture, découpe la partie affichage de la partie récupération de données
    C'est ce que j'ai justement voulu faire en créant une classe "PageFacture" et une classe "AfficheFacture" où "AfficheFacture" est chargé de récupérer les données depuis la base de données (en utilisant lui aussi un objet de type "Requete") puis de les mettre dans mon JTable. Ma classe "PageFacture" utilise alors un objet de type "AfficheFacture". Je pense néanmoins que tu parles d'une autre façon de découper mais je ne comprends pas laquelle.

    - de la même façon, si c'est le panel principal qui doit gérer les erreurs, c'est lui qui doit les récupérer : faire une méthode afficherErreur(String text) dans ton panel principal, et appeler cette méthode partout quand tu as une erreur (en passant le bon message d'erreur)
    La classe "Erreur" que j'ai crée ayant pour constructeur
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    public Erreur(String titre, String message)
    ne fait pas l'affaire ? J'appelle cette classe dès que je rencontre une erreur. Cette classe crée un JDialog modal qui affiche justement l'erreur en question.

    - MySQLIntegrityConstraintViolationException : on ne doit pas gérer les cas testables en utilisant les Exceptions. Les exceptions sont des défauts non prévus/prévisible qui arrivent à l'exécution. Un doublon en base, c'est prévisible avec une simple requête ! (accessoirement, si tu passes à Postgres derrière, ton code ne marchera plus)
    C'est vrai, j'avoue ne pas y avoir pensé, je me suis dis "Cool ils proposent de gérer les doublons de clés primaires" sans chercher plus loin !

    Cela fait beaucoup de questions et j'en ai conscience mais j'aimerais avoir le maximum de bonnes habitudes afin de bien apprendre à utiliser les possibilités et les normes du langage orienté objet.

  4. #4
    Membre chevronné
    Avatar de eulbobo
    Homme Profil pro
    Développeur Java
    Inscrit en
    Novembre 2003
    Messages
    786
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur Java

    Informations forums :
    Inscription : Novembre 2003
    Messages : 786
    Points : 1 993
    Points
    1 993
    Par défaut
    Pour ton objet Requete, le problème c'est qu'il se charge de faire des requêtes mais il renvoie des éléments de requête (ResultSet) : c'est à éviter si tu ne veux pas avoir de problèmes de connexion à ta base de données (avec des ressources non fermées)
    En gros, ton objet requête fait bien des requêtes, mais il ne va pas au bout de sa fonction : renvoyer des résultats. Pour l'instant, c'est une simple classe de type Helper qui ne fait que renvoyer le résultat brut de la requête. Pour l'améliorer tu dois faire en sorte que les objets ResultSet et Statement soient créés et fermés au sein des méthodes de Requete et n'en sortent jamais ! (et faut les fermer ensuite).

    Là tu te dis "oui, mais une fois que je récupère mon ResultSet, il faut bien que je puisse avoir un moyen de le transformer en un résultat qui ait un sens".
    Du coup, plusieurs possibilités :
    - Faire en sorte que ta classe Requete soit abstraite, déclarer une méthode abstraite de type protected qui s'appellera par exemple "convertResultSet", et ensuite, pour chaque utilisation de requête dont tu as besoin, créer une implémentation de Requete (RequeteFacture par exemple) qui ne fera qu'implémenter la conversion de ResultSet
    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
     
    public abstract class Requete {
    		/**
                     * Exécute une requête de type select et la renvoie sous forme de ResultSet
                     * @param requete
                     * @return result
                     */	
    public List requeteSelect(String requete){
    		List result = new ArrayList();
    		Connection connexion = ConnexionBDD.getInstance();// On récupère l'instance de notre connexion
    		try (Statement state = connexion.createStatement();
    			ResultSet rs = state.executeQuery(requete); ) {
     
    			result = convertResultSet(rs);
    		} catch(Exception e){
    			e.printStackTrace();
    		}
     
    		return result;
    	}
     
    	protected abstract List convertResultSet(ResultSet rs);
     
    //Autres méthodes
    }
    Ici je considère que tu es au moins en Java7, la notation try (Statement state = connexion.createStatement(); ResultSet rs = state.executeQuery(requete); ) permettant de fermer les ressources Statement et ResultSet dans l'ordre inverse de leur déclaration (donc d'abord rs, puis state) à la fin de l'exécution.

    Ensuite, tu peux encore améliorer les choses en déclarant un type générique à ta classe abstraite, ce qui te permettra d'être sûr de récupérer les bonnes instances d'objet dans ta liste

    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
     
    public abstract class Requete<T> {
    		/**
                     * Exécute une requête de type select et la renvoie sous forme de ResultSet
                     * @param requete
                     * @return result
                     */	
    public List<T> requeteSelect(String requete){
    		List<T> result = new ArrayList<T>();
    		Connection connexion = ConnexionBDD.getInstance();// On récupère l'instance de notre connexion
    		try (Statement state = connexion.createStatement();
    			ResultSet rs = state.executeQuery(requete); ) {
     
    			result = convertResultSet(rs);
    		} catch(Exception e){
    			e.printStackTrace();
    		}
     
    		return result;
    	}
     
    	protected abstract List<T> convertResultSet(ResultSet rs);
     
    //Autres méthodes
    }
    Et ton implémentation pour facture qui est alors
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    public class RequeteFacture extends Requete<Facture> {
     
    	protected List<Facture> convertResultSet(ResultSet rs){
    		// code pour transformer ton resultSet en liste de factures
    	}
    }
    A l'utilisation, tu n'auras qu'à appeler la méthode comme ça :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
    RequeteFacture rqFact = new RequeteFacture();
    List<Facture> listFactures = rqFact.requeteSelect("select...");

    Inconvénient de ce code : ta méthode de conversion dépend de la requête. Mais d'ailleurs, pourquoi les REQUETES sql ne sont PAS dans la classe REQUETE ?

    Modifions ça un petit peu...
    Dans ta classe PageAjoutFacture par exemple, tu fais la requête suivante :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    ResultSet resultat = req.requeteSelect("select intitule from ligue");
    Et si dans la classe RequeteFacture on rajoutait une méthode?

    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
     
    public class RequeteFacture extends Requete<Facture> {
     
    public List<String> getIntitulesLigne() {
    		List<String> result = new ArrayList<String>();
    		Connection connexion = ConnexionBDD.getInstance();
    		try (Statement state = connexion.createStatement();
    				ResultSet rs = state.executeQuery("select intitule from ligue");){
    			while (rs.next()){
    				result.add(rs.getString(1));
    			}
    		} catch (Exception e){
    			e.printStackTrace();
    		}
     
    		return result;
    	}
     
    	protected List<Facture> convertResultSet(ResultSet rs){
    		// code pour transformer ton resultSet en liste de factures
    	}
    }
    Ici, tu disposes d'une classe fille dont le rôle est d'interroger la base de données pour un domaine précis et de renvoyer des résultats. Et le fait de renvoyer des intitulés de lignes est propre au traitement d'une facture, ça n'a rien de générique (donc rien à faire dans une classe de niveau plus élevé)
    Avantage : le code SQL est regroupé dans les classes qui gèrent les accès et les requêtes à la base de données. Du coup, les interfaces n'ont plus à devoir manipuler des objets du niveau de la base de données (baisse du niveau de couplage)

    Dernière amélioration possible ici : utiliser le concept de composition d'objets avec des classes qu'on va appeler Transformer. Dont le but sera de transformer un ResultSet précis dans un résultat.

    Reprenons le code et ajoutons ça
    Le Transformer
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
    public interface Transformer<T>{
     
             // transformer une ligne de resultSet en un objet
             T transformRs(ResultSet rs) throws SQLException;
    }
    Dans Requete
    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
     
    public abstract class Requete<T> {
    		/**
                     * Exécute une requête de type select et la renvoie sous forme de ResultSet
                     * @param requete
                     * @return result
                     */	
    protected List<T> requeteSelect(String requete, Transformer<T> tr){// passage en protected, on ne l'appelle plus directement
    		List<T> result = new ArrayList<T>();
    		Connection connexion = ConnexionBDD.getInstance();// On récupère l'instance de notre connexion
    		try (Statement state = connexion.createStatement();
    			ResultSet rs = state.executeQuery(requete); ) {
     
    			result = convertResultSet(rs, tr);
    		} catch(Exception e){
    			e.printStackTrace();
    		}
     
    		return result;
    	}
     
            // méthode de conversion générique
    	protected List<T> convertResultSet(ResultSet rs, Transformer<T> tr) throws SQLException{
    		List<T> result = new ArrayList<T>	();
    		while (rs.next()){
    			result.add(tr.transformRs(rs));
    		}
    		return result;
            }
     
    //Autres méthodes
    }

    Et dans RequeteFacture, ça devient
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
    public class RequeteFacture extends Requete<Facture> {
     
    	public List<String> getIntitulesLigne() {
    		// création de ton transformer, ici en anonymous inner class, mais on peut faire des classes réelles aussi
    		Transformer<String> stringTransformer = new Transformer<String>(){
    			String transformRs(ResultSet rs)throws SQLException{
    				return rs.getString(1);
    			}
    		};
                    // ici, tu appelles la méthode de la classe mère qui exécuter la requête, et qui transforme les résultats
                    return requeteSelect("select intitule from ligue", stringTransformer );
    	}
    }
    Du coup, on a quoi maintenant?
    Une classe Requete abstraite qui fait des requêtes mais qui seule n'a pas de sens (il faut implémenter les requêtes)
    Des classes spécifiques pour faire des requêtes spécifiques : on peut se concentrer sur la requête SQL et sa transformation en Objets via des classes spécifiques (Transformer) dont le but est de transformer une ligne de résultat en un type d'objet.

    Résultat : moins de code... BEAUCOUP MOINS DE CODE !
    Moins de répétitions...
    Tu peux ensuite faire des méthodes dans Requete dans le cas par exemple où tu ne veux récupérer qu'un seul résultat (parce que les listes, c'est bien, mais quand on sait qu'on n'aura qu'un seul résultat, autant le prévoir)



    Pour ce qui est des erreurs, dis toi que les exceptions sont là pour ça : quand tu as un problème technique, elles remontent automatiquement jusqu'en haut pour faire sortir du programme... Sauf si tu les interceptes (catch(Exception)) pour pouvoir les traiter.
    Typiquement, dans la classe Requete, tu ne devrais pas avoir de lien vers la couche de présentation. Ta classe Erreur est un composant graphique qui affiche un message d'erreur. Ce que tu dois faire, c'est le plus haut possible dans ton application, catcher l'exception selon le bon type, et ensuite afficher l'erreur qui ira bien... Quitte à faire une classe, mais autant faire une classe avec des méthodes statiques plutôt que de toujours faire un new. Ou créée une méthode dans ton panel principal qui aura pour but d'afficher les erreurs.


    com.mysql.jdbc.MysqlDataTruncation : utiliser ce genre d'erreur pour vérifier un format de date, c'est super moche ! Une exception, comme son nom l'indique, c'est une exception au bon fonctionnement de l'application, c'est un problème non prévisible. Tout ce qui est prévisible doit être géré. Sinon, tu fais ce qu'on appelle une gestion par exception, et crois moi, ce n'est pas une bonne chose (tu risques de perdre des informations ou masquer des problèmes)


    C'est ce que j'ai justement voulu faire en créant une classe "PageFacture" et une classe "AfficheFacture" où "AfficheFacture" est chargé de récupérer les données depuis la base de données (en utilisant lui aussi un objet de type "Requete") puis de les mettre dans mon JTable. Ma classe "PageFacture" utilise alors un objet de type "AfficheFacture". Je pense néanmoins que tu parles d'une autre façon de découper mais je ne comprends pas laquelle.
    Non, tu as bien compris l'idée. Pense néanmoins à aussi créer ton objet Facture ^^

  5. #5
    Modérateur
    Avatar de Alkhan
    Homme Profil pro
    ingénieur full stack
    Inscrit en
    Octobre 2006
    Messages
    1 232
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : ingénieur full stack

    Informations forums :
    Inscription : Octobre 2006
    Messages : 1 232
    Points : 2 061
    Points
    2 061
    Par défaut
    Bonjour,

    J'ajouterais sur le sujet de la persistance des données et du système de requete, que des frameworks permettent de le faire sans réinventer soi même la chose !
    Je pense naturellement aux implementations de la spécification JPA telque Hibernate et EclipseLink pour ne citer que les deux plus répendu.

  6. #6
    Rédacteur/Modérateur
    Avatar de Logan Mauzaize
    Homme Profil pro
    Architecte technique
    Inscrit en
    Août 2005
    Messages
    2 894
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Architecte technique
    Secteur : Transports

    Informations forums :
    Inscription : Août 2005
    Messages : 2 894
    Points : 7 084
    Points
    7 084
    Par défaut
    Il y a aussi jOOQ et QueryDSL SQL

  7. #7
    Membre chevronné
    Avatar de eulbobo
    Homme Profil pro
    Développeur Java
    Inscrit en
    Novembre 2003
    Messages
    786
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur Java

    Informations forums :
    Inscription : Novembre 2003
    Messages : 786
    Points : 1 993
    Points
    1 993
    Par défaut
    A noter que j'ai pris cette objet Requete pour l'exemple parce que c'était ce qu'il y avait de plus parlant dans le code fourni dans le zip pour donner une méthodologie de comment "mieux" développer.

    Accessoirement, s'il décide d'utiliser une des solutions ORM existante, il vaut mieux qu'en amont il ait déjà correctement découpé les différentes couches applicatives (ce qui n'est pas le cas actuellement)
    Mieux vaut déjà bien structurer son projet avant d'y rajouter des libs externes

  8. #8
    Nouveau membre du Club
    Homme Profil pro
    Apprenti
    Inscrit en
    Octobre 2014
    Messages
    70
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 29
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Apprenti

    Informations forums :
    Inscription : Octobre 2014
    Messages : 70
    Points : 35
    Points
    35
    Par défaut
    Tout d'abord, merci à tous pour vos réponses et principalement à eulbobo pour cette réponse très complète !

    Je te l'avoue, cela me fait beaucoup de notions d'un coup, moi qui ai l'habitude de programmer en utilisant simplement des classes, sans classes abstraites, interfaces, ou même héritage

    Là tu te dis "oui, mais une fois que je récupère mon ResultSet, il faut bien que je puisse avoir un moyen de le transformer en un résultat qui ait un sens".
    Du coup, plusieurs possibilités :
    - Faire en sorte que ta classe Requete soit abstraite, déclarer une méthode abstraite de type protected qui s'appellera par exemple "convertResultSet", et ensuite, pour chaque utilisation de requête dont tu as besoin, créer une implémentation de Requete (RequeteFacture par exemple) qui ne fera qu'implémenter la conversion de ResultSet
    Je comprends ta solution mais j'avais codé mon application de manière à ce qu'elle soit le plus modulable possible et que j'ai juste à effectuer pour une requête select par exemple
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    ResultSet resultat = req.requeteSelect("Ma requête aussi complexe que je le veux");
    et ensuite récupérer les informations comme je le souhaitais.

    Pour la suite je comprends ce que tu proposes mais quand tu dis
    Ici, tu disposes d'une classe fille dont le rôle est d'interroger la base de données pour un domaine précis et de renvoyer des résultats. Et le fait de renvoyer des intitulés de lignes est propre au traitement d'une facture, ça n'a rien de générique (donc rien à faire dans une classe de niveau plus élevé)
    Avantage : le code SQL est regroupé dans les classes qui gèrent les accès et les requêtes à la base de données. Du coup, les interfaces n'ont plus à devoir manipuler des objets du niveau de la base de données (baisse du niveau de couplage)
    là, je me pose une question. Toutes les requêtes que nous seront amenés à faire au sein de notre application devront donc avoir une méthode prévue pour la réaliser ? Pour ce type d'application plutôt légère ça va, mais si l'application est plus lourde et offre beaucoup plus de possibilités (je pense à plusieurs possibilités de filtrage par exemple ?), n'y aurait-il pas trop de méthodes à créer ? Car comme tu le dis en citant mon code je fais
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    ResultSet resultat = req.requeteSelect("select intitule from ligue");
    et je pensais que cela était bien plus propre que de créer une méthode pour chaque requête que je pourrai avoir à faire souvent.

    Dernière amélioration possible ici : utiliser le concept de composition d'objets avec des classes qu'on va appeler Transformer. Dont le but sera de transformer un ResultSet précis dans un résultat.
    Je t'avoue qu'à partir d'ici, je suis largué. Je comprends le concept, l'utilité que ça peut avoir, mais je trouve tout de même ça très abstrait. Je n'ai d'ailleurs jamais vu ou abordé cette notion de Transformer, c'est donc une découverte totale pour moi que j'ai du mal à assimiler, j'irai donc chercher quelques exemples sur internet pour mieux comprendre.

    Pour ce qui est des erreurs, dis toi que les exceptions sont là pour ça : quand tu as un problème technique, elles remontent automatiquement jusqu'en haut pour faire sortir du programme... Sauf si tu les interceptes (catch(Exception)) pour pouvoir les traiter.[...]Ce que tu dois faire, c'est le plus haut possible dans ton application, catcher l'exception selon le bon type, et ensuite afficher l'erreur qui ira bien...
    C'est vrai que je n'ai pas non plus tendance à faire remonter mes erreurs, dès que je vois une erreur, je la catch et je n'utilise jamais de "Throws". Je n'ai pas appris cette notion de "faire remonter les erreurs" même si je sais qu'elle existe, je me contente simplement de la traiter au niveau ou elle se déclenche car je n'ai jamais été sensibilisé à l'utilité de faire remonter les erreurs.

    com.mysql.jdbc.MysqlDataTruncation : utiliser ce genre d'erreur pour vérifier un format de date, c'est super moche ! Une exception, comme son nom l'indique, c'est une exception au bon fonctionnement de l'application, c'est un problème non prévisible. Tout ce qui est prévisible doit être géré. Sinon, tu fais ce qu'on appelle une gestion par exception, et crois moi, ce n'est pas une bonne chose (tu risques de perdre des informations ou masquer des problèmes)
    La encore, c'est vrai, même coup que pour les clefs primaires. J'ai eu une exception une fois et j'ai cherché comment la gérer, j'ai trouvé cette solution et je ne me suis pas dit que ça pouvait être une mauvaise manière de répondre à ce problème, merci pour la remarque.


    Pour ce qui est de la persistance des données, je connais (très brièvement) Hibernate, mais vu que nous avions eu un ou deux cours sur JDBC je suis resté sur l'utilisation de cette méthode.
    De plus, comme le dit eulbobo, ma priorité est d'acquérir de bonnes habitudes pour développer en Java et utiliser au mieux les différentes possibilités offertes par le langage en comprenant déjà par exemple comment éviter les redondances ou comment bien inclure des interfaces ou des classes abstraites de manière efficace et justifiée.

  9. #9
    Membre chevronné
    Avatar de eulbobo
    Homme Profil pro
    Développeur Java
    Inscrit en
    Novembre 2003
    Messages
    786
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur Java

    Informations forums :
    Inscription : Novembre 2003
    Messages : 786
    Points : 1 993
    Points
    1 993
    Par défaut
    Citation Envoyé par snyler Voir le message
    Je te l'avoue, cela me fait beaucoup de notions d'un coup, moi qui ai l'habitude de programmer en utilisant simplement des classes, sans classes abstraites, interfaces, ou même héritage
    Ce qui fait la force d'un langage objet, c'est justement les interfaces et l'héritage (via des classes abstraites ou pas), parce que ça ouvre la voie à ce qu'on appelle le polymorphisme ! (et ça, crois moi, c'est surpuissant... Quand on sait ce qu'on fait avec bien entendu)

    Du coup, je t'invite à aller lire un petit cours de langage orientée objet
    celui-là : http://hdd34.developpez.com/cours/artpoo/#L1.3 (attention, le reste de l'article est orienté Pascal, mais les idées principales sont les mêmes)
    et celui-là : http://jmdoudoux.developpez.com/cour...a/chap-poo.php


    Je comprends ta solution mais j'avais codé mon application de manière à ce qu'elle soit le plus modulable possible et que j'ai juste à effectuer pour une requête select par exemple
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    ResultSet resultat = req.requeteSelect("Ma requête aussi complexe que je le veux");
    et ensuite récupérer les informations comme je le souhaitais.
    En faisant ça, le problème c'est que tu couples les parties affichage, traitement et récupération des données. Chaque morceau dépend de manière vitale de l'autre et tout changement apporté à un endroit peut provoquer des trucs graves derrière.
    Typiquement, pour un système de requêtage, il ne faut jamais laisser sortir les objets dédiés à l'accès à la base de données pour deux raisons
    - Tu risques d'avoir des problèmes parce que tes objets ne sont pas fermés correctement
    - Si tu changes de base de données, tu auras tout à refaire... Partout...

    L'idée c'est qu'un changement fonctionnel ou technique doit toujours avoir le minimum d'impact sur le reste de l'application. On part sur le principe que chaque classe a une fonction/responsabilité, et que le fonctionnement interne de la responsabilité lui incombe à elle seule. Si on change le code à l'intérieur d'une classe qui a une responsabilité, le changement doit être transparent pour les autres.

    Avec ton code initial, si tu décides d'utiliser un ORM comme ceux cités plus haut : tu seras bloqué et il faudra TOUT changer.
    Par contre, si tu crées des méthodes qui répondent à une fonction (donner la liste des intitulés), tu pourras les appeler ailleurs sans avoir à te soucier de comment ça marche à l'intérieur !
    Après tout, tu n'as pas besoin de connaitre le code derrière un Statement ou un ResultSet pour savoir que ça marche ! Et bien sache que les implémentation de Statement et de ResultSet que tu utilises proviennent de l'implémentation du driver JDBC, et ce ne sont pas les même selon que tu utilises une base Oracle ou postgres ou MySQL... Pourtant, tu utiliseras toujours celle même écriture : Statement stm = connexion.createStatement(xxxxx);
    ResultSet et Statement sont des interface qui décrivent des fonctionnalités. Le comportement des implémentations potentielles, ce n'est normalement pas ton problème (globalement, savoir comment ton driver fait pour convertir un double vers un champ Number(10,2), tu n'as pas besoin de savoir techniquement comment il fait)


    Il faut que tu essayes de réfléchir en fonctionnalités.
    Quand tu écris ceci
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    ResultSet resultat = req.requeteSelect("Ma requête aussi complexe que je le veux");
    Tu devrais plutôt te dire
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    Liste<Element> maListe = monRecuperateurDeDonnees.donneMoiLesElements()
    Bref, il faut que dans ton application graphique, tu considère que tu n'as pas accès au SQL.
    Tu dois faire en sorte de te séparer de la logique du requêtage pur quand tu n'es pas au niveau des classes qui gèrent les accès à la base de données !


    Pour la suite je comprends ce que tu proposes mais quand tu dis là, je me pose une question. Toutes les requêtes que nous seront amenés à faire au sein de notre application devront donc avoir une méthode prévue pour la réaliser ? Pour ce type d'application plutôt légère ça va, mais si l'application est plus lourde et offre beaucoup plus de possibilités (je pense à plusieurs possibilités de filtrage par exemple ?), n'y aurait-il pas trop de méthodes à créer ? Car comme tu le dis en citant mon code je fais
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    ResultSet resultat = req.requeteSelect("select intitule from ligue");
    et je pensais que cela était bien plus propre que de créer une méthode pour chaque requête que je pourrai avoir à faire souvent.
    Définis "légère"... Parce que dans la norme JavaME, il existe 3 packages contenant ou pas des éléments dans les sources pour faire en sorte qu'il y ait le moins de choses possibles à charger... Mais quand on dit léger, on entend super léger.
    Dans ton cas, tant que tes classes packagées ne dépassent pas les 10Mo, tu peux considérer que tu fais du code léger. Le nombre de méthodes importe peu (même s'il faut éviter d'en avoir trop dans une même classe pour des raisons de lisibilité)



    Je t'avoue qu'à partir d'ici, je suis largué. Je comprends le concept, l'utilité que ça peut avoir, mais je trouve tout de même ça très abstrait. Je n'ai d'ailleurs jamais vu ou abordé cette notion de Transformer, c'est donc une découverte totale pour moi que j'ai du mal à assimiler, j'irai donc chercher quelques exemples sur internet pour mieux comprendre.
    Attention, l'interface Transformer que je t'ai mis, c'est une interface que j'ai écrite moi même (mais dont le principe existe dans différents ORM comme hibernate qui s'appelle ResultTransformer : https://docs.jboss.org/hibernate/cor...ansformer.html et dont le but est de transformer une ligne de résultat en un objet/liste d'objets).

    Ca combine à la fois le concept d'abstraction (classe abstraite qui utilise une méthode abstraite), de composition (une classe qui fait appel à une autre classe pour faire un traitement particulier), et d'interface (le Transformer, dont la classe Requete n'a pas besoin de connaitre l'implémentation pour que ça marche, juste qu'une classe qui implémente Transformer aura une méthode transformRs).



    C'est vrai que je n'ai pas non plus tendance à faire remonter mes erreurs, dès que je vois une erreur, je la catch et je n'utilise jamais de "Throws". Je n'ai pas appris cette notion de "faire remonter les erreurs" même si je sais qu'elle existe, je me contente simplement de la traiter au niveau ou elle se déclenche car je n'ai jamais été sensibilisé à l'utilité de faire remonter les erreurs.
    Maintenant oui, alors fais moi un beau traitement des erreurs en remontant tes exceptions jusque là où elles doivent être traitées : dans l'interface pour les afficher !
    Et rappelle toi : une exception, c'est une erreur qui peut arriver mais que tu ne PEUX PAS anticiper.


    Pour ce qui est de la persistance des données, je connais (très brièvement) Hibernate, mais vu que nous avions eu un ou deux cours sur JDBC je suis resté sur l'utilisation de cette méthode.
    Commence par déjà bien comprendre le fonctionnement de l'API jdbc avant de te lancer dans un ORM (qui se repose du JDBC de toute façon), ça t'évitera des erreurs ou incompréhensions dans le fonctionnement par la suite ^^

  10. #10
    Nouveau membre du Club
    Homme Profil pro
    Apprenti
    Inscrit en
    Octobre 2014
    Messages
    70
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 29
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Apprenti

    Informations forums :
    Inscription : Octobre 2014
    Messages : 70
    Points : 35
    Points
    35
    Par défaut
    Merci pour les liens que tu m'as donné, j'irai voir. Le fait est que je suis au courant de ces notions d'héritage, classes abstraites ou interfaces mais que d'un point de vue théorique, je n'ai jamais vu leur utilité dans un projet et on ne m'a jamais expliqué pourquoi privilégier cette solution dans un cas précis donc je n'ai pas pris l'habitude d'en utiliser.

    Typiquement, pour un système de requêtage, il ne faut jamais laisser sortir les objets dédiés à l'accès à la base de données pour deux raisons
    - Tu risques d'avoir des problèmes parce que tes objets ne sont pas fermés correctement
    Tu as évoqué ce point à de nombreuses reprises donc je vais tâcher d'y faire vraiment attention alors.

    Il faut que tu essayes de réfléchir en fonctionnalités.
    Quand tu écris ceci
    Tu devrais plutôt te dire
    Bref, il faut que dans ton application graphique, tu considère que tu n'as pas accès au SQL.
    Tu dois faire en sorte de te séparer de la logique du requêtage pur quand tu n'es pas au niveau des classes qui gèrent les accès à la base de données !
    D'accord pour ça, je vais tâcher de vraiment découper la partie graphique de la partie accès à la base de données et bien concentrer tout le code SQL dans la partie réservée SQL et non dans la partie interface graphique.

    Définis "légère"... Parce que dans la norme JavaME, il existe 3 packages contenant ou pas des éléments dans les sources pour faire en sorte qu'il y ait le moins de choses possibles à charger... Mais quand on dit léger, on entend super léger.
    Dans ton cas, tant que tes classes packagées ne dépassent pas les 10Mo, tu peux considérer que tu fais du code léger. Le nombre de méthodes importe peu (même s'il faut éviter d'en avoir trop dans une même classe pour des raisons de lisibilité)
    C'est vrai que le terme de "légère" n'était pas très explicite. Par légère je veux dire qu'il y a tout de même peu de fonctionnalités dans cette application et les services proposés sont toujours les mêmes. Ma question était plutôt : si l'on souhaite proposer à l'utilisateur des fonctions plus avancées comme afficher les factures en fonction de leur somme, de la date, de leur nombre de prestations, ... Nous n'allons pas implémenter chaque méthode, il faudra alors une méthode un peu moins "spécifique" que getIntitulesLigne par exemple, non ?

    Attention, l'interface Transformer que je t'ai mis, c'est une interface que j'ai écrite moi même (mais dont le principe existe dans différents ORM comme hibernate qui s'appelle ResultTransformer : https://docs.jboss.org/hibernate/cor...ansformer.html et dont le but est de transformer une ligne de résultat en un objet/liste d'objets).
    Ca combine à la fois le concept d'abstraction (classe abstraite qui utilise une méthode abstraite), de composition (une classe qui fait appel à une autre classe pour faire un traitement particulier), et d'interface (le Transformer, dont la classe Requete n'a pas besoin de connaitre l'implémentation pour que ça marche, juste qu'une classe qui implémente Transformer aura une méthode transformRs).
    D'accord, je pensais qu'il s'agissait d'une interface déjà existante et "disponible". J'avais compris son fonctionnement (que tu avais bien décris) et qu'une classe qui implémenterai Transformer devrait avoir une méthode transformRs en voyant l'interface que tu avais posté.

    Maintenant oui, alors fais moi un beau traitement des erreurs en remontant tes exceptions jusque là où elles doivent être traitées : dans l'interface pour les afficher !
    Et rappelle toi : une exception, c'est une erreur qui peut arriver mais que tu ne PEUX PAS anticiper.
    D'accord pour cette partie, je ferai en sorte pour mes prochains projets de bien gérer la remontée d'exception et de ne pas catcher tout ce que je vois

    Commence par déjà bien comprendre le fonctionnement de l'API jdbc avant de te lancer dans un ORM (qui se repose du JDBC de toute façon), ça t'évitera des erreurs ou incompréhensions dans le fonctionnement par la suite ^^
    Oui, je préfère déjà bien être à l'aise avec JDBC avant d'aller chercher à maîtriser d'autres ORM ^^

  11. #11
    Membre chevronné
    Avatar de eulbobo
    Homme Profil pro
    Développeur Java
    Inscrit en
    Novembre 2003
    Messages
    786
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur Java

    Informations forums :
    Inscription : Novembre 2003
    Messages : 786
    Points : 1 993
    Points
    1 993
    Par défaut
    Citation Envoyé par snyler Voir le message
    C'est vrai que le terme de "légère" n'était pas très explicite. Par légère je veux dire qu'il y a tout de même peu de fonctionnalités dans cette application et les services proposés sont toujours les mêmes. Ma question était plutôt : si l'on souhaite proposer à l'utilisateur des fonctions plus avancées comme afficher les factures en fonction de leur somme, de la date, de leur nombre de prestations, ... Nous n'allons pas implémenter chaque méthode, il faudra alors une méthode un peu moins "spécifique" que getIntitulesLigne par exemple, non ?
    A partir du moment où tu veux augmenter les fonctionnalités utilisateurs, il te faudra presque toujours prévoir le cas ailleurs. Rajouter une méthode dans ta classe de requête pour récupérer des nouvelles données qui font suite à un nouveau traitement, ce n'est pas sale, bien au contraire ! C'est une nouvelle fonctionnalité !

    Afficher des factures en fonction de leur somme/date/nombre de prestation, c'est des paramètres que tu passes à une méthode pour activer des restrictions/tris. A toi de voir quelles fonctionnalités tu veux donner :

    List<Facture> getFactures(); // renvoie toutes les factures
    List<Facture> getFacturesParSomme(double somme); // renvoie toutes les factures selon une somme
    List<Facture> getFacturesParDate(Date dt); // renvoie toutes les factures selon une date
    List<Facture> getFacturesParPrestations(int prestations); // renvoie toutes les factures selon un nombre de prestations
    List<Facture> getFactures(double somme, Date dt, int prestations); // renvoie toutes les factures selon une somme, une date et/ou une prestation
    List<Facture> getFacturesParPlageDate(Date dtinf, Date dtSup); // renvoie toutes les factures dans une plage de dates
    ...

    Il ne faut pas hésiter à démultiplier les possibilité d'appel, mais il faut que les méthodes que tu crées aient un sens et répondent à un besoin précis.

    Si par exemple dans une interface tu peux systématiquement saisir deux dates (dtInf et dtSup), mais que les deux sont optionnelles, tu n'as pas besoin de créer les méthodes :
    List<Facture> getFacturesSupDate(Date dt); // renvoie toutes les factures supérieures à une date
    List<Facture> getFacturesInfDate(Date dt); // renvoie toutes les factures inférieures à une date

    la méthode getFacturesParPlageDate(Date dtinf, Date dtSup) sera suffisante et il suffit de prévoir une implémentation dans laquelle les deux dates sont facultatives.
    Si tu n'as pas besoin de rechercher par somme, date et nombre de prestation en même temps, tu n'as pas besoin de rendre la méthode getFactures(double somme, Date dt, int prestations) visible !

    Le but du jeu est à chaque niveau d'avoir le moins besoin de réfléchir au fonctionnement de ce qui se passe dans les autres couches. Tu veux des résultats en fonction de critères? Tu as besoin d'une méthode dans laquelle tu passes les critères et qui te donne ton résultat directement.

  12. #12
    Nouveau membre du Club
    Homme Profil pro
    Apprenti
    Inscrit en
    Octobre 2014
    Messages
    70
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 29
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Apprenti

    Informations forums :
    Inscription : Octobre 2014
    Messages : 70
    Points : 35
    Points
    35
    Par défaut
    A partir du moment où tu veux augmenter les fonctionnalités utilisateurs, il te faudra presque toujours prévoir le cas ailleurs. Rajouter une méthode dans ta classe de requête pour récupérer des nouvelles données qui font suite à un nouveau traitement, ce n'est pas sale, bien au contraire ! C'est une nouvelle fonctionnalité !
    D'accord, je ne réfléchissais pas du tout comme ça ! Je me disais qu'il valait mieux que j'ai ma méthode qui effectuait mes requêtes select et que je lui transmettais la requête que je souhaitais faire et qu'après je récupérais le résultat en fonction de ce que renvoyait ma fonction plutôt que d'avoir plein de méthodes qui faisaient chacune sa petite requête.

    Il ne faut pas hésiter à démultiplier les possibilité d'appel, mais il faut que les méthodes que tu crées aient un sens et répondent à un besoin précis.
    Comme je te le disais plus haut, je pensais justement que ce n'était pas adapté de créer autant de méthodes et qu'une seule suffisait.

    Le but du jeu est à chaque niveau d'avoir le moins besoin de réfléchir au fonctionnement de ce qui se passe dans les autres couches. Tu veux des résultats en fonction de critères? Tu as besoin d'une méthode dans laquelle tu passes les critères et qui te donne ton résultat directement.
    Tu m'as bien détaillé plus haut en me montrant des exemples de méthodes, j'ai compris qu'il fallait donc implémenter les méthodes qui répondent à un besoin précis.


    Merci beaucoup pour ton aide eulbobo

Discussions similaires

  1. Quel langage pour développer à la manière d'Apple
    Par doudounette dans le forum Langages de programmation
    Réponses: 6
    Dernier message: 20/08/2010, 15h19
  2. Comment Développer en équipe ?
    Par christ_mallet dans le forum Débats sur le développement - Le Best Of
    Réponses: 45
    Dernier message: 19/11/2007, 00h15
  3. Quel outil choisir pour un développement SQL-Server ?
    Par Mouse dans le forum Débats sur le développement - Le Best Of
    Réponses: 23
    Dernier message: 12/08/2003, 06h23
  4. Quel outil pour du développement Client/Serveur (Win XP) ?
    Par jey_bonnet dans le forum Débats sur le développement - Le Best Of
    Réponses: 5
    Dernier message: 02/11/2002, 14h57
  5. Réponses: 2
    Dernier message: 20/03/2002, 23h01

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