¿Las clases de utilidad son malas? [cerrado]


96

Vi este hilo

Si una clase de "Utilidades" es mala, ¿dónde pongo mi código genérico?

y pensé ¿por qué son malvadas las clases de servicios públicos?

Digamos que tengo un modelo de dominio que tiene decenas de clases de profundidad. Necesito poder xml-ify instancias. ¿Hago un método toXml en el padre? ¿Hago una clase auxiliar MyDomainXmlUtility.toXml? Este es un caso en el que la necesidad empresarial abarca todo el modelo de dominio: ¿realmente pertenece como método de instancia? ¿Qué pasa si hay un montón de métodos auxiliares en la funcionalidad xml de la aplicación?


27
¡La devaluación del término "maldad" es malvada!
Matthew Lock

1
@matthew conservé los términos de la publicación en la que se basa mi pregunta ...;)
hvgotcodes

Las clases de utilidad son una mala idea por las mismas razones que los singleton.
CurtainDog

1
El debate sobre la conveniencia de tener un método toXML se centra en modelos de dominios ricos o anémicos. codeflow.blogspot.com/2007/05/anemic-vs-rich-domain-models.html
James P.

@james, el toXML es solo un ejemplo ... ¿qué pasa con algunas funciones de expresiones regulares que se utilizan en todas partes? Por ejemplo, necesita hacer algunas cosas con las cadenas en su modelo de dominio, pero no pudo usar subclases debido a otra preocupación primordial que usó su única superclase (en java)
hvgotcodes

Respuestas:


128

Las clases de utilidad no son exactamente malas, pero pueden violar los principios que componen un buen diseño orientado a objetos. En un buen diseño orientado a objetos, la mayoría de las clases deberían representar una sola cosa y todos sus atributos y operaciones. Si está operando en una cosa, ese método probablemente debería ser miembro de esa cosa.

Sin embargo, hay ocasiones en las que puede usar clases de utilidades para agrupar varios métodos; un ejemplo es la java.util.Collectionsclase que proporciona una serie de utilidades que se pueden usar en cualquier colección de Java. Estos no son específicos de un tipo particular de colección, sino que implementan algoritmos que se pueden usar en cualquier colección.

Realmente, lo que debe hacer es pensar en su diseño y determinar dónde tiene más sentido colocar los métodos. Por lo general, es como operaciones dentro de una clase. Sin embargo, a veces, es una clase de utilidad. Sin embargo, cuando utilice una clase de utilidad, no se limite a incluir métodos aleatorios en ella, sino que organice los métodos por propósito y funcionalidad.


se relaciona bien con este artículo sobre la verificación de la clase de utilidad / ayudante contra el principio SOLID oo, así como legibilidad.com/articles/rk1vvcqy
melaos

8
Si el lenguaje no ofrece ningún mecanismo de espacio de nombres que no sean clases, no tiene más remedio que abusar de una clase donde lo haría un espacio de nombres. En C ++, puede colocar una función independiente, no relacionada con otras funciones, en un espacio de nombres. En Java, debe colocarlo en una clase como miembro estático (clase).
Unslander Monica

1
La agrupación como métodos puede ser canónica desde el punto de vista de la programación orientada a objetos. Sin embargo, OOP rara vez es la solución (entre las excepciones se encuentran la arquitectura de alto nivel y los kits de herramientas de widgets como una de las instancias en las que la semántica increíblemente rota de la reutilización del código por herencia realmente encaja) y, en general, los métodos aumentan el acoplamiento y las dependencias. Ejemplo trivial: si proporciona métodos de impresora / escáner a su clase como métodos, acopla la clase a las bibliotecas de impresora / escáner. Además, usted fija el número de posibles implementaciones en efectivamente una. A menos que agregue la interfaz y aumente aún más las dependencias.
Jo So

Por otro lado, estoy de acuerdo con su opinión "agrupar por propósito y funcionalidad". Repasemos el ejemplo de la impresora / escáner nuevamente, y comencemos con un conjunto de clases concertadas, diseñe con un propósito común. Es posible que desee escribir algún código de depuración y diseñar una representación textual para sus clases. Puede implementar sus impresoras de depuración en un solo archivo que depende de todas esas clases y una biblioteca de impresoras. El código que no es de depuración no se verá afectado por las dependencias de esta implementación.
Jo So

