Comment évitez-vous les getters et les setters?


85

J'ai un peu de mal à concevoir des cours de façon très différente. J'ai lu que les objets exposent leur comportement, pas leurs données; par conséquent, plutôt que d'utiliser des getter / setters pour modifier des données, les méthodes d'une classe donnée doivent être des "verbes" ou des actions opérant sur l'objet. Par exemple, nous aurions dans un objet « Compte », les méthodes Withdraw()et Deposit()plutôt que , setAmount()etc. Voir: Pourquoi les méthodes getter et setter sont mauvais .

Ainsi, par exemple, dans le cas d’une classe de clients qui conserve beaucoup d’informations sur le client, par exemple Nom, Date de naissance, Tél, Adresse, etc., comment éviter les getter / setters pour obtenir et définir tous ces attributs? Quelle méthode de type 'Behavior' peut-on écrire pour renseigner toutes ces données?


3
doublon possible de Y a
moucher


8
Je ferais remarquer que si vous devez traiter avec la spécification Java Beans , vous aurez des getters et des setters. Beaucoup de choses utilisent Java Beans (langage d'expression en jsps) et essayer d'éviter cela sera probablement… un défi.

4
... et du point de vue opposé de MichaelT: si vous n'utilisez pas la spécification JavaBeans (et personnellement, je pense que vous devriez l'éviter à moins que vous n'utilisiez votre objet dans un contexte où il est nécessaire), il n'est pas nécessaire de le "get" wart sur getters, en particulier pour les propriétés qui n'ont pas de setter correspondant. Je pense qu'une méthode appelée name()sur Customerest aussi claire ou plus claire, qu'une méthode appelée getName().
Daniel Pryden

2
@Daniel Pryden: name () peut signifier à la fois de définir ou d'obtenir? ...
IntelliData

Réponses:


55

Comme indiqué dans de nombreuses réponses et commentaires, les DTO sont appropriés et utiles dans certaines situations, notamment pour le transfert de données à travers des frontières (par exemple, la sérialisation en JSON pour l'envoi via un service Web). Pour le reste de cette réponse, j'ignorerai plus ou moins cela et parlerai des classes de domaine et de la manière dont elles peuvent être conçues pour minimiser (voire éliminer) les accesseurs et les installateurs, tout en restant utiles dans un projet de grande envergure. Je ne parlerai pas non plus de la raison pour laquelle il faut supprimer les accesseurs ou les setters, ni à quel moment , car ce sont des questions qui leur sont propres.

A titre d'exemple, imaginez que votre projet est un jeu de plateau comme Chess ou Battleship. Vous pouvez avoir différentes façons de représenter cela dans une couche de présentation (application console, service Web, interface graphique, etc.), mais vous disposez également d'un domaine principal. Une classe que vous pourriez avoir est Coordinate, représentant une position sur le conseil. La "mauvaise" façon de l'écrire serait:

public class Coordinate
{
    public int X {get; set;}
    public int Y {get; set;}
}

(Je vais écrire des exemples de code en C # plutôt qu'en Java, par souci de concision et parce que je le connais mieux. Espérons que cela ne pose pas de problème. Les concepts sont les mêmes et la traduction doit être simple.)

Retrait des setters: immuabilité

Alors que les sites publics et les setters sont potentiellement problématiques, les setters sont le plus "méchant" des deux. Ils sont aussi généralement les plus faciles à éliminer. Le processus est simple: définissez la valeur depuis le constructeur. Toutes les méthodes qui ont déjà muté l'objet doivent plutôt renvoyer un nouveau résultat. Alors:

public class Coordinate
{
    public int X {get; private set;}
    public int Y {get; private set;}

    public Coordinate(int x, int y)
    {
        X = x;
        Y = y;
    }
}

Notez que cela ne protège pas contre les autres méthodes de la classe qui mutent X et Y. Pour être plus strictement immuable, vous pourriez utiliser readonly( finalen Java). Mais dans les deux cas - que vous rendiez vos propriétés véritablement immuables ou que vous empêchiez simplement la mutation publique directe par le biais de setters -, vous avez la possibilité de supprimer vos setters publics. Dans la grande majorité des situations, cela fonctionne très bien.

Suppression des accesseurs, partie 1: concevoir pour le comportement

Ce qui précède est très bien pour les passeurs, mais pour ce qui est des Getters, nous nous sommes tiré dans le pied avant même de commencer. Notre processus consistait à réfléchir à ce qu'est une coordonnée - les données qu'elle représente - et à créer une classe autour de celle-ci. Au lieu de cela, nous aurions dû commencer par quel comportement nous avons besoin d'une coordonnée. Soit dit en passant, ce processus est facilité par TDD, qui extrait uniquement les classes de ce type dès que nous en avons besoin. Nous commençons par le comportement souhaité et travaillons à partir de là.

Supposons donc que le premier endroit où vous ayez eu besoin d'un Coordinateétait la détection de collision: vous vouliez vérifier si deux pièces occupaient le même espace sur le tableau. Voici le "mal" moyen (constructeurs omis pour plus de brièveté):

public class Piece
{
    public Coordinate Position {get; private set;}
}

public class Coordinate
{
    public int X {get; private set;}
    public int Y {get; private set;}
}

    //...And then, inside some class
    public bool DoPiecesCollide(Piece one, Piece two)
    {
        return one.X == two.X && one.Y == two.Y;
    }

Et voici le bon moyen:

public class Piece
{
    private Coordinate _position;
    public bool CollidesWith(Piece other)
    {
        return _position.Equals(other._position);
    }
}

public class Coordinate
{
    private readonly int _x;
    private readonly int _y;
    public bool Equals(Coordinate other)
    {
        return _x == other._x && _y == other._y;
    }
}

( IEquatablemise en oeuvre abrégée pour plus de simplicité). En concevant pour le comportement plutôt que pour la modélisation des données, nous avons réussi à supprimer nos accesseurs.

Notez que ceci est également pertinent pour votre exemple. Vous utilisez peut-être un ORM, ou affichez des informations sur les clients sur un site Web ou quelque chose du genre, auquel cas une sorte de CustomerDTO aurait probablement un sens. Mais ce n'est pas parce que votre système inclut des clients et qu'ils sont représentés dans le modèle de données que vous devez avoir une Customerclasse dans votre domaine. Peut-être que lorsque vous concevez un comportement, un comportement apparaîtra, mais si vous voulez éviter les accesseurs, ne créez pas un comportement préventif.

Suppression de getters, partie 2: comportement externe

Ce qui précède est donc un bon début, mais tôt ou tard, vous rencontrerez probablement un comportement associé à une classe, qui dépend en quelque sorte de l'état de la classe, mais qui n'appartient pas à la classe. Ce type de comportement correspond généralement à la couche de service de votre application.

En prenant notre Coordinateexemple, vous voudrez éventuellement représenter votre jeu auprès de l'utilisateur, ce qui peut vouloir dire que vous dessinez à l'écran. Vous pouvez, par exemple, avoir un projet d'interface utilisateur qui Vector2représente un point à l'écran. Mais il serait inapproprié que la Coordinateclasse prenne en charge la conversion d'une coordonnée en un point à l'écran, ce qui apporterait toutes sortes de problèmes de présentation à votre domaine principal. Malheureusement, ce type de situation est inhérent à la conception OO.

La première option , qui est très souvent choisie, consiste simplement à exposer les maudits getters et à dire au diable. Cela a l'avantage de la simplicité. Mais puisque nous parlons d’éviter les accesseurs, disons, par argument, nous rejetons celui-ci et voyons quelles autres options existent.

Une deuxième option consiste à ajouter une sorte de .ToDTO()méthode à votre classe. Quoi qu'il en soit, cela peut être nécessaire, ou similaire, par exemple, lorsque vous souhaitez enregistrer le jeu, vous devez capturer la quasi-totalité de votre état. Mais la différence entre le faire pour vos services et simplement accéder directement au getter est plus ou moins esthétique. Il a toujours autant de "mal".

Une troisième option - que j'ai vue préconisée par Zoran Horvat dans quelques vidéos de Pluralsight - consiste à utiliser une version modifiée du modèle de visiteur. Il s’agit d’une utilisation et d’une variation assez inhabituelles du modèle et je pense que la distance parcourue par les gens variera énormément selon qu’il s’agit d’ajouter de la complexité sans créer de réel avantage ou si c’est un bon compromis pour la situation. L'idée est essentiellement d'utiliser le modèle de visiteur standard, mais les Visitméthodes prennent comme paramètres l'état dont elles ont besoin, au lieu de la classe qu'elles visitent. Des exemples peuvent être trouvés ici .

Pour notre problème, une solution utilisant ce modèle serait:

public class Coordinate
{
    private readonly int _x;
    private readonly int _y;

    public T Transform<T>(IPositionTransformer<T> transformer)
    {
        return transformer.Transform(_x,_y);
    }
}

public interface IPositionTransformer<T>
{
    T Transform(int x, int y);
}

//This one lives in the presentation layer
public class CoordinateToVectorTransformer : IPositionTransformer<Vector2>
{
    private readonly float _tileWidth;
    private readonly float _tileHeight;
    private readonly Vector2 _topLeft;

    Vector2 Transform(int x, int y)
    {
        return _topLeft + new Vector2(_tileWidth*x + _tileHeight*y);
    }
}

Comme vous pouvez probablement le constater, _xet _yne sont plus vraiment encapsulés. Nous pourrions les extraire en créant un IPositionTransformer<Tuple<int,int>>qui les retourne directement. Selon vos goûts, vous aurez peut-être l'impression que cela rend tout l'exercice inutile.

Cependant, avec les getters publics, il est très facile de mal faire les choses, il suffit d'extraire les données directement et de les utiliser en violation de Tell, Don't Ask . Tandis que vous utilisez ce modèle, il est en fait plus simple de le faire correctement: lorsque vous souhaitez créer un comportement, vous commencez automatiquement par créer un type qui lui est associé. Les violations de la TDA seront manifestement malodorantes et nécessiteront probablement une solution plus simple et meilleure. En pratique, ces points rendent beaucoup plus facile de faire les choses correctement, OO, plutôt que la manière "perverse" que les getters encouragent.

Enfin , même si cela n’est pas évident au départ, il peut en fait exister des moyens d’exposer suffisamment de comportements dont vous avez besoin pour éviter de devoir exposer l’état. Par exemple, en utilisant notre version précédente Coordinatedont le seul membre public est Equals()(en pratique, une IEquatableimplémentation complète serait nécessaire ), vous pourriez écrire la classe suivante dans votre couche de présentation:

public class CoordinateToVectorTransformer
{
    private Dictionary<Coordinate,Vector2> _coordinatePositions;

    public CoordinateToVectorTransformer(int boardWidth, int boardHeight)
    {
        for(int x=0; x<boardWidth; x++)
        {
            for(int y=0; y<boardWidth; y++)
            {
                _coordinatePositions[new Coordinate(x,y)] = GetPosition(x,y);
            }
        }
    }

    private static Vector2 GetPosition(int x, int y)
    {
        //Some implementation goes here...
    }

    public Vector2 Transform(Coordinate coordinate)
    {
        return _coordinatePositions[coordinate];
    }
}

Il se trouve, peut-être étonnamment, que tout le comportement dont nous avions vraiment besoin d'une coordonnée pour atteindre notre objectif était la vérification de l'égalité! Bien entendu, cette solution est adaptée à ce problème et fait des hypothèses sur l'utilisation / les performances de la mémoire. C'est juste un exemple qui correspond à ce domaine de problème particulier, plutôt qu'un plan pour une solution générale.

Et encore une fois, les opinions varieront selon qu’il s’agisse ou non d’une complexité inutile. Dans certains cas, une telle solution n'existe peut-être pas, ou elle peut s'avérer prohibitive ou complexe, auquel cas vous pouvez revenir aux trois solutions ci-dessus.


Magnifiquement répondu! Je voudrais accepter, mais d’abord quelques commentaires: 1. Je pense vraiment que le toDTO () est génial, car nous n’avons pas accès au get / set, ce qui vous permet de changer les champs donnés à DTO sans le code existant. 2. Dites que le client a suffisamment de comportement pour justifier la création d'une entité, comment
accéderiez-

@IntelliData 1. Lorsque vous dites "changer les champs", vous voulez dire changer la définition de la classe ou transformer les données? Ce dernier point peut simplement être évité en supprimant les établissements publics, mais en laissant les accesseurs, de sorte que l'aspect dto est sans importance. Le premier n’est pas vraiment la raison (entière) pour laquelle les getters publics sont "pervers". Voir programmers.stackexchange.com/questions/157526/… pour un exemple.
Ben Aaronson

@IntelliData 2. Il est difficile de répondre à cette question sans connaître le comportement. Mais probablement, la réponse est: vous ne le feriez pas. Quel comportement une Customerclasse pourrait -elle avoir pour être capable de muter son numéro de téléphone? Peut-être que le numéro de téléphone du client change et que je dois maintenir ce changement dans la base de données, mais rien de tout cela ne relève de la responsabilité d'un objet de domaine fournissant un comportement. Il s’agit d’un problème d’accès aux données, qui serait probablement traité avec un DTO et, par exemple, un référentiel.
Ben Aaronson

@IntelliData Maintenir les Customerdonnées de l'objet de domaine relativement récentes (en phase avec la base de données) relève de la gestion de son cycle de vie, qui n'est également pas de sa responsabilité, et finirait probablement par résider dans un référentiel, une usine ou un conteneur IOC. tout ce qui instancie l' Customerart.
Ben Aaronson

2
J'aime beaucoup le concept Design for Behavior . Ceci informe la "structure de données" sous-jacente et aide à éviter les classes trop courantes anémiques et difficiles à utiliser. un de plus.
radarbob

71

Le moyen le plus simple d'éviter les paramètres est de transmettre les valeurs à la méthode du constructeur lorsque vous newmontez l'objet. C'est également le modèle habituel lorsque vous souhaitez rendre un objet immuable. Cela dit, les choses ne sont pas toujours aussi claires dans le monde réel.

Il est vrai que les méthodes devraient concerner le comportement. Cependant, certains objets, tels que Client, existent principalement pour contenir des informations. Ce sont les types d'objets qui bénéficient le plus des accesseurs et des passeurs; Si de telles méthodes n'étaient pas du tout nécessaires, nous les éliminerions tout simplement.

Lectures supplémentaires à quel
moment les Getters et les Setters sont-ils justifiés


3
alors pourquoi tout le battage médiatique sur "Getter / Setters" est diabolique?
IntelliData

46
Juste la pontification habituelle des développeurs de logiciels, qui devraient en savoir plus. Pour l'article que vous avez lié, l'auteur utilise l'expression "les getters et les setters sont diaboliques" pour attirer votre attention, mais cela ne signifie pas que la déclaration est catégoriquement vraie.
Robert Harvey

12
@IntelliData, certaines personnes vous diront que Java est maléfique.
null

18
@MetaFightsetEvil(null);

4
Certainement eval est Evil Incarnate. Ou un proche cousin.
évêque

58

Il est parfaitement correct d'avoir un objet qui expose des données plutôt qu'un comportement. Nous l'appelons simplement un "objet de données". Le modèle existe sous des noms tels que Data Transfer Object ou Value Object. Si l'objet a pour objet de contenir des données, les accesseurs et les installateurs sont valides pour accéder aux données.

Alors, pourquoi quelqu'un dirait-il que "les méthodes de getter et de setter sont mauvaises"? Vous verrez cela souvent - quelqu'un prend une directive parfaitement valable dans un contexte spécifique, puis supprime le contexte afin d'obtenir un titre plus percutant. Par exemple, " privilégier la composition au détriment de l'héritage " est un bon principe, mais assez tôt, quelqu'un supprimera le contexte et écrira " Pourquoi s'étend est le mal " (hé, même auteur, quelle coïncidence!) Ou "l' héritage est mauvais et doit être détruit ».

Si vous examinez le contenu de l'article, celui-ci contient en fait des points valables, mais il étend simplement le point pour créer un titre de type clic. Par exemple, l'article indique que les détails de l'implémentation ne doivent pas être exposés. Ce sont les principes d’encapsulation et de masquage des données qui sont fondamentaux dans OO. Cependant, une méthode getter n’expose pas, par définition, les détails d’implémentation. Dans le cas d'un objet de données Client , les propriétés de Nom , Adresse, etc. ne constituent pas des détails d'implémentation, mais tout le but de l'objet et doivent faire partie de l'interface publique.

Lisez la suite de l'article auquel vous créez un lien, pour voir comment il suggère de définir des propriétés telles que 'name' et 'salaire' sur un objet 'Employee' sans utiliser les pécheurs. Il s’avère qu’il utilise un motif avec un 'exportateur' qui contient des méthodes appelées add name, add Salary qui, à leur tour, définit les champs du même nom ... Il finit donc par utiliser exactement le motif convention de dénomination différente.

Cela revient à penser que vous évitez les pièges des singletons en les renommant nominalement, tout en conservant la même implémentation.


En oop, les soi-disant experts semblent dire que ce n'est pas le cas.
IntelliData

8
Là encore, certaines personnes ont dit "N'utilisez jamais de POO": harmoff.cat-v.org/software/OO_programming
JacquesB

7
FTR, je pense que plus « experts » enseignent encore absurdement créer getters / setters pour tout , que de ne jamais créer du tout. OMI, ce dernier est un conseil moins trompeur.
gauche vers

4
@leftaroundabout: OK, mais je suggère un compromis entre "toujours" et "jamais", qui est "utiliser quand cela convient".
JacquesB

3
Je pense que le problème est que beaucoup de programmeurs transforment chaque objet (ou trop d'objets) en DTO. Parfois, ils sont nécessaires, mais évitez-les autant que possible car ils séparent les données du comportement. (En supposant que vous êtes un fervent partisan)
user949300

11

Pour transformer la Customerclasse d'un objet de données, nous pouvons nous poser les questions suivantes sur les champs de données:

Comment voulons-nous utiliser {champ de données}? Où est utilisé {champ de données}? L'utilisation de {champ de données} peut-elle et doit-elle être déplacée vers la classe?

Par exemple:

  • Quel est le but de Customer.Name?

    Réponses possibles, affichez le nom dans une page Web de connexion, utilisez le nom dans les mailings au client.

    Ce qui conduit à des méthodes:

    • Customer.FillInTemplate (…)
    • Client.IsApplicableForMailing (…)
  • Quel est le but de Customer.DOB?

    Valider l'âge du client. Remises sur l'anniversaire du client. Mailings.

    • Customer.IsApplicableForProduct ()
    • Customer.GetPersonalDiscount ()
    • Customer.IsApplicableForMailing ()

Compte tenu des commentaires, l'exemple d'objet Customer- à la fois en tant qu'objet de données et en tant qu'objet "réel" avec ses propres responsabilités - est trop large; c'est-à-dire qu'il a trop de propriétés / responsabilités. Ce qui conduit soit à de nombreux composants en fonction de Customer(en lisant ses propriétés), soit à Customerde nombreux composants. Peut-être existe-t-il différentes vues du client, peut-être que chacune devrait avoir sa propre classe 1 distincte :

  • Le client dans le cadre d' Accountopérations monétaires n'est probablement utilisé que pour:

    • aider les humains à identifier que leur transfert d'argent va à la bonne personne; et
    • groupe Accounts.

    Ce client n'a pas besoin des domaines tels que DOB, FavouriteColour, Telet peut - être même pas Address.

  • Le client dans le contexte d'un utilisateur se connectant à un site Web bancaire.

    Les champs pertinents sont:

    • FavouriteColour, qui pourrait prendre la forme d’une thématisation personnalisée;
    • LanguagePreferences, et
    • GreetingName

    Au lieu de propriétés avec des getters et des setters, celles-ci pourraient être capturées dans une seule méthode:

    • PersonaliseWebPage (page de modèle);
  • Le client dans le contexte du marketing et de la messagerie personnalisée.

    Ici, ne pas s'appuyer sur les propriétés d'un objet de données, mais plutôt sur les responsabilités de l'objet; par exemple:

    • IsCustomerInterestedInAction (); et
    • GetPersonalDiscounts ().

    Le fait que cet objet client ait une FavouriteColourpropriété et / ou une Addresspropriété devient sans objet: l'implémentation utilise peut-être ces propriétés; mais il peut également utiliser certaines techniques d'apprentissage automatique et utiliser les interactions précédentes avec le client pour découvrir les produits susceptibles d'intéresser le client.


1. Bien sûr, les classes Customeret Accountétaient des exemples. Pour un exemple ou un exercice simple, diviser ce client peut être excessif, mais avec l'exemple du fractionnement, j'espère démontrer que la méthode permettant de transformer un objet de données en objet avec les responsabilités vont fonctionner.


4
Upvote parce que vous répondez réellement à la question :) Cependant, il est également évident que les solutions proposées sont bien pires que de simplement avoir des gettes / setters - par exemple, FillInTemplate enfreint clairement le principe de séparation des préoccupations. Ce qui prouve que la prémisse de la question est erronée.
JacquesB

@ Kasper van den Berg: Et quand vous avez beaucoup d'attributs dans le client, comme c'est généralement le cas, comment les définissez-vous initialement?
IntelliData

2
@IntelliData vos valeurs proviennent probablement d'une base de données, XML, etc. Le Client, ou un CustomerBuilder, les lit, puis les définit en utilisant (c'est-à-dire en Java) un accès aux classes privé / package / inner, ou (ick) réflexion. Pas parfait, mais vous pouvez généralement éviter les décideurs publics. (Voir ma réponse pour plus de détails)
user949300

6
Je ne pense pas que la catégorie de clientèle devrait être au courant de ces choses.
CodesInChaos

Qu'en est-il quelque chose comme Customer.FavoriteColor?
Gabe

8

TL; DR

  • La modélisation du comportement est bonne.

  • La modélisation pour de bonnes (!) Abstractions est préférable.

  • Parfois, des objets de données sont requis.


Comportement et Abstraction

Il y a plusieurs raisons pour éviter les getters et les setters. Comme vous l'avez fait remarquer, l'une consiste à éviter de modéliser des données. C'est en fait la raison mineure. La principale raison est de fournir l'abstraction.

Dans votre exemple avec le compte bancaire, c’est clair: une setBalance()méthode serait très mauvaise, car l’établissement d’un solde n’est pas la raison pour laquelle un compte doit être utilisé. Le comportement du compte doit résumer autant que possible de son solde actuel. Il peut prendre en compte le solde lorsqu’il décide d’échouer un retrait, il peut donner accès au solde actuel, mais la modification de l’interaction avec un compte bancaire ne doit pas obliger l’utilisateur à calculer le nouveau solde. C'est ce que le compte devrait faire lui-même.

Même une paire de deposit()et withdraw()méthodes n'est pas idéal pour modéliser un compte bancaire. Un meilleur moyen serait de ne fournir qu'une transfer()méthode prenant en compte un autre compte et un montant en argument. Cela permettrait à la classe de comptes de s’assurer de manière triviale que vous ne créez / détruisez pas accidentellement de l’argent dans votre système, cela fournirait une abstraction très utilisable et fournirait aux utilisateurs plus de renseignements, car cela obligerait à utiliser des comptes spéciaux. gagné / investi / perdu de l’argent (voir double comptabilité ). Bien sûr, chaque utilisation d’un compte n’a pas besoin de ce niveau d’abstraction, mais il vaut certainement la peine de prendre en compte le niveau d’abstraction que vos classes peuvent fournir.

Notez que fournir l'abstraction et masquer les données internes n'est pas toujours la même chose. Presque toutes les applications contiennent des classes qui ne sont en réalité que des données. Les tuples, les dictionnaires et les tableaux en sont des exemples fréquents. Vous ne voulez pas cacher la coordonnée x d'un point à l'utilisateur. Il y a très peu d'abstraction que vous pouvez / devriez faire avec un point.


La classe client

Un client est certainement une entité de votre système qui devrait essayer de fournir des abstractions utiles. Par exemple, il devrait probablement être associé à un panier et la combinaison du panier et du client devrait permettre de commettre un achat, ce qui pourrait déclencher des actions telles que l’envoi des produits demandés, le paiement d’argent (compte tenu du paiement choisi). méthode), etc.

Le problème est que toutes les données que vous avez mentionnées ne sont pas uniquement associées à un client, elles sont également mutables. Le client peut déménager. Ils peuvent changer leur compagnie de carte de crédit. Ils peuvent changer leur adresse email et leur numéro de téléphone. Heck, ils peuvent même changer de nom et / ou de sexe! Ainsi, une classe de clients complète doit en effet fournir un accès de modification complet à tous ces éléments de données.

Pourtant, les setters peuvent / doivent fournir des services non négligeables: Ils peuvent assurer un format correct des e - mails-, la vérification des adresses postales, etc. adresses De même, les « apporteurs » peuvent fournir des services de haut niveau comme la fourniture de courrier électronique dans le-adresses Name <user@server.com>Format en utilisant les champs de nom et l’adresse e-mail déposée, ou en fournissant une adresse postale correctement formatée, etc. Bien entendu, le contenu de cette fonctionnalité de haut niveau dépend en grande partie de votre cas d'utilisation. C'est peut-être un peu exagéré ou cela peut demander à une autre classe de bien faire les choses. Le choix du niveau d'abstraction n'est pas facile.


Cela semble correct, bien que je ne sois pas d’accord sur le sexe ...;)
IntelliData

