web-dev-qa-db-fra.com

Pourquoi un type serait-il couplé à son constructeur?

J'ai récemment supprimé A Java réponse de la mine sur revue de code , qui a commencé comme ceci:

private Person(PersonBuilder builder) {

Arrêter. Drapeau rouge. Un peruré-personne construirait une personne; Il connaît une personne. La classe de la personne ne devrait pas savoir quoi que ce soit sur un personnage - c'est juste un type immuable. Vous avez créé un couplage circulaire ici, où cela dépend de B qui dépend de A.

La personne devrait simplement apporter ses paramètres; Un client qui est prêt à créer une personne sans construire, il devrait être capable de le faire.

J'ai été giflé avec un bowvote et j'ai dit cela (citant) Drapeau rouge, pourquoi? La mise en œuvre ici a la même forme que Joshua Bloch a démontré dans son livre "Efficace Java" (point 2).

Donc, il semble que la seule façon de mettre en œuvre un modèle A Motif de constructeur In Java est à rendre le constructeur un type imbriqué ( Ce n'est pas la question de cette question à propos de), puis de faire le produit (la classe de l'objet construit) prenez une dépendance sur le constructeur , comme ceci:

private StreetMap(Builder builder) {
    // Required parameters
    Origin      = builder.Origin;
    destination = builder.destination;

    // Optional parameters
    waterColor         = builder.waterColor;
    landColor          = builder.landColor;
    highTrafficColor   = builder.highTrafficColor;
    mediumTrafficColor = builder.mediumTrafficColor;
    lowTrafficColor    = builder.lowTrafficColor;
}

https://fr.wikipedia.org/wiki/builder_pattern#java_example

La même page Wikipedia du même modèle de constructeur a une implémentation extrêmement différente (et beaucoup plus flexible) pour C #:

//Represents a product created by the builder
public class Car
{
    public Car()
    {
    }

    public int Wheels { get; set; }

    public string Colour { get; set; }
}

Comme vous pouvez le constater, le produit ici ne sait rien sur une classe Builder, et pour tout ce qu'il soutient, il pourrait être instancié par un appel de constructeur direct, une usine abstraite, ... ou un constructeur - aussi loin que je le comprends, le produit d'un modèle de création jamais doit savoir quoi que ce soit sur ce qui le crée.

J'ai été servi le contre-argument (qui est apparemment explicitement défendu dans le livre de Bloch) qu'un motif de constructeur pourrait être utilisé pour retravailler un type qui aurait un constructeur gonflé avec des dizaines d'arguments facultatifs. Donc, au lieu de coller à ce que je pensé Je savais que j'ai eu étudié un peu sur ce site et j'ai constaté que comme je soupçonnais, Cet argument ne tient pas de l'ea .

Alors, quelle est l'accord? Pourquoi proposer-vous solutions excessives à un problème qui ne devrait même pas exister en premier lieu? Si nous prenons Joshua Bloch de son piédestal pendant une minute, pouvons-nous proposer une seule bonne raison valide pour coupler deux types de béton et l'appeler une meilleure pratique?

C'est tout ce qui se tient de programmation de Cargo-culte pour moi.

21
Mathieu Guindon

Je suis en désaccord avec votre affirmation selon laquelle c'est un drapeau rouge. Je suis également en désaccord avec votre représentation du contrepoint, qu'il existe une bonne façon de représenter un modèle de constructeur.

Pour moi, il y a une question: Le constructeur est-il une partie nécessaire de l'API du type ? Ici, Le PersonBuilder est-il une partie nécessaire de la personne API ?

Il est tout à fait raisonnable qu'une classe de personne vérifiée, immuable et/ou étroitement encapsulée nécessairement soit créée via un constructeur qu'il fournit (que ce constructeur soit imbriqué ou adjacent). Ce faisant, la personne peut conserver tous ses champs privés ou forfaits-privés et finaux, et laissez également la classe ouverte pour modifier s'il s'agit d'un travail en cours. (Il peut finissez par Fermé pour modification et ouverture de l'extension, mais ce n'est pas important pour le moment et est particulièrement controversé pour des objets de données immuables.)

Si c'est le cas où une personne est nécessairement créée via une personne fournie dans le cadre d'une seule conception de l'API au niveau du paquet, une couplage circulaire est tout à fait bien et un drapeau rouge n'est pas garanti ici. Notamment, vous l'avez dit comme un fait incontournable et non un avis de conception d'API ou un choix, afin que cela ait pu contribuer à une mauvaise réponse. Je ne t'aurais pas révélé, mais je ne blâmais pas quelqu'un d'autre pour le faire et je laisserai le reste de la discussion de "ce qui justifie un bowvote" au Code d'assistance de code et Meta . Les bowvotes se produisent; Ce n'est pas une "gifle".

Bien sûr, si vous voulez laisser de nombreuses manières ouvertes pour construire un objet de personne, le PersonBuilder pourrait devenir un consommateur ou utilitaire de la classe de la personne , sur lequel la personne ne doit pas dépendre directement. Ce choix semble plus flexible - qui ne voudrait pas plus d'options de création d'objet? . Si le constructeur est censé représenter ou remplacer un constructeur avec beaucoup de champs optionnels ou un constructeur ouvert à la modification, le constructeur long de la classe peut être un détail de mise en œuvre mieux à gauche caché. (Gardez également à l'esprit qu'un constructeur officiel et nécessaire ne vous empêche pas de rédiger votre propre utilitaire de construction, que votre utilitaire de construction peut consommer le constructeur en tant qu'API comme dans ma question initiale ci-dessus.)


p.S.: Je note que l'échantillon de révision du code que vous avez énuméré a une personne immuable, mais le contre-exemple de Wikipedia répertorie une voiture mutable avec des getters et des setters. Il peut être plus facile de voir les machines nécessaires omis de la voiture si vous gardez la langue et les invariants conformes à eux.

22
Jeff Bowman

Je comprends à quel point cela peut être ennuyant pour que "faire appel à l'autorité" soit utilisé dans un débat. Les arguments devraient tenir à leur propre OMI et, bien qu'il ne soit pas faux de pointer sur les conseils d'une personne aussi bien respectée, il ne peut vraiment pas être considéré comme un argument complet en soi que nous savons que le soleil se déroule autour de la terre parce que Aristote l'a dit. .

Cela dit, je pense que la prémisse de votre argument est la même. Nous ne devrions jamais coupler deux types de béton et l'appeler une meilleure pratique parce que ... quelqu'un l'a dit?

Pour que l'argument selon lequel le couplage est problématique soit valide, il faut des problèmes spécifiques qu'il crée. Si cette approche n'est pas soumise à ces problèmes, vous ne pouvez pas affirmer logiquement que cette forme de couplage est problématique. Donc, si vous voulez faire votre point ici, je pense que vous devez montrer comment cette approche est soumise à ces problèmes et/ou comment le découplage du constructeur améliorera une conception.

Porthand, j'ai du mal à voir comment l'approche couplée créera des problèmes. Les classes sont complètement couplées, à coup sûr, mais le constructeur n'existe que pour créer des instances de la classe. Une autre façon de regarder cela serait comme une extension du constructeur.

7
JimmyJames

Pour répondre à la raison pour laquelle un type serait couplé à un constructeur, il est nécessaire de comprendre pourquoi tout le monde utiliserait un constructeur en premier lieu. Spécifiquement: Bloch recommande d'utiliser un constructeur lorsque vous avez un grand nombre de paramètres de constructeur. Ce n'est pas la seule raison d'utiliser un constructeur, mais je le spécule est le plus courant. En bref, le constructeur est un remplaçant pour ces paramètres du constructeur et que le constructeur est souvent déclaré dans la même classe qu'il construit. Ainsi, la classe enfermante a déjà une connaissance du constructeur et la transmettant comme argument de constructeur ne change pas cela. C'est pourquoi un type serait couplé à son constructeur.

Pourquoi avoir un grand nombre de paramètres impliquent-il que vous devriez utiliser un constructeur? Eh bien, avoir un grand nombre de paramètres n'est pas rare dans le monde réel, et ne violent pas le principe de responsabilité unique. Il craint également lorsque vous avez dix paramètres de chaîne et que vous essayez de vous rappeler lesquels sont lesquels sont lesquels et qu'il s'agisse de la cinquième ou de la sixième chaîne qui devrait être nulle. Un constructeur facilite la gestion des paramètres et il peut également faire une autre chose cool que vous devriez lire le livre pour en savoir plus.

Quand utilisez-vous un constructeur qui n'est pas couplé avec son type? Généralement, lorsque les composants d'un objet ne sont pas immédiatement disponibles et que les objets dont ces pièces n'ont pas besoin de savoir sur le type que vous essayez de construire ou que vous ajoutez un constructeur pour une classe que vous n'avez pas de contrôle sur .

4
Old Fat Ned

le produit d'un motif créé a besoin de savoir quoi que ce soit sur ce qui le crée.

la classe Person _ ne sait pas ce qui le crée. Il n'a qu'un constructeur avec un paramètre, alors quoi? Le nom du type du paramètre se termine par ... Builder qui ne force rien. C'est juste un nom après tout.

Le constructeur est glorifié1 Objet de configuration. et un objet de configuration vient de regrouper des propriétés ensemble qui appartiennent à l'autre. S'ils n'appartiennent qu'à l'autre dans le but d'être transmis au constructeur d'une classe, alors soyez-le! Origin et destination de l'exemple StreetMap est de type Point. Serait-il possible de transmettre les coordonnées individuelles de chaque point sur la carte? Bien sûr, mais les propriétés d'un Point appartiennent ensemble, plus vous pouvez avoir toutes sortes de méthodes sur eux qui aident à leur construction:

1La différence est que le constructeur n'est pas simplement un objet de données uni, mais permet aux appels de setter chaînées. le Builder est principalement préoccupé par la construction de lui-même.

Maintenant, ajoutons un peu de modèle de commande au mélange, car si vous regardez de près, le constructeur est en réalité une commande:

  1. Il encapsule une action: appeler une méthode d'une autre classe
  2. Il contient les informations nécessaires pour effectuer cette action: les valeurs des paramètres de la méthode

La partie spéciale pour le constructeur est que:

  1. Cette méthode s'appelle est en fait le constructeur d'une classe. Mieux l'appeler un modèle créé maintenant.
  2. La valeur du paramètre est le constructeur lui-même

Ce qui est transmis au constructeur Person peut le construire, mais cela n'a pas nécessairement besoin. Cela pourrait être un ancien objet de configuration ou un constructeur.

1
null

Non, ils sont complètement faux et vous avez absolument raison. Ce modèle de constructeur est juste idiot. Ce n'est pas une affaire de la personne où vient les arguments du constructeur. Le constructeur est juste une commodité pour les utilisateurs et rien de plus.

0
DeadMG