Quand devez-vous et ne devez-vous pas utiliser le mot-clé «nouveau»?

15

J'ai regardé une présentation de Google Tech Talk sur les tests unitaires , donnée par Misko Hevery, et il a dit d'éviter d'utiliser le newmot - clé dans le code logique métier.

J'ai écrit un programme, et j'ai fini par utiliser le newmot - clé ici et là, mais c'était principalement pour instancier des objets qui contiennent des données (c'est-à-dire qu'ils n'avaient aucune fonction ou méthode).

Je me demande, ai-je fait quelque chose de mal quand j'ai utilisé le nouveau mot-clé pour mon programme. Et où pouvons-nous enfreindre cette «règle»?

Sal
la source
2
Pouvez-vous ajouter une balise liée au langage de programmation? De nouveaux existent dans de nombreux langages (C ++, Java, Ruby pour n'en nommer que quelques-uns) et ont une sémantique différente.
sakisk

Réponses:

16

C'est plus une orientation qu'une règle stricte et rapide.

En utilisant "nouveau" dans votre code de production, vous associez votre classe à ses collaborateurs. Si quelqu'un veut utiliser un autre collaborateur, par exemple une sorte de faux collaborateur pour les tests unitaires, il ne peut pas - car le collaborateur est créé dans votre logique métier.

Bien sûr, quelqu'un doit créer ces nouveaux objets, mais il est souvent préférable de le laisser à l'un des deux endroits: un cadre d'injection de dépendances comme Spring, ou bien dans la classe qui instancie votre classe de logique métier, injectée via le constructeur.

Bien sûr, vous pouvez aller trop loin. Si vous souhaitez renvoyer une nouvelle liste de tableaux, vous êtes probablement d'accord - surtout s'il s'agit d'une liste immuable.

La principale question que vous devriez vous poser est "est la principale responsabilité de ce morceau de code pour créer des objets de ce type, ou est-ce juste un détail d'implémentation que je pourrais raisonnablement déplacer ailleurs?"

Bill Michell
la source
1
Eh bien, si vous voulez que ce soit une liste immuable, vous devez utiliser Collections.unmodifiableListquelque chose. Mais je sais ce que tu veux dire :)
MatrixFrog
Oui, mais vous devez en quelque sorte créer la liste originale que vous convertissez ensuite en non modifiable ...
Bill Michell
5

Le nœud de cette question est à la fin: je me demande, ai-je fait quelque chose de mal quand j'ai utilisé le nouveau mot-clé pour mon programme. Et où pouvons-nous enfreindre cette «règle»?

Si vous êtes capable d'écrire des tests unitaires efficaces pour votre code, vous n'avez rien fait de mal. Si votre utilisation newrend difficile ou impossible le test unitaire de votre code, vous devez alors réévaluer votre utilisation de new. Vous pouvez étendre cette analyse à l'interaction avec d'autres classes, mais la possibilité d'écrire des tests unitaires solides est souvent un proxy assez bon.

ObscureRobot
la source
5

En bref, chaque fois que vous utilisez "nouveau", vous couplez étroitement la classe contenant ce code à l'objet en cours de création; afin d'instancier l'un de ces objets, la classe effectuant l'instanciation doit connaître la classe concrète instanciée. Ainsi, lorsque vous utilisez "nouveau", vous devez vous demander si la classe dans laquelle vous placez l'instanciation est un "bon" endroit pour que ces connaissances résident, et vous êtes prêt à apporter des modifications dans ce domaine si la forme du l'objet à instancier devait changer.

Un couplage étroit, c'est-à-dire un objet ayant une connaissance d'une autre classe concrète, n'est pas toujours à éviter; à un certain niveau, quelque chose, QUELQUE PART, doit savoir comment créer cet objet, même si tout le reste traite de l'objet en recevant une copie de lui ailleurs. Cependant, lorsque la classe en cours de création change, toute classe qui connaît l'implémentation concrète de cette classe doit être mise à jour pour traiter correctement les changements de cette classe.

