Concevoir une classe pour prendre des classes entières comme paramètres plutôt que comme propriétés individuelles


30

Disons, par exemple, que vous avez une application avec une classe largement partagée appelée User. Cette classe expose toutes les informations sur l'utilisateur, son identifiant, son nom, les niveaux d'accès à chaque module, le fuseau horaire, etc.

Les données utilisateur sont évidemment largement référencées dans tout le système, mais pour une raison quelconque, le système est configuré de sorte qu'au lieu de passer cet objet utilisateur dans les classes qui en dépendent, nous ne faisons que lui transmettre des propriétés individuelles.

Une classe qui nécessite l'ID utilisateur, nécessitera simplement le GUID en userIdtant que paramètre, parfois nous pourrions également avoir besoin du nom d'utilisateur, de sorte qu'il soit transmis en tant que paramètre distinct. Dans certains cas, cela est transmis à des méthodes individuelles, donc les valeurs ne sont pas du tout conservées au niveau de la classe.

Chaque fois que j'ai besoin d'accéder à une information différente de la classe User, je dois apporter des modifications en ajoutant des paramètres et lorsque l'ajout d'une nouvelle surcharge n'est pas approprié, je dois également modifier chaque référence à la méthode ou au constructeur de classe.

L'utilisateur n'est qu'un exemple. Ceci est largement pratiqué dans notre code.

Ai-je raison de penser qu'il s'agit d'une violation du principe ouvert / fermé? Pas seulement l'acte de changer les classes existantes, mais de les mettre en place en premier lieu afin que des changements généralisés soient très probablement nécessaires à l'avenir?

Si nous venons de passer dans l' Userobjet, je pourrais apporter un petit changement à la classe avec laquelle je travaille. Si je dois ajouter un paramètre, je devrai peut-être apporter des dizaines de modifications aux références à la classe.

Y a-t-il d'autres principes brisés par cette pratique? Inversion de dépendance peut-être? Bien que nous ne référençons pas une abstraction, il n'y a qu'un seul type d'utilisateur, il n'est donc pas vraiment nécessaire d'avoir une interface utilisateur.

Y a-t-il d'autres principes non SOLIDES violés, tels que les principes de programmation défensive de base?

Si mon constructeur ressemble à ceci:

MyConstructor(GUID userid, String username)

Ou ca:

MyConstructor(User theUser)

Posté:

Il a été suggéré de répondre à la question sous "ID passe ou objet?". Cela ne répond pas à la question de savoir comment la décision d'aller dans les deux sens affecte une tentative de suivre les principes SOLIDES, qui est au cœur de cette question.


11
@gnat: Ce n'est certainement pas un doublon. Le doublon possible concerne le chaînage de méthodes pour pénétrer profondément dans une hiérarchie d'objets. Cette question ne semble pas du tout poser la question.
Greg Burghardt

2
Le deuxième formulaire est souvent utilisé lorsque le nombre de paramètres transmis est devenu trop lourd.
Robert Harvey

12
La seule chose que je n'aime pas à propos de la première signature est qu'il n'y a aucune garantie que l'ID utilisateur et le nom d'utilisateur proviennent réellement du même utilisateur. C'est un bug potentiel qui est évité en faisant circuler l'utilisateur partout. Mais la décision dépend vraiment de ce que font les méthodes appelées avec les arguments.
17 de 26

9
Le mot «analyser» n'a aucun sens dans le contexte dans lequel vous l'utilisez. Voulez-vous dire «passer» à la place?
Konrad Rudolph

5
Et le Idans SOLID? MyConstructordit essentiellement "j'ai besoin d'un Guidet d'un string". Alors pourquoi ne pas avoir une interface fournissant a Guidet a string, laisser Userimplémenter cette interface et laisser MyConstructordépendre d'une instance implémentant cette interface? Et si les besoins MyConstructorchangent, changez d'interface. - Cela m'a beaucoup aidé à penser que les interfaces "appartiennent" au consommateur plutôt qu'au fournisseur . Alors pensez "en tant que consommateur, j'ai besoin de quelque chose qui fait ceci et cela" au lieu de "en tant que fournisseur, je peux faire ceci et cela".
Corak

