Les classes d'utilité sont mauvaises? [fermé]


96

J'ai vu ce fil

Si une classe "Utilitaires" est mauvaise, où dois-je mettre mon code générique?

et pensé pourquoi les classes d'utilité sont-elles mauvaises?

Disons que j'ai un modèle de domaine qui comprend des dizaines de classes. Je dois être capable de xml-ify instances. Est-ce que je crée une méthode toXml sur le parent? Dois-je créer une classe d'assistance MyDomainXmlUtility.toXml? C'est un cas où le besoin de l'entreprise couvre tout le modèle de domaine - appartient-il vraiment en tant que méthode d'instance? Qu'en est-il s'il existe un tas de méthodes auxiliaires sur la fonctionnalité xml de l'application?


27
La dévalorisation du terme «mal» est mal!
Matthew Lock

1
@matthew j'ai conservé les termes du message sur lequel ma question est basée ...;)
hvgotcodes

Les classes d'utilité sont une mauvaise idée pour les mêmes raisons que les singletons.
CurtainDog

1
Le débat sur l'opportunité d'avoir une méthode toXML est centré sur les modèles de domaines riches versus anémiques. codeflow.blogspot.com/2007/05/anemic-vs-rich-domain-models.html
James P.

@james, le toXML n'est qu'un exemple ... qu'en est-il de certaines fonctionnalités de regex qui sont utilisées partout? Par exemple, vous devez faire des choses avec les chaînes de votre modèle de domaine, mais vous ne pouvez pas utiliser de sous-classification en raison d'un autre problème majeur qui utilise votre seule superclasse (en java)
hvgotcodes

Réponses:


128

Les classes utilitaires ne sont pas vraiment mauvaises, mais elles peuvent enfreindre les principes qui composent une bonne conception orientée objet. Dans une bonne conception orientée objet, la plupart des classes doivent représenter une seule chose et tous ses attributs et opérations. Si vous opérez sur une chose, cette méthode devrait probablement en faire partie.

Cependant, il y a des moments où vous pouvez utiliser des classes d'utilitaires pour regrouper un certain nombre de méthodes - un exemple étant la java.util.Collectionsclasse qui fournit un certain nombre d'utilitaires pouvant être utilisés sur n'importe quelle collection Java. Ceux-ci ne sont pas spécifiques à un type particulier de collection, mais implémentent à la place des algorithmes qui peuvent être utilisés sur n'importe quelle collection.

En réalité, ce que vous devez faire est de réfléchir à votre conception et de déterminer où il est le plus logique de placer les méthodes. Habituellement, il s'agit d'opérations à l'intérieur d'une classe. Cependant, parfois, il s'agit bien d'une classe d'utilité. Cependant, lorsque vous utilisez une classe utilitaire, ne vous contentez pas d'y lancer des méthodes aléatoires, organisez plutôt les méthodes par objectif et fonctionnalité.


correspond bien à cet article sur la vérification de la classe utilitaire / d'assistance par rapport au principe SOLID oo ainsi que de readability.com/articles/rk1vvcqy
melaos

8
Si le langage n'offre aucun mécanisme d'espace de noms autre que les classes, vous n'avez pas d'autre choix que d'abuser d'une classe là où un espace de noms ferait l'affaire. En C ++, vous pouvez placer une fonction autonome, sans rapport avec d'autres fonctions, dans un espace de noms. En Java, vous devez le placer dans une classe en tant que membre statique (classe).
Unslander Monica