La question que vous devez toujours vous poser est la suivante: "Le fait de savoir comment créer cette autre classe deviendra-t-il un handicap lors de la maintenance de l'application?" Les deux principales méthodologies de conception (SOLID et GRASP) répondaient généralement «oui», pour des raisons subtilement différentes. Cependant, ce ne sont que des méthodologies, et les deux ont l'extrême limite qu'elles n'ont pas été formulées en fonction de la connaissance de votre programme unique. En tant que tels, ils ne peuvent que pécher par excès de prudence et supposer que tout point de couplage serré vous causera ÉVENTUELLEMENT un problème lié à la modification de l'un ou des deux côtés de ce point. Vous devez prendre une décision finale en sachant trois choses; la meilleure pratique théorique (qui consiste à tout coupler librement car tout peut changer); le coût de la mise en œuvre de la meilleure pratique théorique (qui peut inclure plusieurs nouvelles couches d'abstraction qui faciliteront un type de changement tout en en gênant un autre); et la probabilité dans le monde réel que le type de changement que vous prévoyez sera jamais nécessaire.

Quelques directives générales:

  • Évitez le couplage étroit entre les bibliothèques de code compilées. L'interface entre les DLL (ou un EXE et ses DLL) est le principal endroit où un couplage étroit présentera un inconvénient. Si vous apportez une modification à une classe A dans DLL X et que la classe B dans l'EXE principal connaît la classe A, vous devez recompiler et libérer les deux fichiers binaires. Dans un seul binaire, un couplage plus serré est généralement plus autorisé car le binaire entier doit être reconstruit de toute façon. Parfois, avoir à reconstruire plusieurs binaires est inévitable, mais vous devez structurer votre code afin de pouvoir l'éviter autant que possible, en particulier dans les situations où la bande passante est à un prix élevé (comme le déploiement d'applications mobiles; pousser une nouvelle DLL dans une mise à niveau est beaucoup moins cher que de pousser tout le programme).

  • Évitez un couplage étroit entre les principaux "centres logiques" de votre programme. Vous pouvez considérer un programme bien structuré comme composé de tranches horizontales et verticales. Les tranches horizontales peuvent être des niveaux d'application traditionnels, comme l'interface utilisateur, le contrôleur, le domaine, DAO, les données; des tranches verticales peuvent être définies pour des fenêtres ou des vues individuelles, ou pour des "user stories" individuelles (comme la création d'un nouvel enregistrement d'un type de base). Lorsque vous effectuez un appel qui se déplace vers le haut, le bas, la gauche ou la droite dans un système bien structuré, vous devez généralement résumer cet appel. Par exemple, lorsque la validation doit récupérer des données, elle ne doit pas avoir accès directement à la base de données, mais doit appeler une interface pour la récupération des données, qui est soutenue par l'objet réel qui sait comment le faire. Lorsqu'un contrôle d'interface utilisateur doit exécuter une logique avancée impliquant une autre fenêtre, il doit abstraire le déclenchement de cette logique via un événement et / ou un rappel; il n'a pas à savoir ce qui sera fait en conséquence, vous permettant de changer ce qui sera fait sans changer le contrôle qui le déclenche.

  • Dans tous les cas, réfléchissez à la facilité ou à la difficulté d'un changement et à sa probabilité. Si un objet que vous créez n'est utilisé qu'à partir d'un seul endroit et que vous ne prévoyez pas de le changer, un couplage serré est généralement plus autorisé et peut même être supérieur dans ce cas à un couplage lâche. Le couplage lâche nécessite une abstraction, qui est une couche supplémentaire qui empêche la modification des objets dépendants lorsque l'implémentation d'une dépendance doit changer. Cependant, si l'interface elle-même doit changer (en ajoutant un nouvel appel de méthode ou en ajoutant un paramètre à un appel de méthode existant), une interface augmente en fait la quantité de travail nécessaire pour effectuer le changement. Vous devez évaluer la probabilité que différents types de changements affectent la conception,

KeithS
la source
3

Les objets qui ne contiennent que des données, ou les DTO (objets de transfert de données) conviennent, créez-les toute la journée. Là où vous voulez éviter, newc'est sur les classes qui effectuent des actions, vous voulez que les classes consommatrices se programment à la place sur l' interface , où elles peuvent invoquer cette logique et la consommer, mais ne pas être responsables de la création effective des instances des classes qui contiennent la logique . Ces classes exécutant des actions ou contenant de la logique sont des dépendances. Le discours de Misko vise à vous injecter ces dépendances et à laisser une autre entité être responsable de leur création.

Anthony Pegram
la source
2

D'autres ont déjà mentionné ce point, mais ont voulu le citer du Clean Code d'oncle Bob (Bob Martin) car cela pourrait faciliter la compréhension du concept:

"Un mécanisme puissant pour séparer la construction de l'utilisation est l' injection de dépendance (DI), l'application de l' inversion de contrôle (IoC) à la gestion des dépendances. L'inversion de contrôle déplace les responsabilités secondaires d'un objet vers d'autres objets qui sont dédiés à l'objectif, soutenant ainsi le principe de responsabilité unique . Dans le contexte de la gestion des dépendances, un objet ne devrait pas assumer lui-même la responsabilité de l'instanciation des dépendances. Il devrait plutôt transmettre cette responsabilité à un autre mécanisme "faisant autorité", inversant ainsi le contrôle. Comme la configuration est une préoccupation mondiale, cette le mécanisme faisant autorité sera généralement soit la routine "principale", soit un conteneur à usage spécial. "

Je pense que c'est toujours une bonne idée de suivre cette règle. D'autres ont mentionné le plus important IMO -> il dissocie votre classe de ses dépendances (collaborateurs). La deuxième raison est que cela rend vos classes plus petites et plus tordues, plus faciles à comprendre et même à réutiliser dans d'autres contextes.

c_maker
la source