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!
Respuestas:
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 unaRandom
instancia, 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:
fuente
Dice.roll()
no es una excepción válida a la regla "sin estado global".random.nextInt(6) + 1
. ;)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 unoDice
o del generador de números aleatorios. Ergo,Dice
no quiere ser una clase estática utilizando un generador aleatorio estático.Para dar un ejemplo de las limitaciones de una
static
clase, ¿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í?
fuente
new Dice().roll(...)
debe ser considerado acceso estático así como tambiénDice.roll(...)
. Solo nos beneficiamos si inyectamos esa dependencia.static
desorden 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 llameroll()
. Podría editar esta respuesta para aclarar.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
fuente
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
shared
es ciertamente más claro y semánticamente mejor (se comparte entre la clase misma y todas sus instancias) questatic
(permanece después del ciclo de vida del alcance en el que se declara).fuente