Java: ¿es una mala idea tener clases completamente estáticas?

16

Estoy trabajando en un proyecto en solitario más grande y en este momento, y tengo varias clases en las que no veo ninguna razón para crear una instancia.

Mi clase de dados en este momento, por ejemplo, almacena todos sus datos estáticamente y todos sus métodos también son estáticos. No necesito inicializarlo porque cuando quiero tirar los dados y obtener un nuevo valor, solo lo uso Dice.roll().

Tengo varias clases similares que solo tienen una función principal como esta y estoy a punto de comenzar a trabajar en una especie de clase "controladora" que estará a cargo de todos los eventos (como cuando un jugador se mueve y qué turno actual lo es) y descubrí que podía seguir la misma idea para esta clase. Nunca planeo crear varios objetos para estas clases específicas, ¿sería una mala idea hacerlos completamente estáticos?

Me preguntaba si esto se considera "mala práctica" cuando se trata de Java. Por lo que he visto, ¿la comunidad parece estar dividida en este tema? ¡De todos modos, me encantaría discutir sobre esto y los enlaces a recursos también serían geniales!

HexTeke
fuente
1
Si su programa es completamente de procedimiento. ¿Por qué elegiste Java?
Laiv
12
Intente escribir pruebas unitarias para estas clases, y descubrirá por qué a las personas no les gustan los métodos estáticos que acceden al estado estático.
Joeri Sebrechts
@Laiv Todavía soy un poco nuevo en la programación, aproximadamente un año de C ++, estoy tomando una clase de Java este semestre y estoy empezando a gustarme mucho más Java, especialmente las bibliotecas de gráficos.
HexTeke
55
En cuanto a las clases estáticas. Si los métodos estáticos son puros (no tienen estado, tienen argumentos de entrada y un tipo de retorno). No hay nada de qué preocuparse
Laiv

Respuestas:

20

No hay nada malo con las clases estáticas que son verdaderamente estáticas . Es decir, no hay un estado interno del que hablar que pueda causar cambios en la salida de los métodos.

Si Dice.roll()simplemente devuelve un nuevo número aleatorio del 1 al 6, no está cambiando de estado. De acuerdo, es posible que esté compartiendo una Randominstancia, pero no consideraría que un cambio de estado ya que, por definición, la salida siempre será buena, aleatoria. También es seguro para subprocesos, por lo que no hay problemas aquí.

A menudo verá "Ayudante" final u otras clases de utilidad que tienen un constructor privado y miembros estáticos. El constructor privado no contiene lógica y solo sirve para evitar que alguien cree una instancia de la clase. El modificador final solo trae esta idea a casa de que esta no es una clase de la que quieras derivarte. Es simplemente una clase de utilidad. Si se hace correctamente, no debe haber un miembro único u otros miembros de la clase que no sean estáticos y finales.

Mientras sigas estas pautas y no estés haciendo singletons, no hay absolutamente nada de malo en esto. Menciona una clase de controlador, y esto seguramente requerirá cambios de estado, por lo que aconsejaría no usar solo métodos estáticos. Puede confiar en gran medida en una clase de utilidad estática, pero no puede convertirla en una clase de utilidad estática.


¿Qué se considera un cambio de estado para una clase? Bueno, excluyamos números aleatorios por un segundo, ya que no son deterministas por definición y, por lo tanto, el valor de retorno cambia a menudo.

Una función pura es aquella que es determinista, es decir, para una entrada dada, obtendrá una y exactamente una salida. Desea que los métodos estáticos sean funciones puras. En Java hay formas de ajustar el comportamiento de los métodos estáticos para mantener el estado, pero casi nunca son buenas ideas. Cuando declaras un método como estático , el programador típico asumirá de inmediato que es una función pura. Desviarse del comportamiento esperado es cómo tiende a crear errores en su programa, en general, y debe evitarse.

