En nuestra base de código Java sigo viendo el siguiente patrón:
/**
This is a stateless utility class
that groups useful foo-related operations, often with side effects.
*/
public class FooUtil {
public int foo(...) {...}
public void bar(...) {...}
}
/**
This class does applied foo-related things.
*/
class FooSomething {
int DoBusinessWithFoo(FooUtil fooUtil, ...) {
if (fooUtil.foo(...)) fooUtil.bar(...);
}
}
Lo que me molesta es que tengo que pasar una instancia de FooUtil
todas partes, porque las pruebas.
- No puedo hacer que
FooUtil
los métodos sean estáticos, porque no podré burlarme de ellos para probar ambosFooUtil
y sus clases de cliente. - No puedo crear una instancia
FooUtil
en el lugar de consumo con anew
, de nuevo porque no podré burlarme de ella para probarla.
Supongo que mi mejor opción es usar inyección (y lo hago), pero agrega su propio conjunto de problemas. Además, pasar varias instancias de utilidades infla el tamaño de las listas de parámetros del método.
¿Hay alguna manera de manejar esto mejor que no estoy viendo?
Actualización: dado que las clases de utilidad no tienen estado, probablemente podría agregar un INSTANCE
miembro singleton estático , o un getInstance()
método estático , al tiempo que conserva la capacidad de actualizar el campo estático subyacente de la clase para la prueba. Tampoco parece súper limpio.
FooUtil
métodos deberían ser incorporadosFooSomething
, tal vez hay propiedadesFooSomething
que deberían ser cambiadas / extendidas para que losFooUtil
métodos ya no sean necesarios. Danos un ejemplo más concreto de quéFooSomething
nos puede ayudar a dar una buena respuesta a estas preguntas.Respuestas:
En primer lugar, permítanme decir que existen diferentes enfoques de programación para diferentes problemas. Para mi trabajo, prefiero un diseño OO sólido para la organización y el mantenimiento, y mi respuesta lo refleja. Un enfoque funcional tendría una respuesta muy diferente.
Tiendo a encontrar que la mayoría de las clases de utilidad se crean porque el objeto en el que deberían estar los métodos no existe. Esto casi siempre proviene de uno de dos casos, ya sea que alguien está pasando colecciones o alguien está pasando frijoles (a menudo se los llama POJO, pero creo que un montón de setters y getters se describe mejor como un bean).
Cuando tienes una colección que estás pasando, debe haber un código que quiera ser parte de esa colección, y esto termina siendo esparcido por todo tu sistema y eventualmente recopilado en clases de utilidad.
Mi sugerencia es envolver sus colecciones (o beans) con cualquier otro dato estrechamente asociado en nuevas clases y poner el código de "utilidad" en objetos reales.
Puede extender la colección y agregar los métodos allí, pero pierde el control del estado de su objeto de esa manera. Le sugiero que lo encapsule completamente y solo exponga los métodos de lógica de negocios que sean necesarios (piense "No pregunte a sus objetos por sus datos, dar comandos a sus objetos y dejar que actúen sobre sus datos privados ", un inquilino importante de OO y uno que, cuando lo piensa, requiere el enfoque que sugiero aquí).
Este "ajuste" es útil para cualquier objeto de biblioteca de propósito general que contenga datos pero a los que no se les puede agregar código: poner código con los datos que manipula es otro concepto importante en el diseño OO.
Hay muchos estilos de codificación donde este concepto de envoltura sería una mala idea: ¿quién quiere escribir una clase completa cuando solo implementa un script o prueba de una sola vez? Pero creo que la encapsulación de cualquier tipo / colección básica que no pueda agregar código es obligatoria para OO.
fuente
Puede usar un patrón de proveedor e inyectarle
FooSomething
unFooUtilProvider
objeto (podría estar en un constructor; solo una vez).FooUtilProvider
sólo tendría un método:FooUtils get();
. La clase luego usaría cualquierFooUtils
instancia que proporcione.Ahora que está a un paso de usar un marco de Inyección de dependencias, cuando solo tendrías que conectar dos veces el codificador DI: para el código de producción y para tu conjunto de pruebas. Simplemente vincula la
FooUtils
interfaz a cualquieraRealFooUtils
oMockedFooUtils
, y el resto sucede de forma automática. Cada objeto que dependeFooUtilsProvider
obtiene la versión adecuada deFooUtils
.fuente