1
Le regroupement en tant que méthodes peut être canonique du point de vue de la POO. Cependant, la POO est rarement la solution (parmi les exceptions, il y a l'architecture de haut niveau et les boîtes à outils de widgets comme l'une des instances où la sémantique incroyablement brisée de la réutilisation du code par héritage s'adapte réellement) et généralement les méthodes augmentent le couplage et les dépendances. Exemple trivial: si vous fournissez des méthodes d'imprimante / scanner à votre classe en tant que méthodes, vous couplez la classe à des bibliothèques d'imprimante / scanner. De plus, vous fixez le nombre d'implémentations possibles à une seule. Sauf si vous ajoutez une interface et augmentez davantage les dépendances.
Jo So

D'un autre côté, je suis d'accord avec votre sentiment "grouper par objectif et fonctionnalité". Revenons à nouveau sur l'exemple d'imprimante / scanner, et commençons par un ensemble de classes concertées, conçues dans un but commun. Vous voudrez peut-être écrire du code de débogage et concevoir une représentation textuelle pour vos classes. Vous pouvez implémenter vos imprimantes de débogage dans un seul fichier qui dépend de toutes ces classes et d'une bibliothèque d'imprimantes. Le code sans débogage ne sera pas surchargé par les dépendances de cette implémentation.
Jo So

1
Les classes Util et Helper sont un artefact malheureux de ces contraintes, et ceux qui ne comprennent pas la POO ou ne comprennent pas le domaine du problème ont continué à écrire du code procédural.
bilelovitch

57

Je pense que le consensus général est que les classes d'utilité ne sont pas mauvaises en soi . Il vous suffit de les utiliser judicieusement:

  • Concevez les méthodes d'utilité statique pour qu'elles soient générales et réutilisables. Assurez-vous qu'ils sont apatrides; c'est-à-dire pas de variables statiques.

  • Si vous avez beaucoup de méthodes utilitaires, partitionnez-les en classes de manière à ce que les développeurs puissent les trouver facilement.

  • N'utilisez pas de classes utilitaires où les méthodes statiques ou d'instance dans une classe de domaine seraient une meilleure solution. Par exemple, considérez si les méthodes d'une classe de base abstraite ou d'une classe d'assistance instanciable seraient une meilleure solution.

  • À partir de Java 8, les «méthodes par défaut» dans une interface peuvent être une meilleure option que les classes utilitaires. (Voir Interface avec les méthodes par défaut vs classe abstraite dans Java 8 par exemple.)


L'autre façon de regarder cette Question est d'observer que dans la Question citée, "Si les classes d'utilité sont" mauvaises "" est un argument d'homme de paille. C'est comme si je demandais:

"Si les porcs peuvent voler, dois-je porter un parapluie?".

Dans la question ci-dessus, je ne dis pas en fait que les porcs peuvent voler ... ou que je suis d'accord avec la proposition selon laquelle ils pourraient voler.

Les déclarations typiques «xyz is evil» sont des artifices rhétoriques destinés à vous faire réfléchir en posant un point de vue extrême. Ils sont rarement (voire jamais) conçus comme des déclarations de fait littéral.


16

Les classes d'utilité sont problématiques car elles ne regroupent pas les responsabilités avec les données qui les prennent en charge.

Ils sont cependant extrêmement utiles et je les construis tout le temps en tant que structures permanentes ou en tant que tremplins lors d'un refactor plus approfondi.

Du point de vue du code propre , les classes d'utilitaires enfreignent la responsabilité unique et le principe ouvert-fermé. Ils ont de nombreuses raisons de changer et ne sont pas extensibles par conception. Ils ne devraient vraiment exister que lors du refactoring en tant que cruft intermédiaire.


2
J'appellerais cela un problème pratique car, par exemple, en Java, vous ne pouvez pas créer de fonctions qui ne sont pas des méthodes dans une classe. Dans un tel langage, la "classe sans variables et avec uniquement des méthodes statiques" est un idiome qui représente un espace de noms de fonctions utilitaires ... Face à une telle classe en Java, il est à mon humble avis invalide et inutile de parler d'une classe , même si le classmot-clé est utilisé. Il caracole comme un espace de noms, il marche comme un espace de noms - c'est un ...
Unslander Monica

2
Je suis désolé d'être franc, mais "les méthodes statiques sans état [...] bizarres dans un problème [...] philosophique du système POO" est extrêmement erroné. C'est génial si vous pouvez éviter l'état, car cela facilite l'écriture du code correct. C'est CASSÉ quand je dois écrire x.equals(y)parce que 1) le fait que cela ne devrait vraiment modifier aucun état n'est pas véhiculé (et je ne peux pas m'y fier sans connaître l'implémentation) 2) Je n'ai jamais eu l'intention de mettre xun " position favorisée "ou" agi sur "par rapport à y3) Je ne veux pas considérer les" identités "de xou ymais je m'intéresse simplement à leurs valeurs.
Jo So

4
En réalité, la plupart des fonctionnalités du monde réel sont mieux exprimées sous forme de fonctions mathématiques statiques, sans état. Math.PI est-il un objet qui doit être muté? Dois-je vraiment instancier un AbstractSineCalculator qui implémente IAbstractRealOperation juste pour obtenir le sinus d'un nombre?
Jo So

