31 votes

Quel est l'intérêt des setters et des getters en Java ?

Veuillez excuser la longueur du texte, mais voici deux programmes, tous deux identiques, mais l'un avec et l'autre sans setters, getters et constructeurs.

J'ai déjà suivi un cours de C++ de base et je ne me souviens d'aucun de ces éléments, et pour l'instant je n'en vois pas l'intérêt, si quelqu'un pouvait les expliquer en termes simples j'apprécierais beaucoup... pour l'instant ils semblent n'être rien de plus que des pertes d'espace pour faire paraître mon code plus long, mais le professeur dit qu'ils sont importants (et jusqu'à présent c'est tout).

Merci d'avance ! Et maintenant voici le code : Mileage.java :

package gasMileage;

import java.util.Scanner; //program uses class Scanner

public class Mileage 
{
    public int restart;
    public double miles, gallons, totalMiles, totalGallons, milesPerGallon;
    public Mileage(int newRestart, double newMiles, double newGallons, 
                   double newTotalMiles, double newTotalGallons, double newMilesPerGallon)
    {
        setRestart(newRestart);
        setMiles(newMiles);
        setGallons(newGallons);
        setTotalMiles(newTotalMiles);
        setTotalGallons(newTotalGallons);
        setMilesPerGallon(newMilesPerGallon);
    }
    public void setRestart(int newRestart)
    {
        restart = newRestart;
    }
    public int getRestart()
    {
        return restart;
    }
    public void setMiles(double newMiles)
    {
        miles = newMiles;
    }
    public double getMiles()
    {
        return miles;
    }
    public void setGallons(double newGallons)
    {
        gallons = newGallons;
    }
    public double getGallons()
    {
        return gallons;
    }
    public void setTotalMiles(double newTotalMiles)
    {
        totalMiles = newTotalMiles;
    }
    public double getTotalMiles()
    {
        return totalMiles;
    }
    public void setTotalGallons(double newTotalGallons)
    {
        totalGallons = newTotalGallons;
    }
    public double getTotalGallons()
    {
        return totalGallons;
    }
    public void setMilesPerGallon(double newMilesPerGallon)
    {
        milesPerGallon = newMilesPerGallon;
    }
    public double getMilesPerGallon()
    {
        return milesPerGallon;
    }
    public void calculateMileage()
    {
        Scanner input = new Scanner(System.in);
        while(restart == 1)
        {
            System.out.print("Please input number of miles you drove: ");
            miles = input.nextDouble();
            totalMiles = totalMiles + miles;
            System.out.print("Please input number of gallons you used: ");
            gallons = input.nextDouble();
            totalGallons = totalGallons + gallons;
            milesPerGallon = miles / gallons;
            System.out.printf("Your mileage is %.2f MPG.\n", milesPerGallon);
            System.out.print("Would you like to try again? 1 for yes, 2 for no: ");
            restart = input.nextInt();
        }
        milesPerGallon = totalMiles / totalGallons;
        System.out.printf("Your total mileage for these trips is: %.2f.\nYour total gas consumed on these trips was: %.2f.\n", totalMiles, totalGallons);
        System.out.printf("Your total mileage for these trips is: %.2f MPG", milesPerGallon);
    }
}

Mileagetest.java :

package gasMileage;

public class Mileagetest 
{
    public static void main(String[] args) 
    {
        Mileage myMileage = new Mileage(1,0,0,0,0,0);
        myMileage.calculateMileage();
    }
}

Et maintenant, voici celui qui n'a pas d'éléments de définition ni d'éléments d'acquisition :

Testmileage.java :

package gasMileage;

import java.util.Scanner;

