Java - Est-ce une mauvaise idée d'avoir des classes entièrement statiques?

16

Je travaille sur un projet solo plus important et en ce moment, et j'ai plusieurs classes dans lesquelles je ne vois aucune raison de créer une instance de.

Ma classe de dés en ce moment, par exemple, stocke toutes ses données statiquement et toutes ses méthodes sont également statiques. Je n'ai pas besoin de l'initialiser car lorsque je veux lancer les dés et obtenir une nouvelle valeur, je l'utilise Dice.roll().

J'ai plusieurs classes similaires qui n'ont qu'une seule fonction principale comme celle-ci et je suis sur le point de commencer à travailler sur une sorte de classe "contrôleur" qui sera en charge de tous les événements (comme quand un joueur bouge et quel tour en cours) il est) et j'ai trouvé que je pouvais suivre la même idée pour cette classe. Je ne prévois jamais de créer plusieurs objets pour ces classes spécifiques, serait-ce donc une mauvaise idée de les rendre entièrement statiques?

Je me demandais si cela était considéré comme une "mauvaise pratique" en ce qui concerne Java. D'après ce que j'ai vu, la communauté semble être un peu divisée sur ce sujet? Quoi qu'il en soit, j'adorerais en discuter et les liens vers les ressources seraient également formidables!

HexTeke
la source
1
Si votre programme est entièrement procédural. Pourquoi avez-vous choisi Java?
Laiv
12
Essayez d'écrire des tests unitaires pour ces classes et vous comprendrez pourquoi les gens n'aiment pas les méthodes statiques qui accèdent à l'état statique.
Joeri Sebrechts
@Laiv Je suis encore un peu novice en programmation, environ un an de C ++, je prends un cours Java ce semestre et je commence à aimer beaucoup plus Java, les bibliothèques graphiques en particulier.
HexTeke
5
Concernant les classes statiques. Si les méthodes statiques sont pures (ne maintenez aucun état, ont des arguments d'entrée et un type de retour). Il n'y a rien à craindre
Laiv

Réponses:

20

Il n'y a rien de mal avec les classes statiques qui sont vraiment statiques . C'est-à-dire qu'il n'y a pas d'état interne à proprement parler qui ferait évoluer la sortie des méthodes.

Si Dice.roll()renvoie simplement un nouveau nombre aléatoire de 1 à 6, il ne change pas d'état. Certes, vous partagez peut-être une Randominstance, mais je ne considérerais pas qu'un changement d'état comme par définition, la sortie sera toujours bien, aléatoire. Il est également thread-safe donc il n'y a aucun problème ici.

Vous verrez souvent des "Helper" finales ou d'autres classes utilitaires qui ont un constructeur privé et des membres statiques. Le constructeur privé ne contient aucune logique et sert uniquement à empêcher quelqu'un d'instancier la classe. Le dernier modificateur ramène cette idée à la maison que ce n'est pas une classe dont vous voudriez jamais dériver. Il s'agit simplement d'une classe d'utilité. Si c'est fait correctement, il ne devrait pas y avoir de singleton ou d'autres membres de classe qui ne soient pas eux-mêmes statiques et définitifs.

Tant que vous suivez ces directives et que vous ne faites pas de singletons, il n'y a absolument rien de mal à cela. Vous mentionnez une classe de contrôleur, et cela nécessitera presque certainement des changements d'état, donc je déconseille d'utiliser uniquement des méthodes statiques. Vous pouvez vous appuyer fortement sur une classe d'utilité statique, mais vous ne pouvez pas en faire une classe d'utilité statique.


Qu'est-ce qui est considéré comme un changement d'état pour une classe? Eh bien, permet d'exclure des nombres aléatoires pendant une seconde, car ils ne sont pas déterministes par définition et donc la valeur de retour change souvent.

Une fonction pure est une fonction déterministe, c'est-à-dire que pour une entrée donnée, vous obtiendrez une et exactement une sortie. Vous voulez que les méthodes statiques soient de pures fonctions. En Java, il existe des moyens de modifier le comportement des méthodes statiques pour conserver l'état, mais ce ne sont presque jamais de bonnes idées. Lorsque vous déclarez une méthode comme statique , le programmeur typique supposera d'emblée qu'il s'agit d'une fonction pure. Dévier du comportement attendu est la façon dont vous avez tendance à créer des bogues dans votre programme, en général et à éviter.

Un singleton est une classe contenant des méthodes statiques aussi opposées que possible à la "fonction pure". Un seul membre privé statique est conservé en interne à la classe qui est utilisée pour garantir qu'il y a exactement une instance. Ce n'est pas la meilleure pratique et peut vous causer des ennuis plus tard pour un certain nombre de raisons. Pour savoir de quoi nous parlons, voici un exemple simple de singleton:

// DON'T DO THIS!
class Singleton {
  private String name; 
  private static Singleton instance = null;

  private Singleton(String name) {
    this.name = name;
  }

  public static Singleton getInstance() {
    if(instance == null) {
      instance = new Singleton("George");
    }
    return instance;
  }

  public getName() {
    return name;
  }
}

assert Singleton.getInstance().getName() == "George"
Neil
la source
7
Si je veux tester ce qui se passe quand un double six est lancé, je suis coincé ici car vous avez une classe statique avec un générateur de nombres aléatoires statiques. Donc non, Dice.roll()n'est pas une exception valable à la règle «pas d'état global».
David Arno
1
@HexTeke Réponse mise à jour.
Neil
1
@DavidArno Vrai, mais je suppose qu'à ce stade, nous sommes vraiment en difficulté si nous testons un seul appel à random.nextInt(6) + 1. ;)
Neil
3
@Neil, excuses, je ne me suis pas très bien expliqué. Nous ne testons pas la séquence de nombres aléatoires, nous affectons cette séquence pour aider d'autres tests. Si, par exemple, nous testons à RollAndMove()nouveau si nous fournissons un double six, la façon la plus simple et la plus robuste de le faire est de simuler l'un Diceou l' autre ou le générateur de nombres aléatoires. Ergo, Dicene veut pas être une classe statique utilisant un générateur aléatoire statique.
David Arno
12
Votre mise à jour frappe le problème sur la tête cependant: les méthodes statiques doivent être déterministes; ils ne devraient avoir aucun effet secondaire.
David Arno
9

Pour donner un exemple des limites d'une staticclasse, que se passe-t-il si certains de vos joueurs souhaitent obtenir un léger bonus sur leurs jets de dé? Et ils sont prêts à payer beaucoup d'argent! :-)

Oui, vous pouvez ajouter un autre paramètre, donc Dice.roll(bonus),

Plus tard, vous aurez besoin de D20.

Dice.roll(bonus, sides)

Oui, mais certains joueurs ont l'exploit "suprêmement capable" afin qu'ils ne puissent jamais "tâtonner" (lancer 1).

Dice.roll(bonus, sides, isFumbleAllowed).

Cela devient désordonné, n'est-ce pas?

user949300
la source
cela semble être orthogonal à la question, il devient désordonné qu'il s'agisse de méthodes statiques ou de méthodes normales
jk.
4
@jk, je pense avoir compris son point. Cela n'a pas de sens d'avoir une classe statique Dés lorsque vous pensez à plus de types de dés. Dans ce cas, nous pouvons avoir différents objets de dés, en les modélisant avec de bonnes pratiques de POO.
Dherik
1
@TimothyTruckle Je ne conteste pas cela, tout ce que je dis, c'est que cette réponse ne répond pas réellement à la question, car ce type de fluage de portée n'a rien à voir avec la méthode statique ou non.
nvoigt
2
@nvoigt "Je ne conteste pas cela" - Eh bien, oui . La caractéristique la plus puissante d'un langage OO est le polymorphisme . Et l'accès statique nous empêche effectivement de l'utiliser du tout. Et vous avez raison: new Dice().roll(...)doit être concidered accès statique ainsi que Dice.roll(...). Nous ne bénéficions que si nous injectons cette dépendance.
Timothy Truckle
2
@jk la différence étant que le staticdésordre se produit dans chaque appel, et les connaissances nécessaires sont requises dans chaque appel, donc il est éclaboussé partout dans votre application. Dans une structure POO, seul le constructeur / l'usine doit être désordonné et les détails de ce dé sont encapsulés. Après cela, utilisez le polymorphisme et appelez simplement roll(). Je pourrais modifier cette réponse pour clarifier.
user949300
3

Dans le cas particulier d'une classe Dice, je pense que l'utilisation de méthodes d'instance plutôt que de statistiques rendra les tests beaucoup plus faciles.

Si vous voulez tester un élément quelque chose qui utilise une instance de dés (par exemple une classe de jeu), à partir de vos tests, vous pouvez injecter une forme de test double de dés qui renvoie toujours une séquence fixe de valeurs. Votre test peut vérifier que le jeu a le bon résultat pour ces lancers de dés.

Je ne suis pas un développeur Java, mais je pense qu'il serait beaucoup plus difficile de le faire avec une classe Dice entièrement statique. Voir /programming/4482315/why-does-mockito-not-mock-static-methods

bdsl
la source
Pour mémoire : en Java, nous avons PowerMock pour remplacer les dépendances statiques en manipulant le code d'octet. Mais l'utiliser n'est qu'un abandon à un mauvais design ...
Timothy Truckle
0

Ceci est en fait connu comme le modèle Monostate , où chaque instance (et même une "non-instance") partage son statut. Chaque membre est un membre de la classe (c'est-à-dire aucun membre d'instance). Ils sont couramment utilisés pour implémenter des classes "toolkit" qui regroupent un ensemble de méthodes et de constantes liées à une seule responsabilité ou exigence mais n'ont pas besoin d'un état pour fonctionner (elles sont purement fonctionnelles). En fait, Java est fourni avec certains d'entre eux (par exemple, Math ).

Un peu hors sujet: je suis rarement d'accord avec la dénomination des mots clés dans VisualBasic, mais dans ce cas, je pense que sharedc'est certainement plus clair et sémantiquement meilleur (il est partagé entre la classe elle-même et toutes ses instances) que static(reste après le cycle de vie de l'étendue dans laquelle il est déclaré).

Jesus Alonso Abad
la source