Réponses:


31

Il n'y a absolument rien de mal à passer un Userobjet entier en paramètre. En fait, cela pourrait aider à clarifier votre code et à rendre plus évident pour les programmeurs ce qu'une méthode prend si la signature de la méthode nécessite un User.

Passer des types de données simples est agréable, jusqu'à ce qu'ils signifient autre chose que ce qu'ils sont. Considérez cet exemple:

public class Foo
{
    public void Bar(int userId)
    {
        // ...
    }
}

Et un exemple d'utilisation:

var user = blogPostRepository.Find(32);
var foo = new Foo();

foo.Bar(user.Id);

Pouvez-vous repérer le défaut? Le compilateur ne peut pas. L '"ID utilisateur" transmis n'est qu'un entier. Nous nommons la variable usermais initialisons sa valeur à partir de l' blogPostRepositoryobjet, qui renvoie vraisemblablement des BlogPostobjets, pas des Userobjets - pourtant le code se compile et vous vous retrouvez avec une erreur d'exécution bancale.

Considérons maintenant cet exemple modifié:

public class Foo
{
    public void Bar(User user)
    {
        // ...
    }
}

Peut-être que la Barméthode utilise uniquement "l'ID utilisateur" mais la signature de la méthode nécessite un Userobjet. Revenons maintenant au même exemple d'utilisation qu'avant, mais modifions-le pour passer l'intégralité de l '"utilisateur" dans:

var user = blogPostRepository.Find(32);
var foo = new Foo();

foo.Bar(user);

Nous avons maintenant une erreur de compilation. La blogPostRepository.Findméthode retourne un BlogPostobjet, que nous appelons habilement "utilisateur". Ensuite, nous transmettons cet "utilisateur" à la Barméthode et obtenons rapidement une erreur de compilation, car nous ne pouvons pas passer un BlogPostà une méthode qui accepte un User.

Le système de type du langage est exploité pour écrire le code correct plus rapidement et identifier les défauts au moment de la compilation plutôt qu'au moment de l'exécution.

Vraiment, devoir refactoriser beaucoup de code parce que les informations utilisateur changent n'est qu'un symptôme d'autres problèmes. En passant un Userobjet entier , vous bénéficiez des avantages ci-dessus, en plus de ne pas avoir à refactoriser toutes les signatures de méthode qui acceptent les informations utilisateur lorsque quelque chose au sujet de la Userclasse change.


6
Je dirais que votre raisonnement en lui-même pointe en fait vers le passage de champs mais que les champs soient des enveloppes triviales autour de la valeur réelle. Dans cet exemple, un utilisateur a un champ de type UserID et un UserID a un seul champ à valeur entière. Maintenant, la déclaration de Bar vous dit immédiatement que Bar n'utilise pas toutes les informations sur l'utilisateur, seulement son ID, mais vous ne pouvez toujours pas faire d'erreurs idiotes comme passer un entier qui ne provient pas d'un ID utilisateur dans Bar.
Ian

(Cont.) Bien sûr, ce type de style de programmation est assez fastidieux, surtout dans un langage qui n'a pas de support syntaxique agréable (Haskell est bien pour ce style par exemple, car vous pouvez simplement faire correspondre "ID utilisateur") .
Ian

5
@Ian: Je pense que le fait d'enrouler un identifiant dans son propre type contourne le problème d'origine soulevé par l'OP, qui est que les modifications structurelles de la classe User nécessitent de refactoriser de nombreuses signatures de méthode. Le passage de l'objet User entier résout ce problème.
Greg Burghardt

@Ian: Bien que pour être honnête, même en travaillant en C #, j'ai été très tenté d'encapsuler les ID et le tri dans un Struct juste pour donner un peu plus de clarté.
Greg Burghardt