public class Testmileage 
{
    int restart = 1;
    double miles = 0, milesTotal = 0, gas = 0, gasTotal = 0, mpg = 0;
    Scanner input = new Scanner(System.in);
    public void testCalculate()
    {
        while(restart == 1)
        {
            System.out.print("Please input miles: ");
            miles = input.nextDouble();
            milesTotal = milesTotal + miles;
            System.out.print("Please input gas: ");
            gas = input.nextDouble();
            gasTotal = gasTotal + gas;
            mpg = miles/gas;
            System.out.printf("MPG: %.2f", mpg);
            System.out.print("\nContinue? 1 = yes, 2 = no: ");
            restart = input.nextInt();
        }
            mpg = milesTotal / gasTotal;
            System.out.printf("Total Miles: %.2f\nTotal Gallons: %.2f\nTotal MPG: %.2f\n", milesTotal, gasTotal, mpg);
    }
}

Testmileagetest.java :

package gasMileage;

public class Testmileagetest 
{

    /**
     * @param args
     */
    public static void main(String[] args) 
    {
        Testmileage test = new Testmileage();
        test.testCalculate();
    }

}

Merci encore !

5 votes

+1 parce que vous essayez manifestement d'apprendre et de comprendre correctement les concepts.

1 votes

Eh... je suis un peu un geek comme ça je suppose... donnez-moi un devoir de physique et regardez à quelle vitesse je cherche une réponse rapide sur Google... donnez-moi un devoir de codage et regardez combien de temps je passe à chercher la raison pour laquelle ça marche ou pourquoi c'est mieux comme ça. Cela fait environ 3 jours que je réfléchis et que je cherche cette réponse.

1 votes

Je suis surpris que l'on ne vous ait pas enseigné l'encapsulation en C++. Elle s'y applique tout autant qu'en Java ou dans tout autre langage OO. J'irais donner du fil à retordre à votre professeur de C++ à ce sujet ;-).

28voto

Michael Madsen Points 30610

L'intérêt des getters et setters, quelle que soit la langue est de cacher la variable sous-jacente. Cela vous permet d'ajouter une logique de vérification lorsque vous tentez de définir une valeur - par exemple, si vous avez un champ pour une date de naissance, vous ne voudrez peut-être autoriser la définition de ce champ qu'à un moment donné dans le passé. Il n'est pas possible d'appliquer cette logique si le champ est accessible au public et modifiable - vous avez besoin des getters et setters.

Même si vous n'avez pas encore besoin de vérification, vous pourriez en avoir besoin à l'avenir. Le fait d'écrire les getters et setters maintenant signifie que l'interface reste cohérente, de sorte que le code existant ne s'interrompra pas lorsque vous le modifierez.

0 votes

Je ne sais pas exactement comment le formuler, mais alors que je pense comprendre ce que vous avez dit, je ne le saisis pas ? Si cela a un sens ? Vous dites que les sets/gets/constructeurs gardent la variable cachée du programme, je comprends cela, mais ce n'est pas le but ? En quoi le code avec les setters/getters est-il meilleur/important que le code que j'ai écrit sans ? Les deux accomplissent exactement la même chose, mais l'un le fait en un peu moins de code. Je ne cherche pas à coder "moins de lignes de code"... j'essaie juste de voir la différence, si cela a un sens ?

0 votes

La variable n'est qu'un stockage de données, tandis que les getters et setters sont des moyens abstraits d'accéder aux données ou de les modifier. Étant donné que les getters et setters sont des moyens abstraits, les modifications ultérieures de votre structure de données (comme l'ajout de vérification, le calcul de certaines valeurs, l'utilisation d'une base de données, etc.) ne nécessitent pas de changer votre abstraction de getter et setter, ce qui permet de garder tout le code qui utilise la classe identique tout en modifiant le fonctionnement interne du code.

0 votes

Ainsi, les setters et getters sont similaires à la création d'une copie de la variable, permettant de bricoler avec cette copie afin de ne pas altérer les valeurs d'origine ?

25voto

Dave Jarvis Points 12598

Encapsulation