1
@JoSo Je suis tout à fait d'accord sur la valeur de l'apatridie et du calcul direct. Naive OO est philosophiquement opposé à cela et je pense que cela le rend profondément imparfait. Il encourage l'abstraction des choses qui n'en ont pas besoin (comme vous l'avez démontré) et ne parvient pas à fournir des abstractions significatives pour le calcul réel. Cependant, une approche équilibrée de l'utilisation d'un langage OO tend vers l'immuabilité et l'apatridie car ils favorisent la modularité et la maintenabilité.
Alain O'Dea

2
@ AlainO'Dea: Je me rends compte que j'ai mal interprété votre commentaire comme étant contre la fonctionnalité apatride. Je m'excuse pour mon ton - je suis actuellement impliqué dans mon premier projet Java (après avoir évité la POO pendant la majeure partie de mes 10 ans de programmation) et je suis enterré sous des couches de choses abstraites avec un état et des significations peu clairs. Et j'ai juste besoin d'un peu d'air frais :-).
Jo So

8

Je suppose que ça commence à devenir mauvais quand

1) Cela devient trop gros (regroupez-les simplement en catégories significatives dans ce cas).
2) Des méthodes qui ne devraient pas être des méthodes statiques sont présentes

Mais tant que ces conditions ne sont pas remplies, je pense qu'elles sont très utiles.


"Des méthodes qui ne devraient pas être des méthodes statiques sont présentes." Comment est-ce possible?
Koray Tugay

@KorayTugay Considérez le non statique Widget.compareTo(Widget widget)par rapport au statique WidgetUtils.compare(Widget widgetOne, Widget widgetTwo). La comparaison est un exemple de quelque chose qui ne devrait pas être fait de manière statique.
ndm13

5

Règle de base

Vous pouvez examiner ce problème sous deux angles:

  • Les *Utilméthodes générales suggèrent souvent une mauvaise conception de code ou une convention de dénomination paresseuse.
  • Il s'agit d'une solution de conception légitime pour les fonctionnalités sans état interdomaines réutilisables. Veuillez noter que pour presque tous les problèmes courants, il existe des solutions.

Exemple 1. Utilisation correcte des utilclasses / modules. Exemple de bibliothèque externe

Supposons que vous rédigiez une application qui gère les prêts et les cartes de crédit. Les données de ces modules sont exposées via des services Web au jsonformat. En théorie, vous pouvez convertir manuellement des objets en chaînes qui y figureront json, mais cela réinventerait la roue. La bonne solution serait d'inclure dans les deux modules la bibliothèque externe utilisée pour convertir les objets java au format souhaité. (dans l'image d'exemple, j'ai montré gson )

entrez la description de l'image ici


Exemple 2. Utilisation correcte des utilclasses / modules. Écrire le vôtre utilsans excuses aux autres membres de l'équipe

En tant que cas d'utilisation, supposons que nous devons effectuer certains calculs dans deux modules d'application, mais tous deux doivent savoir quand il y a des jours fériés en Pologne. En théorie, vous pouvez effectuer ces calculs à l'intérieur des modules, mais il serait préférable d'extraire cette fonctionnalité pour séparer le module.

Voici un petit détail, mais important. La classe / module que vous avez écrit n'est pas appelée HolidayUtil, mais PolishHolidayCalculator. Fonctionnellement, c'est une utilclasse, mais nous avons réussi à éviter les mots génériques.

entrez la description de l'image ici


1
Un fan yEd que je vois;) Application incroyable.
Sridhar Sarnobat

3

Les classes utilitaires sont mauvaises car elles signifient que vous étiez trop paresseux pour trouver un meilleur nom pour la classe :)

Cela étant dit, je suis paresseux. Parfois, vous avez juste besoin de faire le travail et votre esprit est vide ... c'est alors que les classes "Utilitaires" commencent à s'infiltrer.


3

En repensant à cette question maintenant, je dirais que les méthodes d'extension C # détruisent complètement le besoin de classes utilitaires. Mais toutes les langues n'ont pas une telle construction dans le génie.

Vous avez également JavaScript, où vous pouvez simplement ajouter une nouvelle fonction directement à l'objet existant.

Mais je ne suis pas sûr qu'il existe vraiment une manière élégante de résoudre ce problème dans un langage plus ancien comme C ++ ...

Un bon code OO est un peu difficile à écrire, et il est difficile à trouver car l'écriture de Good OO nécessite beaucoup plus de temps / de connaissances que d'écrire un code fonctionnel décent.

Et quand vous avez un budget limité, votre patron n'est pas toujours content de voir que vous avez passé toute la journée à écrire un tas de cours ...


3

Je ne suis pas entièrement d'accord pour dire que les classes d'utilité sont mauvaises.

Bien qu'une classe utilitaire puisse violer les principes OO de certaines manières, ils ne sont pas toujours mauvais.