1
"il n'y a rien de mal à passer un pointeur à sa place." Ou une référence, pour éviter tous les problèmes de pointeurs que vous pourriez rencontrer.
Yay295

17

Ai-je raison de penser qu'il s'agit d'une violation du principe ouvert / fermé?

Non, ce n'est pas une violation de ce principe. Ce principe vise à ne pas changer Userde manière à affecter les autres parties du code qui l'utilisent. Vos modifications Userpourraient constituer une telle violation, mais cela n'est pas lié.

Y a-t-il d'autres principes brisés par cette pratique? Peut-être une inversion de dépendance?

Non. Ce que vous décrivez - en injectant uniquement les parties requises d'un objet utilisateur dans chaque méthode - est le contraire: c'est une inversion de dépendance pure.

Y a-t-il d'autres principes non SOLIDES violés, tels que les principes de programmation défensive de base?

Non. Cette approche est un moyen de codage parfaitement valable. Il ne viole pas de tels principes.

Mais l'inversion de dépendance n'est qu'un principe; ce n'est pas une loi incassable. Et la DI pure peut ajouter de la complexité au système. Si vous constatez que l'injection des valeurs utilisateur nécessaires dans les méthodes plutôt que de passer l'intégralité de l'objet utilisateur dans la méthode ou le constructeur crée des problèmes, ne le faites pas de cette façon. Il s'agit de trouver un équilibre entre les principes et le pragmatisme.

Pour répondre à votre commentaire:

Il y a des problèmes à analyser inutilement une nouvelle valeur en bas de cinq niveaux de la chaîne, puis à modifier toutes les références aux cinq méthodes existantes ...

Une partie du problème ici est que vous n'aimez manifestement pas cette approche, selon le commentaire "inutilement [passer] ...". Et c'est assez juste; il n'y a pas de bonne réponse ici. Si vous trouvez cela pénible, ne le faites pas de cette façon.

Cependant, en ce qui concerne le principe ouvert / fermé, si vous suivez strictement cela, alors "... changer toutes les références à ces cinq méthodes existantes ..." est une indication que ces méthodes ont été modifiées, quand elles devraient être fermé à modification. En réalité cependant, le principe ouvert / fermé est logique pour les API publiques, mais n'a pas beaucoup de sens pour les internes d'une application.

... mais il est certain qu'un plan pour respecter ce principe dans la mesure du possible inclurait des stratégies pour réduire le besoin de changements futurs?

Mais alors vous vous promenez sur le territoire YAGNI et ce serait toujours orthogonal au principe. Si vous avez une méthode Fooqui prend un nom d'utilisateur et que vous souhaitez également Fooprendre une date de naissance, en suivant le principe, vous ajoutez une nouvelle méthode; Fooreste inchangé. Encore une fois, c'est une bonne pratique pour les API publiques, mais c'est un non-sens pour le code interne.

Comme mentionné précédemment, il s'agit d'équilibre et de bon sens pour une situation donnée. Si ces paramètres changent souvent, alors oui, utilisez-les Userdirectement. Cela vous évitera les changements à grande échelle que vous décrivez. Mais s'ils ne changent pas souvent, transmettre uniquement ce qui est nécessaire est également une bonne approche.


Il y a des problèmes à analyser inutilement une nouvelle valeur sur cinq niveaux de la chaîne, puis à modifier toutes les références à ces cinq méthodes existantes. Pourquoi le principe Open / Closed ne s'appliquerait-il qu'à la classe User et pas aussi à la classe que je modifie actuellement, qui est également consommée par d'autres classes? Je sais que le principe vise spécifiquement à éviter le changement, mais un plan pour respecter ce principe dans la mesure du possible comprendrait-il des stratégies pour réduire le besoin de changements futurs?
Jimbo

@Jimbo, j'ai mis à jour ma réponse pour essayer de répondre à votre commentaire.
David Arno