Les méthodes d'accès ("setters et getters") tentent de dissimuler les détails de la manière dont les données d'un objet sont stockées. En pratique, il s'agit d'un moyen glorifié de stocker et de récupérer des données d'une manière non orientée objet. Les accesseurs n'encapsulent rien de manière efficace en ce sens qu'il y a peu de différence pratique entre les deux morceaux de code suivants :

Person bob = new Person();
Colour hair = bob.getHairColour();
hair.setRed( 255 );

Et ceci :

Person bob = new Person();
Colour hair = bob.hairColour;
hair.red = 255;

Les deux extraits de code exposent l'idée qu'une personne est étroitement liée à un cheveu. Ce couplage étroit se révèle ensuite dans l'ensemble de la base de code, ce qui se traduit par un logiciel fragile. En effet, il devient difficile de modifier la façon dont les cheveux d'une personne sont stockés.

Au lieu de cela :

Person bob = new Person();
bob.colourHair( Colour.RED );

Cela suit le principe de "dire, ne pas demander". En d'autres termes, les objets doivent recevoir des instructions (de la part d'autres objets) pour effectuer une tâche spécifique. C'est là tout l'intérêt de la programmation orientée objet. Et très peu de gens semblent le comprendre.

La différence entre les deux scénarios est la suivante :

  • Dans la première situation, Bob n'avait aucun contrôle sur la couleur de ses cheveux. C'est une bonne chose pour un coiffeur qui a un penchant pour les roux, mais c'est moins bien pour Bob qui méprise cette couleur.
  • Dans la seconde situation, Bob a un contrôle total sur la couleur de ses cheveux car aucun autre objet du système n'est autorisé à changer cette couleur sans la permission de Bob.

Une autre façon d'éviter ce problème est de renvoyer une copie de la couleur des cheveux de Bob (en tant que nouvelle instance), qui n'est plus couplée à Bob. Je trouve cette solution peu élégante car elle signifie qu'un comportement souhaité par une autre classe, utilisant les cheveux d'une personne, n'est plus associé à la personne elle-même. Cela réduit la capacité à réutiliser le code, ce qui conduit à du code dupliqué.

Masquage des types de données

En Java, qui ne peut pas avoir deux signatures de méthode qui ne diffèrent que par le type de retour, cela ne cache pas vraiment le type de données sous-jacent utilisé par l'objet. Vous verrez rarement, voire jamais, ce qui suit :

public class Person {
  private long hColour = 1024;

  public Colour getHairColour() {
    return new Colour( hColour & 255, hColour << 8 & 255, hColour << 16 & 255 );
  }
}

En règle générale, le type de données des variables individuelles est exposé textuellement par l'utilisation de l'accesseur correspondant, et il est nécessaire de procéder à un remaniement pour le modifier :

public class Person {
  private long hColour = 1024;

  public long getHairColour() {
    return hColour;
  }

  /** Cannot exist in Java: compile error. */
  public Colour getHairColour() {
    return new Colour( hColour & 255, hColour << 8 & 255, hColour<< 16 & 255 );
  }
}

Bien qu'il fournisse un niveau d'abstraction, il s'agit d'un voile mince qui ne fait rien pour le couplage lâche.

Dites-le, ne demandez pas

Pour plus d'informations sur cette approche, lire Dites-le, ne demandez pas .

Exemple de fichier

Considérons le code suivant, légèrement modifié à partir de la réponse de ColinD :

public class File {
   private String type = "";

   public String getType() {
      return this.type;
   }

   public void setType( String type ) {
      if( type = null ) {
        type = "";
      }

      this.type = type;
   }

   public boolean isValidType( String type ) {
      return getType().equalsIgnoreCase( type );
   }
}

La méthode getType() dans ce cas est redondant et conduira inévitablement (dans la pratique) à un code dupliqué tel que :

public void arbitraryMethod( File file ) {
  if( file.getType() == "JPEG" ) {
    // Code.
  }
}

public void anotherArbitraryMethod( File file ) {
  if( file.getType() == "WP" ) {
    // Code.
  }
}