1
Las clases Util y Helper son un desafortunado artefacto de tales restricciones, y aquellos que no entienden OOP o no entienden el dominio del problema han seguido escribiendo código de procedimiento.
bilelovitch

57

Creo que el consenso general es que las clases de servicios públicos no son malas per se . Solo necesita usarlos con prudencia:

  • Diseñe los métodos de utilidad estática para que sean generales y reutilizables. Asegúrese de que sean apátridas; es decir, sin variables estáticas.

  • Si tiene muchos métodos de utilidad, divídalos en clases de manera que sea más fácil para los desarrolladores encontrarlos.

  • No use clases de utilidad donde los métodos estáticos o de instancia en una clase de dominio serían una mejor solución. Por ejemplo, considere si los métodos de una clase base abstracta o una clase auxiliar de la que se pueden crear instancias serían una mejor solución.

  • Para Java 8 en adelante, los "métodos predeterminados" en una interfaz pueden ser una mejor opción que las clases de utilidad. (Consulte Interfaz con métodos predeterminados frente a la clase abstracta en Java 8, por ejemplo).


La otra forma de ver esta Pregunta es observar que en la Pregunta citada, "Si las clases de utilidad son" malas "" es un argumento de paja. Es como si yo preguntara:

"Si los cerdos pueden volar, ¿debo llevar un paraguas?".

En la pregunta anterior, en realidad no estoy diciendo que los cerdos puedan volar ... o que estoy de acuerdo con la proposición de que podrían volar.

Las declaraciones típicas de "xyz es malvado" son recursos retóricos que tienen la intención de hacerte pensar planteando un punto de vista extremo. Rara vez (si es que alguna vez) se intentan como declaraciones de hechos literales.


16

Las clases de servicios públicos son problemáticas porque no agrupan las responsabilidades con los datos que las respaldan.

Sin embargo, son extremadamente útiles y los construyo todo el tiempo como estructuras permanentes o como peldaños durante una refactorización más completa.

Desde la perspectiva del Código Limpio , las clases de servicios públicos violan la Responsabilidad Única y el Principio Abierto-Cerrado. Tienen muchas razones para cambiar y, por diseño, no son extensibles. Realmente solo deberían existir durante la refactorización como cruft intermedio.


2
Yo lo llamaría un problema práctico ya que, por ejemplo, en Java no se pueden crear funciones que no sean métodos en una clase. En tal lenguaje, la "clase sin variables y con solo métodos estáticos" es un idioma que representa un espacio de nombres de funciones de utilidad ... Cuando se enfrenta a una clase de este tipo en Java, en mi humilde opinión es inválido y sin sentido hablar de una clase , aunque classse utilice la palabra clave. Grazna como un espacio de nombres, camina como un espacio de nombres, es uno ...
Unslander Monica

2
Lamento ser franco, pero "métodos estáticos sin estado [...] bichos raros en un problema filosófico [...] de un sistema de programación orientada a objetos" es extremadamente equivocado. Es GRANDE si puede evitar el estado porque eso facilita escribir el código correcto. Está ROTO cuando tengo que escribir x.equals(y)porque 1) el hecho de que esto realmente no debería modificar ningún estado no se transmite (y no puedo confiar en que no conozca la implementación) 2) Nunca tuve la intención de poner xun " posición favorecida "o" sobre la que se "actuó" en comparación con y3) No quiero considerar las "identidades" de sus valores xo ysimplemente me interesan.
Jo So

4
Realmente, la mayor parte de la funcionalidad en el mundo real se expresa mejor como funciones matemáticas estáticas, sin estado. ¿Math.PI es un objeto que debería modificarse? ¿Realmente tengo que crear una instancia de AbstractSineCalculator que implemente IAbstractRealOperation solo para obtener el seno de un número?
Jo So

1
@JoSo Estoy completamente de acuerdo con el valor de la apatridia y el cálculo directo. Naive OO se opone filosóficamente a esto y creo que eso lo hace profundamente defectuoso. Fomenta la abstracción de cosas que no la necesitan (como ha demostrado) y no proporciona abstracciones significativas para el cálculo real. Sin embargo, un enfoque equilibrado para usar un lenguaje OO tiende a la inmutabilidad y la apatridia, ya que promueven la modularidad y la capacidad de mantenimiento.
Alain O'Dea

2
@ AlainO'Dea: Me doy cuenta de que leí mal su comentario como opuesto a la funcionalidad sin estado. Pido disculpas por mi tono: actualmente estoy involucrado en mi primer proyecto Java (después de evitar la programación orientada a objetos durante la mayor parte de mis 10 años de programación) y estoy enterrado bajo capas de cosas abstractas con estados y significados poco claros. Y solo necesito un poco de aire fresco :-).
Jo So

8

Supongo que empieza a volverse malvado cuando

1) Se vuelve demasiado grande (solo agrúpelos en categorías significativas en este caso).
2) Existen métodos que no deberían ser estáticos

Pero mientras no se cumplan estas condiciones, creo que son muy útiles.


"Existen métodos que no deberían ser métodos estáticos". ¿Cómo es esto posible?
Koray Tugay

@KorayTugay Considere lo no estático Widget.compareTo(Widget widget)frente a lo estático WidgetUtils.compare(Widget widgetOne, Widget widgetTwo). La comparación es un ejemplo de algo que no debería hacerse de forma estática.
ndm13

5

Regla de oro

Puede ver este problema desde dos perspectivas:

  • Los *Utilmétodos generales son a menudo una sugerencia de un mal diseño de código o una convención de nomenclatura lenta.
  • Es una solución de diseño legítima para funcionalidades sin estado entre dominios reutilizables. Tenga en cuenta que para casi todos los problemas comunes existen soluciones.

Ejemplo 1. Uso correcto de utilclases / módulos. Ejemplo de biblioteca externa

Supongamos que está escribiendo una aplicación que administra préstamos y tarjetas de crédito. Los datos de estos módulos se exponen a través de servicios web en jsonformato. En teoría, puede convertir objetos manualmente en cadenas que estarán dentro json, pero eso reinventaría la rueda. La solución correcta sería incluir en ambos módulos una biblioteca externa utilizada para convertir objetos Java al formato deseado. (en la imagen de ejemplo he mostrado gson )

ingrese la descripción de la imagen aquí


Ejemplo 2. Uso correcto de utilclases / módulos. Escribir el tuyo utilsin excusas a otros miembros del equipo

Como caso de uso, supongamos que necesitamos realizar algunos cálculos en dos módulos de aplicación, pero ambos necesitan saber cuándo hay días festivos en Polonia. En teoría, puede realizar estos cálculos dentro de los módulos, pero sería mejor extraer esta funcionalidad en un módulo separado.

Aquí hay un detalle pequeño, pero importante. La clase / módulo que ha escrito no se llama HolidayUtil, pero PolishHolidayCalculator. Funcionalmente es una utilclase, pero hemos logrado evitar la palabra genérica.

ingrese la descripción de la imagen aquí


1
Veo un fan de YEd;) Aplicación increíble.
Sridhar Sarnobat

3

Las clases de servicios públicos son malas porque significan que eras demasiado vago para pensar en un nombre mejor para la clase :)

Dicho esto, soy un vago. A veces solo necesitas hacer el trabajo y tu mente está en blanco ... ahí es cuando las clases de "Utilidad" comienzan a aparecer.


3

Mirando hacia atrás en esta pregunta ahora, diría que los métodos de extensión de C # destruyen por completo la necesidad de clases de utilidad. Pero no todos los lenguajes tienen una construcción tan genial.

También tiene JavaScript, donde puede agregar una nueva función directamente al objeto existente.

Pero no estoy seguro de que realmente haya una forma elegante de resolver este problema en un lenguaje más antiguo como C ++ ...

Un buen código OO es un poco difícil de escribir y es difícil de encontrar, ya que escribir Good OO requiere mucho más tiempo / conocimiento que escribir un código funcional decente.

Y cuando tienes un presupuesto limitado, tu jefe no siempre está feliz de ver que has pasado todo el día escribiendo un montón de clases ...