Un singleton es una clase que contiene métodos estáticos tan opuestos a la "función pura" como puede ser. Un único miembro privado estático se mantiene internamente en la clase que se utiliza para garantizar que haya exactamente una instancia. Esta no es la mejor práctica y puede meterte en problemas más adelante por varias razones. Para saber de qué estamos hablando, aquí hay un ejemplo simple de un 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
fuente
77
Si quiero probar qué sucede cuando se lanza un doble seis, estoy atrapado aquí, ya que tiene una clase estática con un generador de números aleatorios estáticos. Por lo tanto, no, Dice.roll()no es una excepción válida a la regla "sin estado global".
David Arno
1
@HexTeke Respuesta actualizada.
Neil
1
@DavidArno Cierto, pero supongo que en ese momento estamos realmente en problemas si estamos probando una sola llamada random.nextInt(6) + 1. ;)
Neil
3
@Neil, disculpas, no me expliqué muy bien. No estamos probando la secuencia de números aleatorios, estamos afectando esa secuencia para ayudar a otras pruebas. Si estamos probando que, por ejemplo RollAndMove(), vuelve a rodar cuando suministramos un doble seis, entonces la forma más fácil y más robusta de hacerlo es burlarse de uno Diceo del generador de números aleatorios. Ergo, Diceno quiere ser una clase estática utilizando un generador aleatorio estático.
David Arno
12
Sin embargo, su actualización golpea el problema en la cabeza: los métodos estáticos deben ser deterministas; no deberían tener efectos secundarios.
David Arno
9

Para dar un ejemplo de las limitaciones de una staticclase, ¿qué pasa si algunos de tus jugadores quieren obtener una pequeña bonificación en sus tiradas de dado? ¡Y están dispuestos a pagar mucho dinero! :-)

Sí, podría agregar otro parámetro, entonces Dice.roll(bonus),

Más tarde necesitas D20s.

Dice.roll(bonus, sides)

Sí, pero algunos jugadores tienen la hazaña "supremamente capaz" para que nunca puedan "perder el balón" (sacar un 1).

Dice.roll(bonus, sides, isFumbleAllowed).

Esto se está poniendo desordenado, ¿no es así?

user949300
fuente
Esto parece ser ortogonal a la pregunta, se está volviendo desordenado si se trata de métodos estáticos o métodos normales
jk.
44
@jk, creo que entendí su punto. No tiene sentido tener una clase estática de dados cuando piensas en más tipos de dados. En ese caso, podemos tener diferentes objetos de dados, modelándolos con buenas prácticas de OOP.
Dherik
1
@TimothyTruckle No estoy discutiendo eso, todo lo que digo es que esta respuesta en realidad no responde la pregunta, porque este tipo de alcance no tiene nada que ver con que el método sea estático o no.
nvoigt
2
@nvoigt "No estoy disputando eso" - Bueno, lo hago. La característica más poderosa de un lenguaje OO es el polimorfismo . Y el acceso estático efectivamente nos impide usar eso en absoluto. Y tiene razón: new Dice().roll(...)debe ser considerado acceso estático así como también Dice.roll(...). Solo nos beneficiamos si inyectamos esa dependencia.
Timothy Truckle
2
@jk la diferencia es que el staticdesorden ocurre en cada llamada, y el conocimiento necesario se requiere en cada llamada, por lo que se salpica globalmente en su aplicación. En una estructura OOP, solo el constructor / fábrica necesita estar desordenado, y los detalles de esa matriz están encapsulados. Después de eso, use el polimorfismo y simplemente llame roll(). Podría editar esta respuesta para aclarar.
user949300
3

En el caso particular de una clase Dice, creo que usar métodos de instancia en lugar de estáticos hará que las pruebas sean mucho más fáciles.

Si desea realizar una prueba unitaria de algo que usa una instancia de Dice (p. Ej., Una clase de Juego), entonces, desde sus pruebas, puede inyectar alguna forma de prueba doble de Dice que siempre devuelve una secuencia fija de valores. Su prueba puede verificar que el juego tenga el resultado correcto para esas tiradas de dados.

No soy un desarrollador de Java, pero creo que sería mucho más difícil hacerlo con una clase de dados totalmente estática. Ver /programming/4482315/why-does-mockito-not-mock-static-methods

bdsl
fuente
Solo para los registros: en Java tenemos PowerMock para reemplazar las dependencias estáticas manipulando el código de bytes. Pero usarlo es solo una rendición al mal diseño ...
Timothy Truckle
0

En realidad, esto se conoce como el patrón Monostate , donde cada instancia (e incluso una "no instancia") comparte su estado. Cada miembro es un miembro de la clase (es decir, no hay miembros de instancia). Se usan comúnmente para implementar clases de "kit de herramientas" que agrupan un conjunto de métodos y constantes relacionados con una sola responsabilidad o requisito, pero no necesitan un estado para funcionar (son puramente funcionales). De hecho, Java viene con algunos de ellos agrupados (por ejemplo, Math ).

Un poco fuera de tema: rara vez estoy de acuerdo con el nombre de las palabras clave en VisualBasic, pero en este caso, creo que sharedes ciertamente más claro y semánticamente mejor (se comparte entre la clase misma y todas sus instancias) que static(permanece después del ciclo de vida del alcance en el que se declara).

Jesus Alonso Abad
fuente