Tengo una aplicación web No creo que la tecnología sea importante. La estructura es una aplicación de N niveles, que se muestra en la imagen de la izquierda. Hay 3 capas
UI (patrón MVC), capa de lógica de negocios (BLL) y capa de acceso a datos (DAL)
El problema que tengo es que mi BLL es enorme, ya que tiene la lógica y las rutas a través de la llamada de eventos de la aplicación.
Un flujo típico a través de la aplicación podría ser:
Evento disparado en la interfaz de usuario, atravesar un método en el BLL, realizar la lógica (posiblemente en varias partes del BLL), eventualmente al DAL, volver al BLL (donde probablemente haya más lógica) y luego devolver algún valor a la UI.
El BLL en este ejemplo está muy ocupado y estoy pensando cómo dividir esto. También tengo la lógica y los objetos combinados que no me gustan.
La versión de la derecha es mi esfuerzo.
La lógica sigue siendo cómo fluye la aplicación entre UI y DAL, pero es probable que no haya propiedades ... Solo métodos (la mayoría de las clases en esta capa podrían ser estáticas ya que no almacenan ningún estado). La capa Poco es donde existen clases que tienen propiedades (como una clase Persona donde habría nombre, edad, altura, etc.). Estos no tendrían nada que ver con el flujo de la aplicación, solo almacenan estado.
El flujo podría ser:
Incluso se activa desde la interfaz de usuario y pasa algunos datos al controlador de capa de interfaz de usuario (MVC). Esto traduce los datos en bruto y los convierte en el modelo poco. Luego, el modelo poco se pasa a la capa Lógica (que era el BLL) y, finalmente, a la capa de consulta de comandos, potencialmente manipulada en el camino. La capa de consulta de Comando convierte el POCO en un objeto de base de datos (que son casi lo mismo, pero uno está diseñado para la persistencia, el otro para el front-end). El elemento se almacena y se devuelve un objeto de base de datos a la capa Consulta de comando. Luego se convierte en un POCO, donde vuelve a la capa Lógica, potencialmente procesada más adelante y luego finalmente, de vuelta a la IU
La lógica y las interfaces compartidas es donde podemos tener datos persistentes, como MaxNumberOf_X y TotalAllowed_X y todas las interfaces.
Tanto la lógica / interfaces compartidas como DAL son la "base" de la arquitectura. Estos no saben nada del mundo exterior.
Todo sabe poco más que la lógica / interfaces compartidas y DAL.
El flujo sigue siendo muy similar al primer ejemplo, pero ha hecho que cada capa sea más responsable de 1 cosa (ya sea estado, flujo o cualquier otra cosa) ... pero ¿estoy rompiendo la POO con este enfoque?
Un ejemplo para demostrar la lógica y Poco podría ser:
public class LogicClass
{
private ICommandQueryObject cmdQuery;
public PocoA Method1(PocoB pocoB)
{
return cmdQuery.Save(pocoB);
}
/*This has no state objects, only ways to communicate with other
layers such as the cmdQuery. Everything else is just function
calls to allow flow via the program */
public PocoA Method2(PocoB pocoB)
{
pocoB.UpdateState("world");
return Method1(pocoB);
}
}
public struct PocoX
{
public string DataA {get;set;}
public int DataB {get;set;}
public int DataC {get;set;}
/*This simply returns something that is part of this class.
Everything is self-contained to this class. It doesn't call
trying to directly communicate with databases etc*/
public int GetValue()
{
return DataB * DataC;
}
/*This simply sets something that is part of this class.
Everything is self-contained to this class.
It doesn't call trying to directly communicate with databases etc*/
public void UpdateState(string input)
{
DataA += input;
}
}
fuente
Respuestas:
Sí, es muy probable que rompas los conceptos básicos de OOP. Sin embargo, no se sienta mal, la gente hace esto todo el tiempo, no significa que su arquitectura esté "equivocada". Diría que probablemente sea menos mantenible que un diseño OO adecuado, pero esto es bastante subjetivo y no es su pregunta de todos modos. ( Aquí hay un artículo mío criticando la arquitectura n-tier en general).
Razonamiento : El concepto más básico de OOP es que los datos y la lógica forman una sola unidad (un objeto). Aunque esta es una declaración muy simple y mecánica, aun así, no se sigue realmente en su diseño (si lo entiendo correctamente). Está separando claramente la mayoría de los datos de la mayoría de la lógica. Tener métodos sin estado (como estáticos), por ejemplo, se llama "procedimientos" y, por lo general, son antitéticos a la POO.
Por supuesto, siempre hay excepciones, pero este diseño viola estas cosas como regla.
Nuevamente, me gustaría enfatizar que "viola OOP"! = "Incorrecto", por lo que esto no es necesariamente un juicio de valor. Todo depende de las limitaciones de su arquitectura, casos de uso de mantenibilidad, requisitos, etc.
fuente
Uno de los principios centrales de la programación funcional son las funciones puras.
Uno de los principios centrales de la Programación Orientada a Objetos es unir las funciones con los datos sobre los que actúan.
Ambos principios básicos desaparecen cuando su aplicación tiene que comunicarse con el mundo exterior. De hecho, solo puede ser fiel a estos ideales en un espacio especialmente preparado en su sistema. No todas las líneas de su código deben cumplir con estos ideales. Pero si ninguna línea de su código cumple con estos ideales, no puede afirmar que está usando OOP o FP.
Por lo tanto, está bien tener solo "objetos" de datos a los que les da vueltas porque los necesita para cruzar un límite que simplemente no puede refactorizar para mover el código interesado. Solo sé que eso no es POO. Esa es la realidad OOP es cuando, una vez dentro de ese límite, reúnes toda la lógica que actúa sobre esos datos en un solo lugar.
No es que tengas que hacer eso tampoco. OOP no es todo para todas las personas. Es lo que es. Simplemente no afirmes que algo sigue a OOP cuando no lo hace o vas a confundir a las personas que intentan mantener tu código.
Sus POCO parecen tener lógica de negocios en ellos, así que no me preocuparía demasiado por estar anémico. Lo que me preocupa es que todos parecen mutables. Recuerde que los captadores y establecedores no proporcionan encapsulación real. Si su POCO se dirige a ese límite, entonces está bien. Solo comprenda que esto no le brinda todos los beneficios de un objeto OOP encapsulado real. Algunos llaman a esto un objeto de transferencia de datos o DTO.
Un truco que he usado con éxito es crear objetos de POO que coman DTO. Yo uso el DTO como un objeto de parámetro . Mi constructor lee el estado de él (leído como copia defensiva ) y lo arroja a un lado. Ahora tengo una versión totalmente encapsulada e inmutable del DTO. Todos los métodos relacionados con estos datos se pueden mover aquí siempre que estén en este lado de ese límite.
No proporciono getters o setters. Sigo diciendo, no preguntes . Llamas a mis métodos y hacen lo que hay que hacer. Es probable que ni siquiera te digan lo que hicieron. Solo lo hacen.
Ahora, eventualmente, algo, en algún lugar, se encontrará con otro límite y todo esto se desmorona nuevamente. Esta bien. Gira otro DTO y tíralo sobre la pared.
Esta es la esencia de lo que se trata la arquitectura de puertos y adaptadores. Lo he estado leyendo desde una perspectiva funcional . Quizás también te interese.
fuente
Si leo su explicación correctamente, sus objetos se parecen un poco a esto: (complicado sin contexto)
En eso sus clases de Poco contienen solo datos y sus clases de Lógica contienen los métodos que actúan sobre esos datos; sí, has roto los principios de "Classic OOP"
De nuevo, es difícil distinguirlo de su descripción generalizada, pero me arriesgaría a que lo que ha escrito se pueda clasificar como Modelo de dominio anémico.
No creo que este sea un enfoque particularmente malo, ni, si consideras tus Poco como estructuras, nescarly rompe OOP en el sentido más específico. En eso sus objetos son ahora las clases lógicas. De hecho, si hace que su Pocos sea inmutable, el diseño podría considerarse bastante funcional.
Sin embargo, cuando hace referencia a Shared Logic, Pocos que son casi iguales y estáticos, empiezo a preocuparme por los detalles de su diseño.
fuente
Un problema potencial que vi en su diseño (y es muy común): algunos de los códigos "OO" absolutamente peores que he encontrado fueron causados por una arquitectura que separó los objetos "Data" de los objetos "Code". ¡Esto es algo de pesadilla! El problema es que en todas partes de su código de negocio cuando desea acceder a sus objetos de datos, TIENE QUE codificarlo allí mismo en línea (no es necesario, podría crear una clase de utilidad u otra función para manejarlo, pero esto es lo que He visto suceder repetidamente con el tiempo).
El código de acceso / actualización generalmente no se recopila, por lo que terminas con una funcionalidad duplicada en todas partes.
Por otro lado, esos objetos de datos son útiles, por ejemplo, como persistencia de la base de datos. He intentado tres soluciones:
Copiar valores dentro y fuera de objetos "reales" y tirar su objeto de datos es tedioso (pero puede ser una solución válida si quiere seguir ese camino).
Agregar métodos de disputa de datos a los objetos de datos puede funcionar, pero puede convertirse en un gran objeto de datos desordenado que está haciendo más de una cosa. También puede hacer que la encapsulación sea más difícil ya que muchos mecanismos de persistencia quieren accesores públicos ... No me ha encantado cuando lo hice, pero es una solución válida
La solución que mejor funcionó para mí es el concepto de una clase "Wrapper" que encapsula la clase "Data" y contiene toda la funcionalidad de disputa de datos, entonces no expongo la clase de datos en absoluto (ni siquiera los setters y getters a menos que sean absolutamente necesarios). Esto elimina la tentación de manipular el objeto directamente y te obliga a agregar funcionalidades compartidas al contenedor.
La otra ventaja es que puede asegurarse de que su clase de datos esté siempre en un estado válido. Aquí hay un ejemplo rápido de psuedocode:
Tenga en cuenta que no tiene el control de edad extendido a lo largo de su código en diferentes áreas y también que no está tentado a usarlo porque ni siquiera puede averiguar cuál es el cumpleaños (a menos que lo necesite para otra cosa, en en cuyo caso puedes agregarlo).
Tiendo a no solo extender el objeto de datos porque pierdes esta encapsulación y la garantía de seguridad, en ese punto también podrías agregar los métodos a la clase de datos.
De esa manera, su lógica de negocios no tiene un montón de basura / iteradores de acceso a datos distribuidos por todo, se vuelve mucho más legible y menos redundante. También recomiendo adquirir el hábito de envolver siempre las colecciones por la misma razón: mantener las construcciones de bucle / búsqueda fuera de la lógica de su negocio y asegurarse de que siempre estén en buen estado.
fuente
Nunca cambie su código porque piensa o alguien le dice que no es esto o no eso. Cambie su código si le da problemas y descubrió una manera de evitar estos problemas sin crear otros.
Entonces, aparte de que no le gustan las cosas, desea invertir mucho tiempo para hacer un cambio. Escribe los problemas que tienes ahora. Escriba cómo su nuevo diseño resolvería los problemas. Calcule el valor de la mejora y el costo de realizar sus cambios. Luego, y esto es lo más importante, asegúrese de tener tiempo para completar esos cambios, o terminará la mitad en este estado, la mitad en ese estado, y esa es la peor situación posible. (Una vez trabajé en un proyecto con 13 tipos diferentes de cadenas y tres esfuerzos identificables a medias para estandarizar un tipo)
fuente
La categoría "OOP" es mucho más grande y más abstracta que lo que está describiendo. No le importa todo esto. Se preocupa por la responsabilidad clara, la cohesión, el acoplamiento. Entonces, en el nivel que está preguntando, no tiene mucho sentido preguntar sobre la "práctica de OOPS".
Dicho esto, a tu ejemplo:
Me parece que hay un malentendido sobre lo que significa MVC. Está llamando a su UI "MVC", por separado de su lógica empresarial y control "backend". Pero para mí, MVC incluye toda la aplicación web:
Aquí hay algunos supuestos básicos extremadamente importantes:
Importante: la interfaz de usuario es parte de MVC. No al revés (como en su diagrama). Si acepta eso, entonces los modelos gordos son bastante buenos, siempre que no contengan cosas que no deberían.
Tenga en cuenta que "modelos gordos" significa que toda la lógica de negocios está en la categoría Modelo (paquete, módulo, cualquiera que sea el nombre en el idioma de su elección). Obviamente, las clases individuales deben estar estructuradas en OOP de una buena manera de acuerdo con las pautas de codificación que usted mismo le dé (es decir, algunas líneas de código máximas por clase o por método, etc.).
También tenga en cuenta que la forma en que se implementa la capa de datos tiene consecuencias muy importantes; especialmente si la capa del modelo puede funcionar sin una capa de datos (por ejemplo, para pruebas unitarias o para bases de datos en memoria baratas en la computadora portátil del desarrollador en lugar de las caras bases de datos Oracle o lo que sea que tenga). Pero esto realmente es un detalle de implementación en el nivel de arquitectura que estamos viendo en este momento. Obviamente, aquí todavía desea tener una separación, es decir, no me gustaría ver código que tenga una lógica de dominio pura directamente entrelazada con el acceso a datos, acoplando esto intensamente. Un tema para otra pregunta.
Para volver a su pregunta: me parece que hay una gran superposición entre su nueva arquitectura y el esquema MVC que he descrito, por lo que no está en un camino completamente equivocado, pero parece que está reinventando algunas cosas, o usarlo porque su entorno / bibliotecas de programación actual lo sugiere. Difícil de decir para mí. Por lo tanto, no puedo darle una respuesta exacta sobre si lo que pretende es particularmente bueno o malo. Puede averiguarlo verificando si cada "cosa" tiene exactamente una clase responsable de ello; si todo es altamente cohesivo y poco acoplado. Eso le da una buena indicación y, en mi opinión, es suficiente para un buen diseño de OOP (o un buen punto de referencia del mismo, si lo desea).
fuente