Par exemple, imaginez que vous vouliez une fonction qui nettoiera une chaîne de toutes les sous-chaînes correspondant à la valeur x.

stl c ++ (à partir de maintenant) ne prend pas directement en charge cela.

Vous pouvez créer une extension polymorphe de std::string.

Mais le problème est, voulez-vous vraiment que CHAQUE chaîne que vous utilisez dans votre projet soit votre classe de chaînes étendue?

Il y a des moments où OO n'a pas vraiment de sens, et c'est l'un d'entre eux. Nous voulons que notre programme soit compatible avec d'autres programmes, nous allons donc nous en tenir à std::stringet créer une classe StringUtil_(ou quelque chose).

Je dirais que c'est mieux si vous vous en tenez à un utilitaire par classe. Je dirais que c'est idiot d'avoir un utilitaire pour toutes les classes ou plusieurs utilitaires pour une classe.


2

Il est très facile de marquer quelque chose comme un utilitaire simplement parce que le concepteur ne pouvait pas penser à un endroit approprié pour mettre le code. Il existe souvent peu de véritables «utilitaires».

En règle générale, je garde généralement le code dans le paquet où il est utilisé pour la première fois, puis je ne le refactore à un endroit plus générique que si je trouve que plus tard, il est vraiment nécessaire ailleurs. La seule exception est si j'ai déjà un package qui exécute des fonctionnalités similaires / connexes et que le code y correspond le mieux.


2

Les classes utilitaires contenant des méthodes statiques sans état peuvent être utiles. Ces tests sont souvent très faciles à tester.



1

La plupart des classes Util sont mauvaises parce que:

  1. Ils élargissent le champ des méthodes. Ils rendent le code public qui serait autrement privé. Si la méthode util est requise par plusieurs appelants dans des classes séparées et qu'elle est stable (c'est-à-dire qu'elle n'a pas besoin d'être mise à jour), il est préférable à mon avis de copier et coller des méthodes d'assistance privées dans la classe appelante. Une fois que vous l'exposez en tant qu'API, vous rendez plus difficile la compréhension du point d'entrée public d'un composant jar (vous conservez une arborescence appelée hiérarchie avec un parent par méthode. Il est plus facile de séparer mentalement en composants, ce qui est plus difficile lorsque vous avez des méthodes appelées à partir de plusieurs méthodes parentes).
  2. Ils entraînent un code mort. Les méthodes util au fil du temps deviennent inutilisées à mesure que votre application évolue et vous vous retrouvez avec du code inutilisé polluant votre base de code. Si elle était restée privée, votre compilateur vous dirait que la méthode n'est pas utilisée et que vous pouvez simplement la supprimer (le meilleur code est pas du tout de code). Une fois que vous avez rendu une telle méthode non privée, votre ordinateur sera impuissant à vous aider à supprimer le code inutilisé. Il peut être appelé à partir d'un fichier jar différent pour tout ce que l'ordinateur sait.

Il existe quelques analogies entre les bibliothèques statiques et dynamiques.


0

Lorsque je ne peux pas ajouter une méthode à une classe (par exemple, Accountest verrouillée contre les modifications par les développeurs Jr.), j'ajoute simplement quelques méthodes statiques à ma classe Utilities comme ceci:

public static int method01_Account(Object o, String... args) {
    Account acc = (Account)o;
    ...
    return acc.getInt();
}  

1
On dirait que vous êtes le Jr pour moi ..: P La manière non-diabolique de faire ceci est de demander poliment à votre "Jr Developer" de déverrouiller le Accountfichier. Ou mieux, optez pour des systèmes de contrôle de source non verrouillables.
Sudeep

1
Je suppose qu'il voulait dire que le Accountfichier était verrouillé de sorte que les développeurs Jr. ne peuvent pas y apporter de modifications. En tant que développeur junior, pour contourner cela, il a fait comme ci-dessus. Cela dit, mieux vaut simplement obtenir un accès en écriture au fichier et le faire correctement.
Doc

Ajouter +1 à cela parce que les votes négatifs semblent durs lorsque personne n'a le contexte complet de la raison pour laquelle vous avez fourni cette réponse
joelc

0

Les classes d'utilité ne sont pas toujours mauvaises. Mais ils ne doivent contenir que les méthodes communes à un large éventail de fonctionnalités. S'il y a des méthodes qui ne sont utilisables que parmi un nombre limité de classes, envisagez de créer une classe abstraite en tant que parent commun et mettez-y les méthodes.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.