Je rencontre souvent ce problème, en particulier en Java, même si je pense que c'est un problème général OOP. C'est-à-dire: 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 une sorte 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 lançant une erreur si le displayCompleteNameOnInvoice
est appelé avant que le nom n'ait été attribué. Cela semble une bonne idée, non?
Je peux ajouter un code déclencheur d'exceptions à la méthode getCompleteName
. Mais de cette façon, je viole un contrat "implicite" avec l'utilisateur de classe; en général, les getters ne sont pas censés lever d'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.
O Je peux lever l'exception de l'intérieur du displayCompleteNameOnInvoice
. Mais pour ce faire, je dois tester directement les champs name
ou surname
et, ce faisant, je violerais l'abstraction représentée par getCompleteName
. C'est cette responsabilité de 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 dans certains cas, il suffit de surname
.
So 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 cherche toujours le meilleur équilibre entre simplicité et justesse. Existe-t-il une référence de conception pour ce problème?
Ne permettez pas à votre classe d'être construite sans attribuer un nom.
La réponse fournie par DeadMG la résout à peu près, mais permettez-moi de la formuler un peu différemment: évitez d'avoir des objets avec un état invalide. 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 Builder Pattern . Ou toute autre chose qui est une réification distincte d'un objet en construction par opposition à la "vraie chose", où ce dernier est garanti d'avoir un état valide et est celui qui expose réellement les opérations définies sur cet état (par exemple getCompleteName
).
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 surviennent avec un état incorrectement initialisé dans les objets. Ce problème s'aggrave si vous traitez avec la concurrence et que quelque chose peut accéder à l'objet avant que vous ayez complètement terminé de le configurer.
Donc, la question pour cette partie est "pourquoi autorisez-vous le nom ou le prénom à être nul?" Si ce n'est pas quelque chose qui est valide dans la classe pour que cela fonctionne correctement, ne le permettez pas. Ayez un constructeur ou un constructeur qui valide correctement la création de l'objet et si ce n'est pas le cas, soulevez le problème à ce stade. Vous pouvez également utiliser l'une des @NotNull
Annotations qui existent pour aider à communiquer que "cela ne peut pas être nul" et à le faire respecter dans le codage et l'analyse.
Avec cette garantie en place, il devient beaucoup plus facile de raisonner sur le code, ce qu'il fait, et de ne pas avoir à lever d'exceptions dans des endroits étranges ou à mettre des contrôles excessifs autour des fonctions getter.
Il y a pas mal de choses sur ce sujet. Vous avez Combien de logique dans les getters et Qu'est-ce qui devrait être autorisé à l'intérieur des getters et des setters? d'ici et les getters et les setters exécutant une logique supplémentaire over sur Stack Overflow. C'est un problème qui revient sans cesse dans la conception des classes.
Le cœur de ceci vient de:
en général, les getters ne sont pas censés lever des exceptions si leurs valeurs ne sont pas définies
Et tu as raison. Ils ne devraient pas. Ils devraient avoir l'air "stupides" pour le reste du monde et faire ce qui est attendu. Mettre trop de logique là-dedans conduit à des problèmes où la loi du moindre étonnement est violée. Et avouons-le, vous ne voulez vraiment pas emballer les getters avec un try catch
Parce que nous savons combien nous aimons faire cela en général.
Il existe également des situations où vous devez utiliser une méthode getFoo
telle que le framework JavaBeans et lorsque vous avez quelque chose de EL appelant s'attendant à obtenir un bean (de sorte que <% bar.foo %>
appelle réellement getFoo()
dans la classe - en mettant de côté le 'si le bean est en train de faire la composition ou cela devrait-il être laissé à 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 qu'un getter donné soit spécifié dans une interface ou ait fait partie de l'API publique précédemment exposée pour la classe qui est refactorisée (une version précédente avait juste 'completeName' qui a été retourné et un refactoring s'est cassé en deux domaines).
Faites ce qui est le plus facile à raisonner. Vous passerez plus de temps à maintenir ce code que vous ne le passerez à le concevoir (bien que moins vous passerez de temps à le concevoir, plus vous passerez de temps à le maintenir). Le choix idéal est de le concevoir de manière à ce qu'il ne prenne pas autant de temps à le maintenir, mais ne restez pas là à y penser pendant des jours non plus - à moins que ce ne soit vraiment un choix de conception qui aura des jours d'implications plus tard.
Essayer de s'en tenir à la pureté sémantique du getter étant private T foo; T getFoo() { return foo; }
vous causera des ennuis à un moment donné. Parfois, le code ne correspond tout simplement pas à ce modèle et les contorsions que l'on traverse pour essayer de le garder ainsi n'ont tout simplement pas de sens ... et, en fin de compte, il est plus difficile à concevoir.
Acceptez parfois que les idéaux de la conception ne peuvent pas être réalisés comme vous le souhaitez dans le code. Les directives sont des directives - pas des entraves.
Je ne suis pas un Java guy, mais cela semble adhérer aux deux contraintes que vous avez présentées.
getCompleteName
ne lève pas d'exception si les noms ne sont pas initialisés, et displayCompleteNameOnInvoice
le fait.
public String getCompleteName()
{
if (name == null || surname == null)
{
return null;
}
return name + " " + surname;
}
public void displayCompleteNameOnInvoice()
{
String completeName = getCompleteName();
if (completeName == null)
{
//throw an exception.
}
//do something with it....
}
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:
Exemple simple: FileStream.Length
en C # /. NET , qui lance NotSupportedException
si le fichier n'est pas recherché.
Vous avez tout le nom du chèque à l'envers.
getCompleteName()
n'a pas besoin de savoir quelles fonctions l'utiliseront. Ainsi, ne pas avoir de name
lors de l'appel de displayCompleteNameOnInvoice()
n'est pas la responsabilité de getCompleteName()
.
Mais, name
est un prérequis clair pour displayCompleteNameOnInvoice()
. 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).