J'apprécie votre contribution. BTW. Même Robert C Martin n'accepte pas que le principe Open / Closed ait une règle stricte. C'est une règle d'or qui sera inévitablement brisée. L'application du principe est un exercice visant à le respecter autant que possible. C'est pourquoi j'ai utilisé le mot "praticable" précédemment.
Jimbo

Ce n'est pas une inversion de dépendance pour passer les paramètres de l'utilisateur plutôt que l'utilisateur lui-même.
James Ellis-Jones

@ JamesEllis-Jones, l'inversion de dépendance inverse les dépendances de "demander" à "dire". Si vous passez une Userinstance, puis interrogez cet objet pour obtenir un paramètre, vous n'inversez que partiellement les dépendances; il y a encore des demandes en cours. La véritable inversion des dépendances est 100% "dites, ne demandez pas". Mais cela a un prix complexe.
David Arno

10

Oui, la modification d'une fonction existante est une violation du principe ouvert / fermé. Vous modifiez quelque chose qui devrait être fermé à la modification en raison du changement des exigences. Une meilleure conception (pour ne pas changer lorsque les exigences changent) serait de transmettre l'utilisateur à des choses qui devraient fonctionner sur les utilisateurs.

Mais cela pourrait aller à l'encontre du principe de ségrégation d'interface, car vous pourriez transmettre beaucoup plus d'informations que la fonction n'a besoin pour faire son travail.

Donc, comme pour la plupart des choses - cela dépend .

En utilisant juste un nom d'utilisateur, la fonction sera plus flexible, en travaillant avec les noms d'utilisateur indépendamment de d'où ils viennent et sans avoir besoin de créer un objet utilisateur pleinement fonctionnel. Il offre une résilience au changement si vous pensez que la source des données va changer.

L'utilisation de l'ensemble de l'Utilisateur clarifie l'utilisation et conclut un contrat plus ferme avec ses appelants. Il offre une résilience au changement si vous pensez que davantage d'Utilisateur sera nécessaire.


