Historia de la cuestión
Desde el principio, Java fue desarrollado como un lenguaje orientado a objetos, donde la tarea principal se resolvía creando instancias de clases. Sin embargo, para métodos que se usan con frecuencia (como ordenación, transformación de datos), comenzaron a aparecer clases de utilidades con un conjunto de métodos estáticos (por ejemplo, java.util.Collections).
Problema
Los métodos estáticos facilitan la llamada a funciones auxiliares, pero no son adecuados para almacenar estado, inyección de dependencias y pruebas en aislamiento. Por otro lado, las instancias de clases son más flexibles, pero aumentan la cantidad de código para la inicialización y requieren un ciclo de vida bien pensado.
Solución
Clases de utilidades (utility classes) — un conjunto de métodos estáticos sin estado, no requieren la creación de un objeto. Son buenos para manipulaciones de colecciones, transformaciones, operaciones matemáticas.
Instancias de clases — almacenan estado, utilizan dependencias, proporcionan extensibilidad y testabilidad. Se aplican en lógica empresarial, servicios, controladores, etc.
Ejemplo de código:
// Ejemplo de clase de utilidades public class MathUtils { public static int add(int a, int b) { return a + b; } } // Uso: int sum = MathUtils.add(1, 2); // Ejemplo de instancia de clase para lógica empresarial public class OrderService { private final OrderRepository repo; public OrderService(OrderRepository repo) { this.repo = repo; } public void placeOrder(Order o) { repo.save(o); } }
Características clave:
¿Se pueden heredar clases de utilidades y ampliar sus métodos estáticos?
No, por lo general las clases de utilidades se declaran como final, con un constructor privado. La herencia de métodos estáticos es posible, pero no tiene sentido, ya que los métodos estáticos no se heredan en el nivel de llamada de instancia.
Ejemplo de código:
public final class MyUtils { private MyUtils() {} // previene la creación de instancia }
¿Pueden las clases de utilidades contener estado?
No. Si una clase de utilidades contiene estado (campos de instancia o estáticos), esto viola el principio de escritura de utilidades, lleva a errores de concurrencia y empeora la legibilidad.
¿Se pueden simular métodos estáticos de clases de utilidades al probar?
Solo con herramientas especiales como PowerMock, que hacen que las pruebas sean más complicadas y a veces inestables. En casos normales, el enfoque amigable con DI basado en instancias es preferible para las pruebas.
En el proyecto, todos los servicios se implementan mediante métodos de utilidades estáticas. La inyección de dependencias es imposible, las pruebas unitarias no están aisladas, cada prueba depende del estado del entorno.
Pros:
Contras:
En los servicios se utilizan clases separadas con inyección de dependencias, a través de interfaces. Para transformaciones y operaciones simples, se utilizan clases de utilidades separadas sin estado.
Pros:
Contras: