J'ai une discussion avec mon collègue sur la quantité de travail qu'un constructeur peut faire. J'ai une classe, B qui nécessite en interne un autre objet A. L'objet A est l'un des quelques membres dont la classe B a besoin pour faire son travail. Toutes ses méthodes publiques dépendent de l'objet interne A. Les informations sur l'objet A sont stockées dans la base de données, j'essaie donc de les valider et de les obtenir en les recherchant sur la base de données dans le constructeur. Mon collègue a souligné que le constructeur ne devrait pas faire beaucoup de travail autre que la capture des paramètres du constructeur. Étant donné que toutes les méthodes publiques échoueraient de toute façon si l'objet A n'est pas trouvé en utilisant les entrées du constructeur, j'ai soutenu qu'au lieu de permettre la création d'une instance et l'échec ultérieur, il est en fait préférable de lancer tôt le constructeur.
Que pensent les autres? J'utilise C # si cela fait une différence.
Lecture Y a-t-il jamais une raison de faire tout le travail d'un objet chez un constructeur? je me demande si récupérer l'objet A en allant dans DB fait partie de "toute autre initialisation nécessaire pour rendre l'objet prêt à l'emploi" parce que si l'utilisateur transmettait des valeurs erronées au constructeur, je ne serais pas en mesure d'utiliser l'une de ses méthodes publiques.
Les constructeurs doivent instancier les champs d'un objet et effectuer toute autre initialisation nécessaire pour rendre l'objet prêt à l'emploi. Cela signifie généralement que les constructeurs sont petits, mais il existe des scénarios où cela représenterait une quantité de travail considérable.
la source
Réponses:
Votre question est composée de deux parties complètement distinctes:
Dois-je lever l'exception du constructeur ou dois-je faire échouer la méthode?
Il s'agit clairement d'une application du principe Fail-fast . Faire échouer le constructeur est beaucoup plus facile à déboguer que d'avoir à trouver pourquoi la méthode échoue. Par exemple, vous pouvez obtenir l'instance déjà créée à partir d'une autre partie du code et vous obtenez des erreurs lors de l'appel de méthodes. Est-il évident que l'objet est mal créé? Non.
Quant au problème "envelopper l'appel dans try / catch". Les exceptions sont censées être exceptionnelles . Si vous savez qu'un code lèvera une exception, vous n'encapsulez pas le code dans try / catch, mais vous validez les paramètres de ce code avant d'exécuter le code qui peut lever une exception. Les exceptions sont uniquement destinées à garantir que le système ne passe pas dans un état non valide. Si vous savez que certains paramètres d'entrée peuvent conduire à un état non valide, assurez-vous que ces paramètres ne se produisent jamais. De cette façon, vous n'avez qu'à essayer / attraper dans des endroits, où vous pouvez logiquement gérer l'exception, qui est généralement à la limite du système.
Puis-je accéder à "d'autres parties du système, comme DB" à partir du constructeur.
Je pense que cela va à l'encontre du principe du moindre étonnement . Peu de gens s'attendraient à ce que le constructeur accède à une base de données. Alors non, tu ne devrais pas faire ça.
la source
Pouah.
Un constructeur devrait faire le moins possible - le problème étant que le comportement d'exception dans les constructeurs est maladroit. Très peu de programmeurs savent quel est le bon comportement (y compris les scénarios d'héritage), et forcer vos utilisateurs à essayer / attraper chaque instanciation est ... au mieux douloureux.
Il y a deux parties à cela, dans le constructeur et en utilisant le constructeur. C'est gênant dans le constructeur car vous ne pouvez pas faire grand-chose quand une exception se produit. Vous ne pouvez pas retourner un type différent. Vous ne pouvez pas retourner null. Vous pouvez essentiellement renvoyer l'exception, renvoyer un objet cassé (mauvais constructeur) ou (dans le meilleur des cas) remplacer une partie interne par une valeur par défaut saine. Utiliser le constructeur est gênant car alors vos instanciations (et les instanciations de tous les types dérivés!) Peuvent se lancer. Vous ne pouvez alors pas les utiliser dans les initialiseurs de membres. Vous finissez par utiliser des exceptions pour la logique pour voir "ma création a-t-elle réussi?", Au-delà de la lisibilité (et de la fragilité) provoquée par try / catch partout.
Si votre constructeur fait des choses non triviales ou des choses dont on peut raisonnablement penser qu'elles échoueront, envisagez
Create
plutôt une méthode statique (avec un constructeur non public). Il est beaucoup plus utilisable, plus facile à déboguer et vous offre toujours une instance entièrement construite en cas de succès.la source
Connection
objet .NET peutCreateCommand
vous être utile afin que vous sachiez que la commande est correctement connectée.L'équilibre de complexité constructeur-méthode est en effet un sujet de discussion sur le bon montant. Un constructeur assez souvent vide
Class() {}
est utilisé, avec une méthodeInit(..) {..}
pour les choses plus compliquées. Parmi les arguments à prendre en compte:Class x = new Class(); x.Init(..);
plusieurs fois la même séquence , en partie dans les tests unitaires. Pas si bon, cependant, limpide pour la lecture. Parfois, je les mets simplement dans une ligne de code.la source
init
méthode est que la gestion des échecs est beaucoup plus facile. En particulier, la valeur de retour de lainit
méthode peut indiquer si l'initialisation a réussi et la méthode peut garantir que l'objet est toujours en bon état.