3

No estoy del todo de acuerdo con que las clases de servicios públicos sean malas.

Si bien una clase de utilidad puede violar los principios de OO de alguna manera, no siempre son malos.

Por ejemplo, imagina que quieres una función que limpie una cadena de valores coincidentes de todas las subcadenas x.

stl c ++ (a partir de ahora) no es compatible directamente con esto.

Podría crear una extensión polimórfica de std::string.

Pero el problema es, ¿realmente quieres que CADA cadena que uses en tu proyecto sea tu clase de cadena extendida?

Hay momentos en los que OO realmente no tiene sentido, y este es uno de ellos. Queremos que nuestro programa sea compatible con otros programas, así que nos quedaremos std::stringy crearemos una clase StringUtil_(o algo).

Yo diría que es mejor si te quedas con una utilidad por clase. Yo diría que es una tontería tener una utilidad para todas las clases o muchas utilidades para una clase.


2

Es muy fácil calificar algo como una utilidad simplemente porque el diseñador no pudo pensar en un lugar apropiado para colocar el código. A menudo hay pocas "utilidades" verdaderas.

Como regla general, generalmente guardo el código en el paquete donde se usa por primera vez, y luego solo lo refactorizo ​​a un lugar más genérico si descubro que más tarde realmente se necesita en otro lugar. La única excepción es si ya tengo un paquete que realiza una funcionalidad similar / relacionada, y el código encaja mejor allí.


2

Las clases de utilidad que contienen métodos estáticos sin estado pueden resultar útiles. Suelen ser muy fáciles de realizar pruebas unitarias.



1

La mayoría de las clases de Util son malas porque:

  1. Amplían el alcance de los métodos. Hacen público el código que de otro modo sería privado. Si varias personas que llaman necesitan el método util en clases separadas y es estable (es decir, no necesita actualización), en mi opinión, es mejor copiar y pegar métodos auxiliares privados en la clase de llamada. Una vez que lo expone como una API, hace que sea más difícil entender cuál es el punto de entrada público a un componente jar (mantiene un árbol estructurado llamado jerarquía con un padre por método. Esto es más fácil de segregar mentalmente en componentes, lo cual es más difícil cuando tiene métodos llamados desde varios métodos principales).
  2. Resultan en código muerto. Los métodos de utilidades con el tiempo dejan de utilizarse a medida que su aplicación evoluciona y termina con un código no utilizado que contamina su base de código. Si hubiera permanecido privado, su compilador le diría que el método no está en uso y que podría simplemente eliminarlo (el mejor código es ningún código). Una vez que haga que este método no sea privado, su computadora no podrá ayudarlo a eliminar el código no utilizado. Se puede llamar desde un archivo jar diferente para todo lo que la computadora sabe.

Hay algunas analogías entre bibliotecas estáticas y dinámicas.


0

Cuando no puedo agregar un método a una clase (digamos, Accountestá bloqueado contra cambios por Desarrolladores Jr.), simplemente agrego algunos métodos estáticos a mi clase de Utilidades así:

public static int method01_Account(Object o, String... args) {
    Account acc = (Account)o;
    ...
    return acc.getInt();
}  

1
Parece que eres el Jr para mí ...: P La forma no maligna de hacer esto es pedirle cortésmente a tu "Jr Developer" que desbloquee el Accountarchivo. O mejor, opte por sistemas de control de fuente sin bloqueo.
Sudeep

1
Supongo que quiso decir que el Accountarchivo estaba bloqueado de manera que los desarrolladores Jr. no pueden realizar cambios en él. Como desarrollador Jr., para sortearlo, hizo lo anterior. Dicho esto, es mejor obtener acceso de escritura al archivo y hacerlo correctamente.
Doc

La adición de 1 a esto porque downvotes parecer duro cuando nadie tiene todo el contexto de la razón por que proporcionó esta respuesta
joelc

0

Las clases de utilidad no siempre son malas. Pero solo deben contener los métodos que son comunes en una amplia gama de funciones. Si hay métodos que solo se pueden utilizar entre un número limitado de clases, considere la posibilidad de crear una clase abstracta como un padre común y coloque los métodos en ella.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.