Dans notre base de code Java, je continue de voir le modèle suivant:
/**
This is a stateless utility class
that groups useful foo-related operations, often with side effects.
*/
public class FooUtil {
public int foo(...) {...}
public void bar(...) {...}
}
/**
This class does applied foo-related things.
*/
class FooSomething {
int DoBusinessWithFoo(FooUtil fooUtil, ...) {
if (fooUtil.foo(...)) fooUtil.bar(...);
}
}
Ce qui me dérange, c'est que je dois passer une instance de FooUtil
partout, car les tests.
- Je ne peux pas rendre
FooUtil
les méthodes de statique, car je ne pourrai pas les simuler pour tester les deuxFooUtil
et ses classes clientes. - Je ne peux pas créer une instance de
FooUtil
sur le lieu de consommation avec unnew
, encore une fois parce que je ne pourrai pas m'en moquer pour les tests.
Je suppose que mon meilleur pari est d'utiliser l'injection (et je le fais), mais il ajoute son propre ensemble de tracas. En outre, la transmission de plusieurs instances util gonfle la taille des listes de paramètres de méthode.
Y a-t-il un moyen de mieux gérer cela que je ne vois pas?
Mise à jour: puisque les classes utilitaires sont sans état, je pourrais probablement ajouter un INSTANCE
membre singleton statique ou une getInstance()
méthode statique , tout en conservant la possibilité de mettre à jour le champ statique sous-jacent de la classe pour le test. Ne semble pas super propre non plus.
FooUtil
méthodes devraient être introduitesFooSomething
, peut-être qu'il y a des propriétésFooSomething
qui devraient être modifiées / étendues afin que lesFooUtil
méthodes ne soient plus nécessaires. Veuillez nous donner un exemple plus concret de ce quiFooSomething
peut nous aider à fournir une bonne réponse à ces questions.Réponses:
Tout d'abord, permettez-moi de dire qu'il existe différentes approches de programmation pour différents problèmes. Pour mon travail, je préfère une conception OO solide pour l'orginazation et la maintenance et ma réponse reflète cela. Une approche fonctionnelle aurait une réponse très différente.
J'ai tendance à constater que la plupart des classes d'utilitaires sont créées parce que l'objet sur lequel les méthodes doivent se trouver n'existe pas. Cela vient presque toujours de l'un des deux cas, soit quelqu'un passe des collections, soit quelqu'un passe des haricots (ceux-ci sont souvent appelés POJO mais je pense qu'un tas de setters et de getters est mieux décrit comme un bean).
Lorsque vous avez une collection que vous faites circuler, il doit y avoir du code qui veut faire partie de cette collection, et cela finit par être dispersé dans tout votre système et finalement collecté dans des classes utilitaires.
Ma suggestion est d'envelopper vos collections (ou beans) avec toute autre donnée étroitement associée dans de nouvelles classes et de mettre le code "utilitaire" dans des objets réels.
Vous pouvez étendre la collection et y ajouter les méthodes mais vous perdez ainsi le contrôle de l'état de votre objet - je vous suggère de l'encapsuler complètement et d'exposer uniquement les méthodes de logique métier qui sont nécessaires (pensez "Ne demandez pas à vos objets leurs données, donnez des commandes à vos objets et laissez-les agir sur leurs données privées ", un locataire OO important et qui, quand vous y réfléchissez, nécessite l'approche que je suggère ici).
Cet «encapsulage» est utile pour tous les objets de bibliothèque à usage général qui contiennent des données, mais vous ne pouvez pas ajouter de code à - Mettre du code avec les données qu'il manipule est un autre concept majeur dans la conception OO.
Il existe de nombreux styles de codage où ce concept d'encapsulation serait une mauvaise idée - qui veut écrire une classe entière lors de l'implémentation d'un script ou d'un test unique? Mais je pense que l'encapsulation de tous les types / collections de base que vous ne pouvez pas ajouter de code à vous-même est obligatoire pour OO.
la source
Vous pouvez utiliser un modèle de fournisseur et injecter votre objet
FooSomething
avec unFooUtilProvider
objet (peut être dans un constructeur; une seule fois).FooUtilProvider
aurait seulement une méthode:FooUtils get();
. La classe utiliserait alors l'FooUtils
instance qu'elle fournit.C'est maintenant une étape loin de l'utilisation d'un framework d'injection de dépendances, lorsque vous n'auriez qu'à câbler le co-conteneur DI deux fois: pour le code de production et pour votre suite de tests. Vous liez simplement l'
FooUtils
interface à l'unRealFooUtils
ou à l'autreMockedFooUtils
, et le reste se fait automatiquement. Chaque objet qui en dépendFooUtilsProvider
obtient la version appropriée deFooUtils
.la source