+1 mais je ne suis pas sûr de votre phrase "vous pourriez être en train de passer plus d'informations". Lorsque vous passez (Utilisateur l'utilisateur), vous passez le minimum d'informations, une référence à un objet. Il est vrai que cette référence peut être utilisée pour obtenir plus d'informations, mais cela signifie que le code appelant n'a pas à l'obtenir. Lorsque vous passez (GUID userid, string username), la méthode appelée peut toujours appeler User.find (userid) pour trouver l'interface publique de l'objet, donc vous ne cachez rien.
dcorking

5
@dcorking, " Lorsque vous transmettez (utilisateur l'utilisateur) vous transmettez le minimum d'informations, une référence à un objet ". Vous transmettez le maximum d'informations liées à cet objet: l'objet entier. msgstr " la méthode appelée peut toujours appeler User.find (userid) ...". Dans un système bien conçu, cela ne serait pas possible car la méthode en question n'y aurait pas accès User.find(). En fait , il ne devrait même pas être un User.find. Trouver un utilisateur ne devrait jamais être la responsabilité de User.
David Arno

2
@dcorking - enh. Que vous passiez une référence qui se trouve être petite est une coïncidence technique. Vous associez le tout Userà la fonction. Peut-être que cela a du sens. Mais peut-être que la fonction ne devrait se soucier que du nom de l'utilisateur - et transmettre des informations telles que la date de connexion de l'utilisateur ou l'adresse est incorrecte.
Telastyn

@DavidArno c'est peut-être la clé pour une réponse claire à OP. À qui incombe la responsabilité de trouver un utilisateur? Existe-t-il un nom pour le principe de conception consistant à séparer le chercheur / l'usine de la classe?
dcorking

1
@dcorking Je dirais que c'est une implication du principe de responsabilité unique. Savoir où les utilisateurs sont stockés et comment les récupérer par ID sont des responsabilités distinctes qu'une Userclasse ne devrait pas avoir. Il peut y avoir un UserRepositoryou similaire qui traite de telles choses.
Hulk

3

Cette conception suit le modèle d'objet de paramètre . Il résout les problèmes résultant de la présence de nombreux paramètres dans la signature de la méthode.

Ai-je raison de penser qu'il s'agit d'une violation du principe ouvert / fermé?

Non. L'application de ce modèle active le principe d'ouverture / fermeture (OCP). Par exemple, des classes dérivées de Userpeuvent être fournies en tant que paramètres qui induisent un comportement différent dans la classe consommatrice.

Y a-t-il d'autres principes brisés par cette pratique?

Cela peut arriver. Permettez-moi de vous expliquer sur la base des principes SOLIDES.

Le principe de responsabilité unique (SRP) peut être violé s'il a la conception comme vous l'avez expliqué:

Cette classe expose toutes les informations sur l'utilisateur, son identifiant, son nom, les niveaux d'accès à chaque module, le fuseau horaire, etc.

Le problème est avec toutes les informations . Si la Userclasse a de nombreuses propriétés, elle devient un énorme objet de transfert de données qui transporte des informations non liées du point de vue des classes consommatrices. Exemple: Dans la perspective d'une classe consommer UserAuthenticationla propriété User.Idet User.Namesont pertinents, mais non User.Timezone.

Le principe de ségrégation d'interface (ISP) est également violé avec un raisonnement similaire mais ajoute une autre perspective. Exemple: supposons qu'une classe consommatrice UserManagementnécessite que la propriété User.Namesoit divisée User.LastNameet que User.FirstNamela classe UserAuthenticationdoit également être modifiée pour cela.

Heureusement, le FAI vous offre également une solution possible au problème: généralement, ces objets de paramètres ou ces objets de transport de données commencent petit et augmentent avec le temps. Si cela devient compliqué, envisagez l'approche suivante: introduisez des interfaces adaptées aux besoins des classes consommatrices. Exemple: introduisez des interfaces et laissez la Userclasse en dériver:

class User : IUserAuthenticationInfo, IUserLocationInfo { ... }

Chaque interface doit exposer un sous-ensemble de propriétés connexes de la Userclasse nécessaire pour qu'une classe consommatrice remplisse pleinement son fonctionnement. Recherchez des groupes de propriétés. Essayez de réutiliser les interfaces. Dans le cas de la classe consommatrice, UserAuthenticationutilisez IUserAuthenticationInfoau lieu de User. Ensuite, si possible, divisez la Userclasse en plusieurs classes concrètes en utilisant les interfaces comme "gabarit".


1
Une fois que l'utilisateur se complique, il y a une explosion combinatoire de sous-interfaces possibles par exemple, si l'utilisateur n'a que 3 propriétés, il y a 7 combinaisons possibles. Votre proposition semble agréable mais irréalisable.
user949300

1. Analytiquement, vous avez raison. Cependant, selon la façon dont le domaine est modélisé, les bits d'informations connexes ont tendance à se regrouper. Il n'est donc pratiquement pas nécessaire de traiter toutes les combinaisons possibles d'interfaces et de propriétés. 2. L'approche décrite n'était pas destinée à être une solution universelle, mais je devrais peut-être ajouter un peu plus «possible» et «peut» dans la réponse.
Theo Lenndorff

2

Face à ce problème dans mon propre code, j'ai conclu que les classes / objets de modèle de base sont la réponse.

Un exemple courant serait le modèle de référentiel. Souvent, lors de l'interrogation d'une base de données via des référentiels, de nombreuses méthodes du référentiel prennent en grande partie les mêmes paramètres.

Mes règles générales pour les référentiels sont:

  • Lorsque plusieurs méthodes prennent les mêmes 2 paramètres ou plus, les paramètres doivent être regroupés en tant qu'objet modèle.

  • Lorsqu'une méthode prend plus de 2 paramètres, les paramètres doivent être regroupés en tant qu'objet modèle.

  • Les modèles peuvent hériter d'une base commune, mais seulement lorsque cela a vraiment du sens (généralement mieux refactoriser plus tard que de commencer par l'héritage à l'esprit).


Les problèmes liés à l'utilisation de modèles provenant d'autres couches / zones ne deviennent apparents que lorsque le projet commence à devenir un peu complexe. Ce n'est qu'alors que moins de code crée plus de travail ou plus de complications.

Et oui, il est tout à fait correct d'avoir 2 modèles différents avec des propriétés identiques qui servent des couches / objectifs différents (par exemple, ViewModels vs POCO).


2

Voyons simplement les aspects individuels de SOLID:

  • Responsabilité unique: éventuellement fiolée si les gens ont tendance à ne faire circuler que des sections de la classe
  • Ouvert / fermé: Peu importe où les sections de la classe sont transmises, uniquement là où l'objet complet est transmis. (Je pense que c'est là que la dissonance cognitive entre en jeu: vous devez changer le code lointain mais la classe elle-même semble bien.)
  • Substitution de Liskov: Non, nous ne faisons pas de sous-classes.
  • Inversion de dépendance (dépend des abstractions, pas des données concrètes). Oui, c'est violé: les gens n'ont pas d'abstractions, ils sortent des éléments concrets de la classe et les transmettent. Je pense que c'est le principal problème ici.