Enjeux :

  • Type de données. Le type ne peut pas facilement passer d'une chaîne de caractères à un nombre entier (ou à une autre classe).
  • Protocole implicite. Il est fastidieux d'abstraire le type de l'élément spécifique ( PNG , JPEG , TIFF , EPS ) au général ( IMAGE , DOCUMENT , SPREADSHEET ).
  • Présente les insectes. La modification du protocole implicite ne génère pas d'erreur de compilation, ce qui peut entraîner des bogues.

Évitez complètement le problème en empêchant les autres classes de demandant pour les données :

public void arbitraryMethod( File file ) {
  if( file.isValidType( "JPEG" ) ) {
    // Code.
  }
}

Cela implique de modifier le get à la méthode d'accès à private :

public class File {
   public final static String TYPE_IMAGE = "IMAGE";

   private String type = "";

   private String getType() {
      return this.type;
   }

   public void setType( String type ) {
      if( type == null ) {
        type = "";
      }
      else if(
        type.equalsIgnoreCase( "JPEG" ) ||
        type.equalsIgnoreCase( "JPG" ) ||
        type.equalsIgnoreCase( "PNG" ) ) {
        type = File.TYPE_IMAGE;
      }

      this.type = type;
   }

   public boolean isValidType( String type ) {
      // Coerce the given type to a generic type.
      //
      File f = new File( this );
      f.setType( type );

      // Check if the generic type is valid.
      //
      return isValidGenericType( f.getType() );
   }
}

Aucun autre code du système n'est interrompu lorsque le code File fait passer le protocole implicite de types spécifiques (par exemple, JPEG) à des types génériques (par exemple, IMAGE). Tout le code du système doit utiliser la classe isValidType qui ne donne pas le type à l'objet appelant, mais la méthode raconte les File pour valider un type.

2 votes

Pourquoi ne puis-je pas trouver le bouton +100 ? Cette réponse est exacte. En guise d'humble note de bas de page, j'ajouterai que les haricots avaient l'habitude d'utiliser des getters et des setters, ce qui les faisait malheureusement paraître plus légitimes qu'ils ne le méritaient, et que les annotations sont préférées de nos jours.

18voto

ColinD Points 48573

Les autres réponses donnent généralement une bonne idée de certaines des raisons pour l'utilisation des getters et setters, mais je veux donner un peu un parfait exemple de pourquoi ils sont utiles.

Prenons, par exemple, un fichier (en ignorant l'existence d'un File classe en Java). Cette File classe dispose d'un champ pour stocker le type de fichier (.pdf, .exe, .txt, etc)... nous allons ignorer tout le reste.

Au départ, vous décidez de le stocker en tant que String sans getters et setters:

public class File {
   // ...
   public String type;
   // ...
}

Voici quelques questions à ne pas utiliser les accesseurs et mutateurs.

Pas de contrôle sur la façon dont le champ est défini:

Tous les clients de votre classe peuvent faire ce qu'ils veulent avec elle:

public void doSomething(File file) {
   // ...
   file.type = "this definitely isn't a normal file type";
   // ...
}

Vous décidez plus tard que vous ne voulez probablement pas à le faire... mais comme ils ont un accès direct au champ dans votre classe, vous n'avez aucun moyen de les prévenir.

L'impossibilité de modifier facilement la représentation interne:

Plus tard encore, vous décidez que vous voulez stocker le type de fichier comme une instance d'une interface appelée FileType, vous permettant d'associer un comportement à différents types de fichiers. Toutefois, de nombreux clients de votre classe sont déjà récupération et la définition des types de fichier comme Strings. Donc, si vous voulez avoir un problème là... tu ferais casser beaucoup de code (même code dans d'autres projets que vous ne pouvez pas résoudre vous-même, si c'est une bibliothèque) si vous venez de modifier le champ d'un String d'un FileType.

Comment les Getters et les Setters de résoudre ce