6

En essayant de développer la réponse de Kasper, il est plus facile de se plaindre et d’éliminer les setters. Dans un argument plutôt vague, à la main (et heureusement humoristique):

Quand Customer.Name changera-t-il?

Rarement. Peut-être qu'ils se sont mariés. Ou est entré dans la protection des témoins. Mais dans ce cas, vous voudriez également vérifier et éventuellement changer leur lieu de résidence, leurs proches parents et d’autres informations.

Quand la date de naissance changerait-elle?

Seulement lors de la création initiale ou lors de la saisie de données. Ou si elles sont un joueur de baseball Domincan. :-)

Ces champs ne doivent pas être accessibles avec les paramètres habituels. Vous avez peut-être une Customer.initialEntry()méthode ou une Customer.screwedUpHaveToChange()méthode qui nécessite des autorisations spéciales. Mais n'a pas de Customer.setDOB()méthode publique .

En général, un client est lu à partir d'une base de données, d'une API REST, de XML, etc. Utilisez une méthode Customer.readFromDB()ou, si vous êtes plus strict en matière de SRP / séparation des problèmes, vous aurez un générateur séparé, par exemple un CustomerPersisterobjet avec une read()méthode. En interne, ils définissent en quelque sorte les champs (je préfère utiliser l'accès aux packages ou une classe interne, YMMV). Mais encore une fois, évitez les setters publics.

(Addendum en tant que question a quelque peu changé ...)

Supposons que votre application utilise beaucoup les bases de données relationnelles. Il serait insensé d'avoir Customer.saveToMYSQL()ou des Customer.readFromMYSQL()méthodes. Cela crée un couplage indésirable avec une entité concrète, non standard et susceptible de changer . Par exemple, lorsque vous modifiez le schéma ou passez à Postgress ou Oracle.

Cependant, l' OMI, il est parfaitement acceptable pour quelques clients à une norme abstraite , ResultSet. Un objet assistant séparé (que je vais appeler CustomerDBHelper, qui est probablement une sous-classe de AbstractMySQLHelper) connaît toutes les connexions complexes à votre base de données, connaît les détails d'optimisation complexes, connaît les tables, la requête, les jointures, etc. (ou utilise un ORM comme Hibernate) pour générer le ResultSet. Votre objet parle à la ResultSet, qui est une norme abstraite , peu susceptible de changer. Lorsque vous modifiez la base de données sous-jacente ou le schéma, le client ne change pas , contrairement à CustomerDBHelper . Si vous êtes chanceux, seul AbstractMySQLHelper est modifié. Il effectue automatiquement les modifications pour le client, le commerçant, l'expédition, etc.

De cette façon, vous pouvez (peut-être) éviter ou réduire le besoin de getters et de setters.

Et, l’essentiel de l’article Holub, comparez ce qui précède avec ce que vous feriez si vous utilisiez des accesseurs et des setters pour tout et que vous changiez la base de données.

De même, supposons que vous utilisiez beaucoup de XML. IMO, vous pouvez coupler votre client à un standard abstrait, tel qu'un Python xml.etree.ElementTree ou un Java org.w3c.dom.Element . Le client obtient et se définit à partir de cela. Encore une fois, vous pouvez (peut-être) réduire le besoin d’obtenteurs et de passeurs.


diriez-vous qu'il est conseillé d'utiliser un modèle de générateur?
IntelliData

Builder est utile pour rendre la construction d'un objet plus facile et plus robuste et, si vous le souhaitez, permettre à l'objet d'être immuable. Cependant, il expose toujours (partiellement) le fait que l'objet sous-jacent a un champ DOB, donc ce n'est pas la fin de tout.
user949300

1

Le problème des getters et des setters peut être dû au fait qu'une classe peut être utilisée dans la logique métier d'une manière, mais vous pouvez également avoir des classes auxiliaires pour sérialiser / désérialiser les données d'une base de données, d'un fichier ou d'un autre stockage persistant.

Étant donné qu'il existe de nombreuses façons de stocker / récupérer vos données et que vous souhaitez dissocier les objets de données de leur mode de stockage, l'encapsulation peut être "compromise" en rendant ces membres publics, ou en les rendant accessibles via des accesseurs ce qui est presque aussi mauvais que de les rendre public.

Il y a différentes façons de contourner cela. Une façon est de rendre les données disponibles à un "ami". Bien que l'amitié ne soit pas héritée, ceci peut être surmonté par n'importe quel sérialiseur demandant les informations à un ami, c'est-à-dire le sérialiseur de base "transmettant" les informations.

Votre classe pourrait avoir une méthode générique "fromMetadata" ou "toMetadata". From-metadata construit un objet et peut donc être un constructeur. S'il s'agit d'un langage typé dynamiquement, les métadonnées sont assez standard pour un tel langage et constituent probablement le principal moyen de construire de tels objets.

Si votre langage est spécifiquement C ++, une solution consiste à avoir une "structure" publique de données, puis à donner à votre classe une instance de cette "structure" en tant que membre et en fait à toutes les données que vous allez stocker. récupérer pour y être stocké. Vous pouvez ensuite facilement écrire des "wrappers" pour lire / écrire vos données dans plusieurs formats.

Si votre langage est C # ou Java et que vous n'avez pas de "struct", vous pouvez le faire de la même manière, mais votre struct est maintenant une classe secondaire. Il n'y a pas de véritable concept de "propriété" des données ou de const-ness. Par conséquent, si vous distribuez une instance de la classe contenant vos données et qu'elle est entièrement publique, tout ce qui reste en attente peut la modifier. Vous pouvez le "cloner" bien que cela puisse coûter cher. Sinon, vous pouvez faire en sorte que cette classe ait des données privées, mais utilisez des accesseurs. Cela donne aux utilisateurs de votre classe un moyen détourné d'accéder aux données, mais ce n'est pas l'interface directe avec votre classe. Il s'agit en réalité d'un détail du stockage des données de la classe, ce qui constitue également un cas d'utilisation.


0

La POO consiste à encapsuler et à cacher les comportements à l'intérieur d'objets. Les objets sont des boîtes noires. C'est une façon de concevoir quelque chose. L'actif est dans de nombreux cas, il n'est pas nécessaire de connaître l'état interne d'un autre composant et il est préférable de ne pas le savoir. Vous pouvez appliquer cette idée principalement avec des interfaces ou à l'intérieur d'un objet avec une visibilité et en veillant à ce que seuls les verbes / actions autorisés soient disponibles pour l'appelant.

Cela fonctionne bien pour une sorte de problème. Par exemple, dans les interfaces utilisateur pour modéliser des composants d'interface utilisateur individuels. Lorsque vous interagissez avec une zone de texte, vous ne vous préoccupez que de définir le texte, de l'obtenir ou d'écouter l'événement de changement de texte. Vous n'êtes généralement pas intéressé par l'emplacement du curseur, la police utilisée pour dessiner le texte ou l'utilisation du clavier. L'encapsulation en fournit beaucoup ici.

Au contraire, lorsque vous appelez un service réseau, vous fournissez une entrée explicite. Il y a généralement une grammaire (comme en JSON ou XML) et toute possibilité d'appeler le service n'a aucune raison d'être cachée. L'idée est que vous pouvez appeler le service comme vous le souhaitez et que le format des données est public et publié.

Dans ce cas, ou dans beaucoup d’autres (comme l’accès à une base de données), vous travaillez réellement avec des données partagées. En tant que tel, il n’ya aucune raison de le cacher, vous voulez au contraire le rendre disponible. Il peut y avoir un problème d’accès en lecture / écriture ou de cohérence de datacheck, mais à ce noyau, le concept de noyau s’il est public.

Pour une telle exigence de conception où vous souhaitez éviter l’encapsulation et rendre les choses publiques et claires, vous voulez éviter les objets. Ce dont vous avez vraiment besoin, ce sont des n-uplets, des structures C ou leurs équivalents, pas des objets.

Mais cela se produit également dans des langages tels que Java, les objets ou les tableaux d'objets ne peuvent être modélisés. Les objets eux-mêmes peuvent contenir quelques types natifs (int, float ...) mais c'est tout. Mais les objets peuvent aussi se comporter comme une simple structure avec seulement des champs publics et tout.

Donc, si vous modélisez des données, vous pouvez utiliser uniquement des champs publics à l'intérieur d'objets car vous n'en avez pas besoin de plus. Vous n'utilisez pas d'encapsulation parce que vous n'en avez pas besoin. Ceci est fait de cette façon dans beaucoup de langues. En java, historiquement, une rose standard où avec getter / setter vous pouviez au moins avoir un contrôle lecture / écriture (en n’ajoutant pas setter par exemple) et que les outils et le framework utilisant l’API instrospection rechercheraient des méthodes getter / setter et les utiliseraient remplir automatiquement le contenu ou afficher les thèses sous forme de champs modifiables dans l'interface utilisateur générée automatiquement.

Là aussi l'argument vous pouvez ajouter un peu de logique / vérification dans la méthode setter.

En réalité, il n’ya pratiquement aucune justification pour les getter / setters car ils sont le plus souvent utilisés pour modéliser des données pures. Les frameworks et les développeurs utilisant vos objets ne s’attendent pas à ce que le getter / setter ne fasse que définir / récupérer les champs de toute façon. En réalité, vous ne faites pas plus avec getter / setter que ce qui pourrait être fait avec des champs publics.

Mais c’est difficile de supprimer les vieilles habitudes et les vieilles habitudes… Vous pourriez même être menacé par vos collègues ou votre professeur si vous ne placez pas aveuglément des passeurs / passeurs partout s'ils ne possèdent pas le fond pour mieux comprendre ce qu’ils sont et ce qu’ils sont. ne pas.

Vous auriez probablement besoin de changer de langue pour vous débarrasser de tous ces codes de getters / setters. (Comme C # ou lisp). Pour moi, les getters / passeurs ne sont qu'une autre erreur d'un milliard de dollars ...


6
Les propriétés C #
n'implémentent

1
L'avantage de [gs] etters est que vous pouvez faire ce que vous n'avez pas l'habitude de faire (vérifier les valeurs, avertir les observateurs), simuler des champs inexistants, etc., et surtout que vous pouvez le changer plus tard . Avec Lombok , le passe- partout est parti: @Getter @Setter class MutablePoint3D {private int x, y, z;}.
Maaartinus

1
@ Maaartinus Bien sûr, [gs] etter peut tout faire comme n'importe quelle autre méthode. Cela signifie que tout code appelant doit être conscient que toute valeur définie peut générer une exception, être modifiée ou potentiellement envoyer une notification de modification ... ou autre. Que plus ou moins [gs] etter ne donnent pas accès à un champ mais utilisent du code arbitraire.
Nicolas Bousquet

Les propriétés @IntelliData C # permettent de ne pas écrire un seul caractère inutile de boilerplate et de ne pas se soucier de tout ce truc de getter / setter ... C'est déjà une meilleure réalisation que le projet lombok. Également pour moi, un POJO avec seulement des accesseurs / configurateurs n'est pas là pour fournir une encapsulation mais plutôt pour publier un format de données qu'il est libre de lire ou d'écrire en tant qu'échange avec un service. L'encapsulation est alors l'inverse de l'exigence de conception.
Nicolas Bousquet

Je ne pense pas vraiment que les propriétés sont vraiment bonnes. Bien sûr, vous enregistrez le préfixe et les parenthèses, c’est-à-dire 5 caractères par appel, mais 1. ils ressemblent à des champs, ce qui prête à confusion. 2. ils sont une chose supplémentaire pour laquelle vous avez besoin de soutien dans la réflexion. Pas de problème, mais pas de gros avantage non plus (par rapport à Java + Lombok; le Java pur est clairement perdu).
Maaartinus

0

Ainsi, par exemple, dans le cas d’une classe de clients qui conserve beaucoup d’informations sur le client, telles que Nom, date de naissance, tél, adresse, etc., comment éviter les getter / setters pour obtenir et définir tous ces attributs? Quelle méthode de type 'Behavior' peut-on écrire pour renseigner toutes ces données?

Je pense que cette question est épineuse parce que vous vous inquiétez des méthodes de comportement pour remplir les données, mais je ne vois aucune indication sur le comportement que la Customerclasse d'objets est destinée à encapsuler.

Ne confondez pas en Customertant que classe d'objets avec «Client» en tant qu'utilisateur / acteur effectuant différentes tâches à l'aide de votre logiciel.

Lorsque vous parlez d' une classe de clients qui conserve beaucoup d'informations sur le client, il semble que, dans la mesure du possible, votre classe de clients ne la distingue pas vraiment du roc. Un Rockpeut avoir une couleur, vous pouvez lui donner un nom, vous pouvez avoir un champ pour stocker son adresse actuelle, mais nous n'attendons aucun comportement intelligent de la part d'un rocher.

De l'article lié à propos des getters / setters étant pervers:

Le processus de conception OO est centré sur les cas d'utilisation: un utilisateur exécute des tâches autonomes qui ont des résultats utiles. (La connexion n'est pas un cas d'utilisation car il manque un résultat utile dans le domaine du problème. Tirer un chèque de règlement est un cas d'utilisation.) Un système OO implémente ensuite les activités nécessaires pour exécuter les différents scénarios qui composent un cas d'utilisation.

Si aucun comportement Customern'est défini, le fait de désigner un rocher comme un mot ne change pas le fait qu'il ne s'agit que d'un objet avec certaines propriétés que vous souhaitez suivre, et peu importe les astuces que vous souhaitez jouer pour vous écarter des getters et les setters. Un rocher ne se soucie pas d'avoir un nom valide et on ne s'attendrait pas à ce qu'un rocher sache si une adresse est valide ou non.

Votre système de commande peut associer un Rockà une commande d'achat et tant que celle-ci Rocka une adresse définie, une partie du système peut ainsi garantir qu'un article est livré à une pierre.

Dans tous ces cas, il Rocks’agit simplement d’un objet de données et le restera jusqu’à ce que nous définissions des comportements spécifiques avec des résultats utiles au lieu d’hypothétiques.


Essaye ça:

Lorsque vous évitez de surcharger le mot 'Client' avec 2 significations potentiellement différentes, cela devrait faciliter la conceptualisation.

Est-ce qu'un Rockobjet passe un ordre ou est-ce quelque chose qu'un être humain fait en cliquant sur des éléments de l'interface utilisateur pour déclencher des actions dans votre système?


Le client est un acteur qui fait beaucoup de choses, mais qui a aussi beaucoup d’informations associées; cela justifie-t-il la création de 2 classes distinctes, 1 en tant qu'acteur et 1 en tant qu'objet de données?
IntelliData

@IntelliData en passant des objets riches à travers des couches est une tâche difficile. Si vous envoyez l'objet depuis un contrôleur vers une vue, celle-ci doit comprendre le contrat général de l'objet (standard JavaBeans par exemple). Si vous envoyez l'objet sur le réseau, JaxB ou un outil similaire doit pouvoir le rétablir dans un objet muet (car vous ne leur avez pas donné l'objet riche complet). Les objets riches sont parfaits pour manipuler des données - ils sont médiocres pour transférer un état. Inversement, les objets muets sont médiocres pour manipuler des données et ok pour transférer un état.

0

J'ajoute mes 2 centimes ici en mentionnant l' approche des objets parlant SQL .

Cette approche est basée sur la notion d'objet autonome. Il dispose de toutes les ressources nécessaires pour mettre en œuvre son comportement. Il n'est pas nécessaire de lui dire comment faire son travail - une requête déclarative suffit. Et un objet n'a certainement pas à contenir toutes ses données en tant que propriétés de classe. Ce n'est vraiment pas - et ne devrait pas - peu importe d'où ils viennent.

En parlant d'un agrégat , l'immuabilité n'est pas un problème. Disons que vous avez une séquence d’états que l’agrégat peut contenir: Racine agrégée comme saga C’est parfaitement bien d’implémenter chaque état en tant qu’objet autonome. Vous pourriez probablement aller encore plus loin: discutez avec votre expert de domaine. Les chances sont qu'il ou elle ne voit pas cet agrégat comme une entité unifiée. Chaque état a probablement sa propre signification, méritant son propre objet.

Enfin, j'aimerais noter que le processus de recherche d'objet est très similaire avec la décomposition du système en sous-systèmes . Les deux sont basés sur le comportement, pas autre chose.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.