En algunos de mis códigos, tengo una fábrica estática similar a esta:
public class SomeFactory {
// Static class
private SomeFactory() {...}
public static Foo createFoo() {...}
public static Foo createFooerFoo() {...}
}
Durante una revisión de código, se propuso que este debería ser un singleton e inyectado. Entonces, debería verse así:
public class SomeFactory {
public SomeFactory() {}
public Foo createFoo() {...}
public Foo createFooerFoo() {...}
}
Algunas cosas a destacar:
- Ambas fábricas son apátridas.
- La única diferencia entre los métodos son sus ámbitos (instancia frente a estático). Las implementaciones son las mismas.
- Foo es un bean que no tiene una interfaz.
Los argumentos que tuve para volverme estático fueron:
- La clase no tiene estado, por lo tanto, no necesita ser instanciada
- Parece más natural poder llamar a un método estático que tener que crear una instancia de fábrica
Los argumentos para la fábrica como singleton fueron:
- Es bueno inyectar todo
- A pesar de la apatridia de la fábrica, las pruebas son más fáciles con la inyección (fácil de burlar)
- Se debe burlar al probar al consumidor
Tengo algunos problemas serios con el enfoque singleton ya que parece sugerir que ningún método debería ser estático. También parece sugerir que las utilidades como StringUtils
deberían ser envueltas e inyectadas, lo que parece una tontería. Por último, implica que tendré que burlarme de la fábrica en algún momento, lo que no parece correcto. No puedo pensar en cuándo necesitaría burlarme de la fábrica.
¿Qué piensa la comunidad? Si bien no me gusta el enfoque singleton, no parece tener un argumento terriblemente fuerte contra él.
fuente
DateTime
yFile
son notoriamente difíciles de probar exactamente por las mismas razones. Si tiene una clase, por ejemplo, que establece laCreated
fechaDateTime.Now
en el constructor, ¿cómo hace para crear una prueba unitaria con dos de estos objetos que se crearon con 5 minutos de diferencia? ¿Qué pasa con años de diferencia? Realmente no puedes hacerlo (sin mucho trabajo).private
constructor y ungetInstance()
método? Lo siento, ¡recolector de liendres incorregible!Respuestas:
¿Por qué separarías tu fábrica del tipo de objeto que crea?
Esto es lo que Joshua Bloch describe como su Elemento 1 en la página 5 de su libro, "Java eficaz". Java es lo suficientemente detallado sin agregar clases adicionales y singletons y demás. Hay algunos casos en los que una clase de fábrica separada tiene sentido, pero son pocos y distantes entre sí.
Parafraseando el Artículo 1 de Joshua Bloch, a diferencia de los constructores, los métodos estáticos de fábrica pueden:
Desventajas
Realmente, deben llevar el libro de Bloch a su revisor de códigos y ver si los dos pueden estar de acuerdo con el estilo de Bloch.
fuente
public static IFoo of(...) { return new Foo(...); }
¿Cuál es el problema? Bloch enumera satisfacer su preocupación como una de las ventajas de este diseño (segundo punto anterior). No debo entender tu comentario.Justo fuera de mi cabeza, estos son algunos de los problemas con la estática en Java:
los métodos estáticos no juegan según las "reglas" de OOP. No puede forzar a una clase a implementar métodos estáticos específicos (que yo sepa). Por lo tanto, no puede tener varias clases implementando el mismo conjunto de métodos estáticos con las mismas firmas. Entonces estás atrapado con uno de lo que sea que estés haciendo. Realmente tampoco puedes subclasificar una clase estática. Entonces no hay polimorfismo, etc.
Como los métodos no son objetos, los métodos estáticos no se pueden pasar y no se pueden usar (sin hacer un montón de codificación repetitiva), excepto por el código que está vinculado a ellos, lo que hace que el código del cliente sea altamente acoplado. (Para ser justos, esto no es solo culpa de la estática).
los campos estáticos son bastante similares a los globales
Mencionaste:
Lo que me da curiosidad por preguntarte:
StringUtils
está correctamente diseñado e implementado?fuente
Foo f = createFoo();
no tener una instancia con nombre. La instancia en sí no proporciona ninguna utilidad. (2) Creo que sí. Fue diseñado para superar el hecho de que muchos de esos métodos no existen dentro de laString
clase. Reemplazar algo tan fundamental comoString
no es una opción, por lo que las utilidades son el camino a seguir. (continuación)Integer
. Son muy simples, tienen muy poca lógica y solo están ahí para proporcionar conveniencia (por ejemplo, no hay otra razón OO para tenerlos). La parte principal de mi argumento es que no se basan en ningún estado, no hay razón para aislarlos durante las pruebas. Las personas no se burlanStringUtils
cuando hacen pruebas, ¿por qué necesitarían burlarse de esta simple fábrica de conveniencia?StringUtils
porque hay una garantía razonable de que los métodos allí no tienen ningún efecto secundario.StringUtils
devuelve algún resultado sin alterar las entradas, mi fábrica tampoco modifica nada.Creo que la aplicación que está creando debe ser bastante sencilla, o de lo contrario, está relativamente temprano en su ciclo de vida. El único otro escenario en el que no podría haber tenido problemas con sus estadísticas estáticas es si ha logrado limitar las otras partes de su código que conocen su Fábrica a uno o dos lugares.
Imagine que su aplicación evoluciona y ahora, en algunos casos, necesita producir una
FooBar
(subclase de Foo). Ahora necesita revisar todos los lugares en su código que conocen suSomeFactory
y escribir algún tipo de lógica que miresomeCondition
y llameSomeFactory
oSomeOtherFactory
. En realidad, mirando tu código de ejemplo, es peor que eso. Planea simplemente agregar método tras método para crear diferentes Foos y todo el código de su cliente debe verificar una condición antes de determinar qué Foo debe hacer. Lo que significa que todos los clientes necesitan tener un conocimiento íntimo de cómo funciona su Fábrica y todos deben cambiar cada vez que se necesita un nuevo Foo (¡o se lo quita!).Ahora imagine si acaba de crear un
IFooFactory
que hace unIFoo
. Ahora, producir una subclase diferente o incluso una implementación completamente diferente es un juego de niños: simplemente ingresas la IFooFactory correcta más arriba en la cadena y luego, cuando el cliente la obtiene, llamagimmeAFoo()
y obtendrá la información correcta porque obtuvo la fábrica correcta .Tal vez se pregunte cómo se desarrolla esto en el código de producción. Para darle un ejemplo de la base de código en la que estoy trabajando que está comenzando a nivelarse después de poco más de un año de elaborar proyectos, tengo un montón de Fábricas que se utilizan para crear objetos de datos de Preguntas (son algo así como Modelos de Presentación ) Tengo
QuestionFactoryMap
un hash básicamente para buscar qué fábrica de preguntas usar cuando. Entonces, para crear una aplicación que haga diferentes preguntas, pongo diferentes fábricas en el mapa.Las preguntas pueden ser presentadas en cualquiera de las instrucciones o ejercicios (que tanto implemento
IContentBlock
), por lo que cada unoInstructionsFactory
oExerciseFactory
recibirá una copia de la QuestionFactoryMap. Luego, las diferentes fábricas de Instrucciones y Ejercicios se entregan a laContentBlockBuilder
que nuevamente mantiene un hash para usar cuando. Y luego, cuando se carga el XML, ContentBlockBuilder llama a la Fábrica de ejercicios o instrucciones del hash y lo solicitacreateContent()
, y luego la Fábrica delega la construcción de las preguntas a lo que sea que extraiga de su QuestionFactoryMap interno en función de qué está en cada nodo XML frente al hash. Desafortunadamente , esa cosa es unBaseQuestion
lugar en lugar de unIQuestion
, pero eso demuestra que incluso cuando se tiene cuidado con el diseño, puede cometer errores que pueden perseguirlo en el futuro.Tu instinto te dice que estas estadísticas te harán daño, y ciertamente hay mucho en la literatura para apoyar tu intestino. Nunca perderá al recibir la Inyección de dependencia que termina usando solo para sus Pruebas de unidad (no es que crea que si construye un buen código no lo encontrará usando más que eso), pero las estadísticas pueden destruir completamente su habilidad para modificar rápida y fácilmente su código. Entonces, ¿por qué incluso ir allí?
fuente
Integer
clase, cosas simples como la conversión de una cadena. Eso es lo queFoo
hace. MiFoo
no está destinado a extenderse (es mi culpa por no decir esto), por lo que no tengo sus problemas polimórficos. Entiendo el valor de tener fábricas polimórficas y las he usado en el pasado ... Simplemente no creo que sean apropiadas aquí. ¿Te imaginas si tienes que crear una instancia de una fábrica para realizar las operaciones simplesInteger
que actualmente están integradas a través de fábricas estáticas?Foo
, sin embargo, es mucho más simple que muchas de las clases. Es solo un simple POJO.if (this) then {makeThisFoo()} else {makeThatFoo()}
través de su código. Una cosa que he notado sobre las personas con una arquitectura crónicamente mala es que son como personas con dolor crónico. En realidad, no saben lo que se siente no sentir dolor, por lo que el dolor que sienten no parece tan malo.Foo
. Con razón, nunca lo declaró definitivo.Foo
es un bean que no debe extenderse.