Une chose qui tend à confondre les instincts de conception est que la classe est essentiellement destinée aux objets globaux et essentiellement en lecture seule. Dans une telle situation, la violation des abstractions ne fait pas beaucoup de mal: la simple lecture de données non modifiées crée un couplage assez faible; ce n'est que lorsqu'elle devient un énorme tas que la douleur devient perceptible.
Pour rétablir les instincts de conception, supposez simplement que l'objet n'est pas très global. De quel contexte une fonction aurait-elle besoin si l' Userobjet pouvait être muté à tout moment? Quels composants de l'objet seraient probablement mutés ensemble? Ceux-ci peuvent être séparés User, que ce soit en tant que sous-objet référencé ou en tant qu'interface qui expose uniquement une "tranche" de champs connexes n'est pas si important.

Un autre principe: regardez les fonctions qui utilisent des parties de User et voyez quels champs (attributs) ont tendance à aller ensemble. C'est une bonne liste préliminaire de sous-objets - vous devez certainement vous demander s'ils vont vraiment ensemble.

C'est beaucoup de travail, et c'est un peu difficile à faire, et votre code deviendra légèrement moins flexible car il deviendra un peu plus difficile d'identifier le sous-objet (sous-interface) qui doit être transmis à une fonction, en particulier si les sous-objets se chevauchent.

Le fractionnement Userdeviendra en fait laid si les sous-objets se chevauchent, alors les gens seront confus quant à celui à choisir si tous les champs requis proviennent du chevauchement. Si vous vous séparez de manière hiérarchique (par exemple, vous en avez UserMarketSegment, entre autres UserLocation), les gens ne seront pas sûrs du niveau auquel la fonction qu'ils écrivent est: traite-t-il des données utilisateur au Location niveau ou au MarketSegmentniveau? Cela n'aide pas vraiment que cela puisse changer avec le temps, c'est-à-dire que vous revenez à changer les signatures de fonction, parfois sur toute une chaîne d'appel.

En d'autres termes: à moins que vous ne connaissiez vraiment votre domaine et que vous n'ayez une idée assez claire de quel module traite de quels aspects User, cela ne vaut pas vraiment la peine d'améliorer la structure du programme.


1

C'est une question vraiment intéressante. Cela dépend.

Si vous pensez que votre méthode peut changer à l'avenir en interne pour nécessiter différents paramètres de l'objet User, vous devez certainement passer le tout. L'avantage est que le code externe à la méthode est ensuite protégé contre les modifications au sein de la méthode en termes de paramètres qu'il utilise, ce qui, comme vous le dites, entraînerait une cascade de modifications externes. Ainsi, le passage à l'ensemble de l'utilisateur augmente l'encapsulation.

