Por ejemplo, una clase generalmente tiene miembros y métodos de clase, por ejemplo:
public class Cat{
private String name;
private int weight;
private Image image;
public void printInfo(){
System.out.println("Name:"+this.name+",weight:"+this.weight);
}
public void draw(){
//some draw code which uses this.image
}
}
Pero después de leer sobre el principio de responsabilidad única y el principio de apertura cerrada, prefiero separar una clase en DTO y clase auxiliar con métodos estáticos solamente, por ejemplo:
public class CatData{
public String name;
public int weight;
public Image image;
}
public class CatMethods{
public static void printInfo(Cat cat){
System.out.println("Name:"+cat.name+",weight:"+cat.weight);
}
public static void draw(Cat cat){
//some draw code which uses cat.image
}
}
Creo que se ajusta al principio de responsabilidad única porque ahora la responsabilidad de CatData es mantener solo los datos, no le importan los métodos (también para CatMethods). Y también se ajusta al principio abierto cerrado porque agregar nuevos métodos no necesita cambiar la clase CatData.
Mi pregunta es, ¿es un buen o un antipatrón?
object-oriented
static-methods
dto
ocomfd
fuente
fuente
Respuestas:
Ha mostrado dos extremos ("todo privado y todos los métodos (quizás no relacionados) en un objeto" frente a "todo público y ningún método dentro del objeto"). En mi humilde opinión, el buen modelado OO no es ninguno de ellos , el punto óptimo está en algún lugar en el medio.
Una prueba decisiva de qué métodos o lógica pertenece a una clase, y qué pertenece fuera, es observar las dependencias que los métodos introducirán. Los métodos que no introducen dependencias adicionales están bien, siempre que se ajusten bien a la abstracción del objeto dado . Los métodos que requieren dependencias externas adicionales (como una biblioteca de dibujo o una biblioteca de E / S) rara vez se ajustan bien. Incluso si hiciera desaparecer las dependencias mediante el uso de la inyección de dependencias, todavía pensaría dos veces si es realmente necesario colocar dichos métodos dentro de la clase de dominio.
Por lo tanto, no debe hacer que todos los miembros sean públicos, ni necesita implementar métodos para cada operación en un objeto dentro de la clase. Aquí hay una sugerencia alternativa:
Ahora el
Cat
objeto proporciona suficiente lógica para permitir que el código circundante se implemente fácilmenteprintInfo
ydraw
sin exponer todos los atributos en público. El lugar adecuado para estos dos métodos no es muy probablemente una clase de diosCatMethods
(yaprintInfo
ydraw
son muy probablemente diferentes preocupaciones, así que creo que es muy poco probable que pertenecen a la misma clase).Me puedo imaginar una
CatDrawingController
que implementedraw
(y tal vez use una inyección de dependencia para obtener un objeto Canvas). También puedo imaginar otra clase que implementa algunos resultados y usos de la consolagetInfo
(por lo queprintInfo
puede volverse obsoleto en este contexto). Pero para tomar decisiones sensatas sobre esto, uno necesita conocer el contexto y cómo seCat
usará realmente la clase.Esa es realmente la forma en que interpreté los críticos del Modelo de dominio anémico de Fowler : para una lógica generalmente reutilizable (sin dependencias externas), las clases de dominio en sí mismas son un buen lugar, por lo que deberían usarse para eso. Pero eso no significa implementar ninguna lógica allí, sino todo lo contrario.
Tenga en cuenta también que el ejemplo anterior todavía deja espacio para tomar una decisión sobre la (im) mutabilidad. Si la
Cat
clase no expondrá ningún setter, yImage
es inmutable en sí misma, este diseño permitirá hacerCat
inmutable (lo que no hará el enfoque DTO). Pero si cree que la inmutabilidad no es necesaria o no es útil para su caso, también puede ir en esa dirección.fuente
getInfo()
podría ser engañoso para alguien que hace esta pregunta. Mezcla sutilmente las responsabilidades de presentación como loprintInfo()
hace, derrotando el propósito de laDrawingController
claseDrawingController
. Estoy de acuerdo con esogetInfo()
yprintInfo()
son nombres horribles. ConsideretoString()
yprintTo(Stream stream)
Respuesta tardía pero no puedo resistirme.
En la mayoría de los casos, la mayoría de las reglas, aplicadas sin pensar, irán terriblemente mal (incluida esta).
Permíteme contarte una historia sobre el nacimiento de un objeto en medio del caos de un código de procedimiento correcto, rápido y sucio que ocurrió, no por diseño, sino por desesperación.
Mi pasante y yo estamos programando pares para crear rápidamente un código desechable para raspar una página web. No tenemos absolutamente ninguna razón para esperar que este código dure mucho, por lo que solo estamos eliminando algo que funciona. Tomamos toda la página como una cadena y cortamos las cosas que necesitamos de la manera más increíblemente frágil que puedas imaginar. No juzgues Funciona.
Ahora, mientras hacía esto, creé algunos métodos estáticos para cortar. Mi pasante creó una clase de DTO que se parecía mucho a la tuya
CatData
.Cuando vi por primera vez el DTO me molestó. Los años de daño que Java ha causado en mi cerebro me hicieron retroceder en los campos públicos. Pero estábamos trabajando en C #. C # no necesita getters y setters prematuros para preservar su derecho a hacer que los datos sean inmutables o encapsulados más tarde. Sin cambiar la interfaz, puede agregarlos cuando lo desee. Tal vez solo para poder establecer un punto de interrupción. Todo sin decirles nada a sus clientes. Sí C #. Boo Java.
Así que me contuve la lengua. Vi como él usaba mis métodos estáticos para inicializar esto antes de usarlo. Teníamos unos 14 de ellos. Era feo, pero no teníamos motivos para preocuparnos.
Luego lo necesitábamos en otros lugares. Nos encontramos queriendo copiar y pegar el código. Se lanzaron 14 líneas de inicialización. Estaba empezando a ser doloroso. Dudó y me pidió ideas.
De mala gana le pregunté, "¿considerarías un objeto?"
Volvió a mirar su DTO y frunció el ceño confundido. "Es un objeto".
"Me refiero a un objeto real"
"¿Eh?"
"Déjame mostrarte algo. Tú decides si es útil"
Elegí un nuevo nombre y rápidamente preparé algo que se parecía un poco a esto:
Esto no hizo nada que los métodos estáticos no estuvieran haciendo. Pero ahora había absorbido los 14 métodos estáticos en una clase donde podían estar solos con los datos en los que trabajaban.
No forcé a mi interno a usarlo. Simplemente lo ofrecí y le dejé decidir si quería seguir con los métodos estáticos. Me fui a casa pensando que probablemente se apegaría a lo que ya tenía trabajando. Al día siguiente descubrí que lo estaba usando en muchos lugares. Descomprimió el resto del código que todavía era feo y procesal, pero esta parte de complejidad ahora estaba oculta para nosotros detrás de un objeto. Fue un poco mejor.
Ahora, seguro, cada vez que acceda a esto, está haciendo un buen trabajo. Un DTO es un buen valor rápido en caché. Me preocupé por eso, pero me di cuenta de que podía agregar el almacenamiento en caché si alguna vez lo necesitáramos sin tocar ninguno de los códigos de uso. Así que no me voy a molestar hasta que nos importe.
¿Estoy diciendo que siempre debes apegarte a los objetos OO sobre los DTO? No. El DTO brilla cuando necesita cruzar un límite que le impide moverse. Los DTO tienen su lugar.
Pero también lo hacen los objetos OO. Aprende a usar ambas herramientas. Aprende lo que cuesta cada uno. Aprenda a dejar que el problema, la situación y el interno decidan. Dogma no es tu amigo aquí.
Dado que mi respuesta ya es ridículamente larga, permítame desacreditarle algunos conceptos erróneos con una revisión de su código.
¿Dónde está tu constructor? Esto no me muestra lo suficiente como para saber si es útil.
Puede hacer muchas cosas tontas en nombre del Principio de responsabilidad única. Podría argumentar que las cadenas de gato y las entradas de gato deben estar separadas. Que los métodos de dibujo y las imágenes deben tener su propia clase. Que su programa en ejecución es una responsabilidad única, por lo que solo debe tener una clase. :PAG
Para mí, la mejor manera de seguir el Principio de responsabilidad única es encontrar una buena abstracción que le permita poner la complejidad en una caja para que pueda ocultarla. Si puede darle un buen nombre que evite que las personas se sorprendan por lo que encuentran cuando miran dentro, lo ha seguido bastante bien. Esperar que dicte más decisiones que eso es pedir problemas. Honestamente, ambos listados de código lo hacen, así que no veo por qué SRP importa aquí.
Bueno no. El principio de abrir y cerrar no se trata de agregar nuevos métodos. Se trata de poder cambiar la implementación de métodos antiguos y no editar nada. Nada que te use y no tus viejos métodos. En su lugar, escribe un código nuevo en otro lugar. Alguna forma de polimorfismo lo hará muy bien. No veo eso aquí.
Bueno demonios, ¿cómo debería saberlo? Mire, hacerlo de cualquier manera tiene beneficios y costos. Cuando separa el código de los datos, puede cambiarlos sin tener que volver a compilar el otro. Tal vez eso es críticamente importante para ti. Tal vez solo haga que su código sea innecesariamente complicado.
Si te hace sentir mejor, no estás tan lejos de algo que Martin Fowler llama un objeto de parámetro . No tiene que tomar solo primitivas en su objeto.
Lo que me gustaría que hicieras es desarrollar una idea de cómo hacer tu separación, o no, en cualquier estilo de codificación. Porque lo creas o no, no estás obligado a elegir un estilo. Solo tienes que vivir con tu elección.
fuente
Te has topado con un tema de debate que ha estado creando discusiones entre los desarrolladores durante más de una década. En 2003, Martin Fowler acuñó la frase "Modelo de dominio anémico" (ADM) para describir esta separación de datos y funcionalidad. Él, y otros que están de acuerdo con él, argumentan que los "Modelos de dominio enriquecido" (mezcla de datos y funcionalidad) son "OO adecuados", y que el enfoque ADM es un antipatrón no OO.
Siempre ha habido quienes rechazan este argumento y ese lado del argumento se ha vuelto más fuerte y audaz en los últimos años con la adopción por parte de más desarrolladores de técnicas de desarrollo funcional. Ese enfoque fomenta activamente la separación de datos y preocupaciones de función. Los datos deben ser inmutables tanto como sea posible, por lo que la encapsulación del estado mutable se convierte en una preocupación. No es beneficioso adjuntar funciones directamente a esos datos en tales situaciones. Si es entonces "no OO" o no, no tiene absolutamente ningún interés para esa gente.
Independientemente de en qué lado de la cerca te sientes (me siento muy firmemente en el lado "Martin Fowler está hablando un montón de cosas viejas"), tu uso de métodos estáticos
printInfo
ydraw
está casi universalmente mal visto. Es difícil burlarse de los métodos estáticos al escribir pruebas unitarias. Por lo tanto, si tienen efectos secundarios (como imprimir o dibujar en una pantalla u otro dispositivo), no deberían ser estáticos, o deberían pasar la ubicación de salida como un parámetro.Entonces podrías tener una interfaz:
Y una implementación que se inyecta en el resto de su sistema en tiempo de ejecución (con otras implementaciones que se utilizan para las pruebas):
O agregue parámetros adicionales para eliminar los efectos secundarios de esos métodos:
fuente
DTO: objetos de transporte de datos
son útiles solo para eso. Si va a desviar datos entre programas o sistemas, los DTO son deseables, ya que proporcionan un subconjunto manejable de un objeto que solo se ocupa de la estructura y el formato de los datos. La ventaja es que no tiene que sincronizar las actualizaciones de métodos complejos en varios sistemas (siempre que los datos subyacentes no cambien).
El objetivo de OO es vincular los datos y el código que actúa sobre esos datos muy juntos. Separar un objeto lógico en clases separadas suele ser una mala idea.
fuente
Muchos patrones y principios de diseño entran en conflicto y, en última instancia, solo su criterio como buen programador lo ayudará a decidir qué patrones deben aplicarse a un problema específico.
En mi opinión, el principio Hacer lo más simple que posiblemente podría funcionar debería ganar por defecto a menos que tenga una buena razón para hacer que el diseño sea más complicado. Entonces, en su ejemplo específico, optaría por el primer diseño, que es un enfoque orientado a objetos más tradicional con datos y funciones juntas en la misma clase.
Aquí hay un par de preguntas que puede hacerse sobre la aplicación:
Si responde que sí a cualquiera de estas preguntas, podría obtener un diseño más complejo con DTO y clases funcionales separadas.
fuente
¿Es un buen patrón?
Probablemente obtendrá diferentes respuestas aquí porque las personas siguen diferentes escuelas de pensamiento. Entonces, incluso antes de comenzar: esta respuesta está de acuerdo con la programación orientada a objetos como la describe Robert C. Martin en el libro "Código limpio".
"Verdadero" POO
Que yo sepa, hay 2 interpretaciones de OOP. Para la mayoría de las personas, se reduce a "un objeto es una clase".
Sin embargo, la otra escuela de pensamiento me pareció más útil: "Un objeto es algo que hace algo". Si está trabajando con clases, cada clase sería una de dos cosas: un " titular de datos " o un objeto . Los titulares de datos tienen datos (duh), los objetos hacen cosas. ¡Eso no significa que un titular de datos no pueda tener métodos! Piense en un
list
: Alist
realmente no hace nada, pero tiene métodos para imponer la forma en que almacena los datos .Titulares de datos
Usted
Cat
es un excelente ejemplo de un titular de datos. Claro, fuera de su programa, un gato es algo que hace algo , pero dentro de su programa, unCat
es una Cadenaname
, un intweight
y una Imagenimage
.¡Que los titulares de datos no hagan nada tampoco significa que no contengan lógica empresarial! Si su
Cat
clase tiene un constructor que lo requierename
,weight
yimage
ha encapsulado con éxito la regla de negocio que cada gato tiene. Si puede cambiarweight
después de queCat
se haya creado una clase, esa es otra regla empresarial encapsulada. Estas son cosas muy básicas, pero eso solo significa que es muy importante no equivocarse, y de esta manera, se asegura de que sea imposible equivocarse.Objetos
Los objetos hacen algo. Si desea objetos Clean *, podemos cambiarlo a "Los objetos hacen una cosa".
Imprimir la información del gato es el trabajo de un objeto. Por ejemplo, podría llamar a este objeto "
CatInfoLogger
", con un método públicoLog(Cat)
. Eso es todo lo que necesitamos saber desde el exterior: este objeto registra la información de un gato y para hacerlo, necesita unCat
.En el interior, este objeto tendría referencias a lo que sea necesario para cumplir con su responsabilidad única de registrar a los gatos. En este caso, eso es solo una referencia para
System
forSystem.out.println
, pero en la mayoría de los casos, serán referencias privadas a otros objetos. Si la lógica para formatear la salida antes de imprimirla se vuelve demasiado compleja,CatInfoLogger
solo puede obtener una referencia a un nuevo objetoCatInfoFormatter
. Si más adelante, cada registro también debe escribirse en un archivo, puede agregar unCatToFileLogger
objeto que lo haga.Titulares de datos vs objetos - resumen
Ahora, ¿por qué harías todo eso? Porque ahora cambiar una clase cambia solo una cosa ( la forma en que se registra un gato en el ejemplo). Si está cambiando un objeto, solo está cambiando la forma en que se realiza una determinada acción; si cambia un titular de datos, solo cambia qué datos se retienen y / o de qué manera se conservan.
Cambiar los datos puede significar que la forma en que se hace algo también tiene que cambiar, pero ese cambio no sucederá hasta que lo haga usted mismo cambiando el objeto responsable.
¿Exceso?
Esta solución puede parecer un poco exagerada. Déjame ser sincero: lo es . Si todo su programa es tan grande como el ejemplo, no haga nada de lo anterior, solo elija cualquiera de las formas propuestas con un lanzamiento de moneda. No hay peligro de confundir a otros códigos si no es ningún otro código.
Sin embargo, cualquier software "serio" (= más que un script o 2) es probablemente lo suficientemente grande como para beneficiarse de una estructura como esta.
Responder
Convertir "la mayoría de las clases" (sin ninguna calificación adicional) en DTO / titulares de datos, no es un anti patrón, porque en realidad no es ningún patrón, es más o menos aleatorio.
Sin embargo, mantener los datos y los objetos separados se considera una buena manera de mantener limpio su código *. A veces solo necesitará un DTO plano y "anémico" y eso es todo. Otras veces, necesita métodos para imponer la forma en que se almacenan los datos. Simplemente no ponga la funcionalidad de su programa con los datos.
* Eso es "Limpio" con una C mayúscula como el título del libro, porque, recuerda el primer párrafo, se trata de una escuela de pensamiento, mientras que también hay otras.
fuente