Je rencontre souvent ce problème, en particulier en Java, même s’il s’agit d’un problème général de programmation orientée objet. Autrement dit, le fait de lever une exception révèle un problème de conception.
Supposons que j'ai une classe qui a un String name
champ et un String surname
champ.
Ensuite, il utilise ces champs pour composer le nom complet d'une personne afin de l'afficher sur un type de document, par exemple une facture.
public void String name;
public void String surname;
public String getCompleteName() {return name + " " + surname;}
public void displayCompleteNameOnInvoice() {
String completeName = getCompleteName();
//do something with it....
}
Maintenant, je veux renforcer le comportement de ma classe en générant une erreur si le displayCompleteNameOnInvoice
nom est appelé avant que le nom ait été attribué. Cela semble une bonne idée, n'est-ce pas?
Je peux ajouter un code d'exception à la getCompleteName
méthode. Mais de cette façon, je viole un contrat «implicite» avec l'utilisateur de la classe. En général, les getters ne sont pas supposés lancer des exceptions si leurs valeurs ne sont pas définies. Ok, ce n’est pas un getter standard car il ne renvoie pas un seul champ, mais du point de vue de l’utilisateur, la distinction peut être trop subtile pour y penser.
Ou je peux jeter l'exception de l'intérieur displayCompleteNameOnInvoice
. Mais pour le faire, je dois tester directement name
ou surname
champs et ce faisant, je violeriez l'abstraction représentée par getCompleteName
. C'est la responsabilité de cette méthode de vérifier et de créer le nom complet. Il pourrait même décider, en se basant sur d’autres données, que cela suffit dans certains cas surname
.
Ainsi, la seule possibilité semble changer la sémantique de la méthode getCompleteName
en composeCompleteName
, ce qui suggère un comportement plus «actif» et, avec lui, la capacité de lancer une exception.
Est-ce la meilleure solution de conception? Je recherche toujours le meilleur équilibre entre simplicité et exactitude. Existe-t-il une référence de conception pour ce problème?
la source
displayCompleteNameOnInvoice
peut simplement lever une exception sigetCompleteName
retournenull
, n'est-ce pas?Réponses:
Ne laissez pas votre classe être construite sans assigner un nom.
la source
La réponse fournie par DeadMG la décrit assez bien, mais permettez-moi de la formuler un peu différemment: éviter d'avoir des objets avec un état non valide. Un objet doit être "entier" dans le contexte de la tâche qu'il remplit. Ou cela ne devrait pas exister.
Donc, si vous voulez de la fluidité, utilisez quelque chose comme le motif Builder . Ou tout ce qui est une réification distincte d'un objet en construction par opposition à la "chose réelle", où le dernier est garanti comme ayant un état valide et qui expose en réalité les opérations définies sur cet état (par exemple
getCompleteName
).la source
So if you want fluidity, use something like the builder pattern
- fluidité ou immuabilité (sauf si c'est ce que vous voulez dire). Si l'on veut créer des objets immuables, mais qu'il faille différer la définition de certaines valeurs, il convient de les définir un par un sur un objet générateur et de ne créer un objet immuable que lorsque toutes les valeurs requises ont été rassemblées. État ...Ne pas avoir d'objet avec un état invalide.
Le but d'un constructeur ou d'un constructeur est de définir l'état de l'objet sur quelque chose de cohérent et utilisable. Sans cette garantie, des problèmes surgissent avec un état incorrectement initialisé dans les objets. Ce problème devient complexe si vous avez affaire à une concurrence et que quelque chose peut accéder à l'objet avant que vous ayez complètement fini de le configurer.
Donc, la question pour cette partie est "pourquoi autorisez-vous le nom ou le nom de famille à être nuls?" Si cela n’est pas valable dans la classe pour que cela fonctionne correctement, ne le permettent pas. Ayez un constructeur ou un générateur qui valide correctement la création de l'objet et si ce n'est pas correct, soulevez le problème à ce stade. Vous pouvez également souhaiter utiliser l’une des
@NotNull
annotations existantes pour indiquer que "cela ne peut pas être nul" et l’appliquer dans le codage et l’analyse.Avec cette garantie en place, il devient beaucoup plus facile de raisonner sur le code, sur ce qu’il fait, et de ne pas avoir à lancer des exceptions dans des endroits étranges ou à effectuer des vérifications excessives sur les fonctions d’obtention.
Getters qui font plus.
Il y a pas mal de choses à ce sujet. Vous avez combien de logique dans les Getters et Qu'est - ce qui devrait être autorisé dans les Getters et les setters? à partir d’ici, les getters et les setters effectuent une logique supplémentaire sur Stack Overflow. C'est un problème qui revient sans cesse dans la conception de la classe.
Le noyau de ceci vient de:
Et tu as raison. Ils ne devraient pas. Ils devraient avoir l'air stupide du reste du monde et faire ce qui est attendu. Mettre trop de logique là-dedans conduit à des problèmes de violation de la loi du moindre étonnement. Et avouons-le, vous ne voulez vraiment pas envelopper les accesseurs avec un
try catch
parce que nous savons à quel point nous aimons le faire en général.Il y a aussi des situations où vous devez utiliser une
getFoo
méthode telle que le framework JavaBeans et où vous avez quelque chose d' appels d' EL qui espère obtenir un bean (de sorte que les<% bar.foo %>
appelsgetFoo()
dans la classe - en mettant de côté le 'si le bean est en train de faire la composition ou devrait être laissés à la vue? 'car on peut facilement trouver des situations où l'une ou l'autre peut clairement être la bonne réponse)Sachez également qu’il est possible de spécifier un getter donné dans une interface ou d’avoir fait partie de l’API publique précédemment exposée pour la classe en cours de refactorisation (une version précédente venait juste de recevoir 'completeName' et un refactoring cassé.) en deux champs).
À la fin de la journée...
Faites la chose la plus facile à raisonner. Vous passerez plus de temps à gérer ce code qu'à concevoir (bien que moins vous passez de temps à concevoir, plus vous passerez de temps à le maintenir). Le choix idéal est de le concevoir de manière à ce que l'entretien ne prenne pas autant de temps, mais ne réfléchissez pas pendant des jours, à moins qu'il ne s'agisse vraiment d'un choix de conception qui aura des conséquences plus tard. .
private T foo; T getFoo() { return foo; }
Si vous essayez de vous en tenir à la pureté sémantique du getter , vous aurez des problèmes à un moment donné. Parfois, le code ne correspond tout simplement pas à ce modèle et les contorsions que l'on subit pour essayer de le conserver ainsi n'ont tout simplement pas de sens ... et finalement rendent la conception plus difficile.Acceptez parfois que les idéaux de la conception ne se réalisent pas comme vous le souhaitez dans le code. Les directives sont des directives - pas des chaînes.
la source
surname
ouname
étant null est un état valide pour l'objet, manipulez-le en conséquence. Mais si ce n'est pas un état valide, les méthodes de la classe émettent des exceptions, il faut éviter de se trouver dans cet état à partir du moment où il a été initialisé. Vous pouvez faire passer des objets incomplets, mais transmettre des objets non valides est problématique. Si un nom de famille nul est exceptionnel, essayez de l’empêcher plus tôt que plus tard.Je ne suis pas un gars de Java, mais cela semble respecter les deux contraintes que vous avez présentées.
getCompleteName
ne lève pas d'exception si les noms ne sont pas initialisés, et ledisplayCompleteNameOnInvoice
fait.la source
getCompleteName()
n'aura pas à se soucier de savoir si la propriété est réellement stockée ou si elle est calculée à la volée.getCompleteName
, ce qui est hideux.getCompleteName()
dans le reste de la classe, ni même dans d'autres parties du programme. Dans certains cas, le retournull
peut être exactement ce qui est requis. Mais bien qu'il existe UNE méthode dont la spécification est "déclencher une exception dans ce cas", c'est EXACTEMENT ce que nous devrions coder.Il semble que personne ne réponde à votre question.
Contrairement à ce que les gens aiment croire, "éviter les objets invalides" n'est pas souvent une solution pratique.
La réponse est:
Oui ça devrait.
Exemple simple:
FileStream.Length
en C # /. NET , qui jetteNotSupportedException
si le fichier n’est pas cherchable.la source
Vous avez tout le nom de vérification vérifier à l'envers.
getCompleteName()
ne doit pas savoir quelles fonctions l’utiliseront. Ainsi, ne pas avoir d'name
appeler lors de l'appeldisplayCompleteNameOnInvoice()
n'est pas lagetCompleteName()
responsabilité de s.Mais,
name
est une condition préalable claire pourdisplayCompleteNameOnInvoice()
. Ainsi, il devrait prendre la responsabilité de vérifier l'état (ou il devrait déléguer la responsabilité de vérifier à une autre méthode, si vous préférez).la source