Si vous êtes sûr que vous n'aurez jamais besoin d'utiliser autre chose que de dire l'e-mail de l'utilisateur, vous devez le transmettre. L'avantage de ceci est que vous pouvez ensuite utiliser la méthode dans un plus large éventail de contextes: vous pouvez l'utiliser par exemple avec le courriel d'une entreprise ou avec un courriel que quelqu'un vient de taper. Cela augmente la flexibilité.

Cela fait partie d'un éventail plus large de questions sur la création de classes pour avoir une portée large ou étroite, y compris s'il faut injecter ou non des dépendances et si des objets sont disponibles globalement. Il y a actuellement une tendance malheureuse à penser qu'une portée plus étroite est toujours bonne. Cependant, il y a toujours un compromis entre l'encapsulation et la flexibilité comme dans ce cas.


1

Je trouve qu'il vaut mieux passer le moins de paramètres possible et autant que nécessaire. Cela facilite les tests et ne nécessite pas de créer des objets entiers.

Dans votre exemple, si vous allez utiliser uniquement l'ID utilisateur ou le nom d'utilisateur, c'est tout ce que vous devez transmettre. Si ce modèle se répète plusieurs fois et que l'objet utilisateur réel est beaucoup plus grand, mon conseil est de créer une ou des interfaces plus petites pour cela. Il pourrait être

interface IIdentifieable
{
    Guid ID { get; }
}

ou

interface INameable
{
    string Name { get; }
}

Cela rend le test avec la simulation beaucoup plus facile et vous savez instantanément quelles valeurs sont vraiment utilisées. Sinon, vous devez souvent initialiser des objets complexes avec de nombreuses autres dépendances, bien qu'à la fin vous ayez juste besoin d'une ou deux propriétés.


1

Voici quelque chose que j'ai rencontré de temps en temps:

  • Une méthode prend un argument de type User(ou Productou autre) qui a beaucoup de propriétés, même si la méthode n'en utilise que quelques-unes.
  • Pour une raison quelconque, une partie du code doit appeler cette méthode même si elle n'a pas d' Userobjet entièrement rempli . Il crée une instance et initialise uniquement les propriétés dont la méthode a réellement besoin.
  • Cela arrive plusieurs fois.
  • Après un certain temps, lorsque vous rencontrez une méthode qui a un Userargument, vous devez trouver des appels à cette méthode pour trouver d'où Uservient le afin de savoir quelles propriétés sont remplies. S'agit-il d'un "vrai" utilisateur avec une adresse e-mail, ou a-t-il simplement été créé pour transmettre un ID utilisateur et certaines autorisations?

Si vous créez une Useret ne remplissez que quelques propriétés car ce sont celles dont la méthode a besoin, alors l'appelant en sait vraiment plus sur le fonctionnement interne de la méthode qu'il ne devrait.

Pire encore, lorsque vous avez une instance de User, vous devez savoir d'où elle provient pour savoir quelles propriétés sont remplies. Vous ne voulez pas avoir à le savoir.

Au fil du temps, lorsque les développeurs voient Userutilisé comme conteneur pour les arguments de méthode, ils peuvent commencer à lui ajouter des propriétés pour des scénarios à usage unique. Maintenant, ça devient laid, parce que la classe est encombrée de propriétés qui seront presque toujours nulles ou par défaut.

Une telle corruption n'est pas inévitable, mais elle se produit encore et encore lorsque nous passons un objet juste parce que nous avons besoin d'accéder à quelques-unes de ses propriétés. La zone de danger est la première fois que quelqu'un crée une instance de Useret remplit simplement quelques propriétés afin de pouvoir la transmettre à une méthode. Posez votre pied dessus car c'est un chemin sombre.

Dans la mesure du possible, donnez le bon exemple au prochain développeur en ne transmettant que ce dont vous avez besoin.

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.