Maintenant, imaginez que vous avait plutôt fait le type de champ private et créé

public String getType() {
   return this.type;
}

public void setType(String type) {
   this.type = type;
}

Contrôle sur la définition de la propriété:

Maintenant, lorsque vous voulez mettre en œuvre une exigence que seules certaines chaînes sont valables types de fichiers et de prévenir les autres chaînes, vous pouvez simplement écrire:

public void setType(String type) {
   if(!isValidType(type)) {
       throw new IllegalArgumentException("Invalid file type: " + type);
   }
   this.type = type;
}

private boolean isValidType(String type) {
   // logic here
}

Possibilité de modifier facilement la représentation interne:

Modification de l' String représentation du type est relativement facile. Imaginez que vous avez un enum ValidFileType qui implémente FileType et contient les types de fichiers.

Vous pouvez changer facilement de la représentation interne du type de fichier dans la classe comme ceci:

public class File {
   // ...
   private FileType type;
   // ...
   public String getType() {
      return type.toString();
   }

   public void setType(String type) {
      FileType newType = ValidFileType.valueOf(type);

      if(newType == null) {
         throw new IllegalArgumentException("Invalid file type: " + type);
      }

      this.type = newType;
   }
}

Depuis les clients de la classe ont été appelant getType() et setType() de toute façon, rien ne change à partir de leur point de vue. Seulement le fonctionnement interne de la classe a changé, pas l'interface que les autres classes sont à l'aide.

5voto

Lou Franco Points 48823

L'idée est que si vos classes clientes appellent des fonctions get/set, vous pouvez changer ce qu'elles font plus tard et les appelants sont isolés. Si vous avez une variable publique et que j'y accède directement, vous n'avez aucun moyen d'ajouter un comportement plus tard lorsqu'on y accède ou qu'on la définit.

Même dans votre exemple simple, vous pourriez en tirer davantage parti.

Au lieu d'utiliser :

milesPerGallon = miles / gallons;

dans calculateMileage()

Vous pourriez modifier setMiles() et setGallons() pour mettre à jour les milesPerGallon lorsqu'ils sont appelés. Ensuite, supprimez setMilesPerGallon() pour indiquer qu'il s'agit d'une propriété en lecture seule.

0 votes

Pouvez-vous nous expliquer un peu plus en détail ? Qu'entendez-vous par "isolé" ? Et comment pourrais-je modifier ce qu'ils peuvent faire plus tard ? Et pourquoi ne pourrais-je pas ajouter un comportement plus tard si vous y accédez directement ?

0 votes

@Jeff : Si vous accédez directement aux miles, gallons et milesPerGallon, les milesPerGalllon ne seront pas mis à jour, puisqu'il n'y a pas de logique de codage pour le faire.

0 votes

Par "isolé", j'entends qu'il y a des changements que vous pourriez apporter à la classe et que les autres classes qui utilisent votre classe n'auraient pas besoin de changer du tout. Par exemple, normalement calculateMileage() ne serait pas dans la classe Mileage (c'est l'interface utilisateur d'une application, pas la calculatrice) -- cette autre classe (par exemple ConsoleMileageUI) accéderait à Mileage par son interface publique (get/set) -- le calcul des miles/gallon devrait rester dans Mileage cependant -- peut-être être appelé dans setMiles et setGallons -- ou alternativement dans getMilesPerGallon(). Quel que soit votre choix, calculateMileage() n'aurait pas à changer.

3voto

Lucero Points 38928

L'idée est qu'une classe ne doit pas permettre l'accès direct à ses champs, car il s'agit d'une spécificité d'implémentation. Il se peut que vous souhaitiez modifier la classe ultérieurement afin d'utiliser un autre stockage de données, mais que vous conserviez la même classe pour ses "utilisateurs", ou que vous souhaitiez créer une interface qui ne peut pas non plus inclure de champs.

Jetez un coup d'œil à la Article de Wikipédia sur le sujet.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X