Soy bastante nuevo en los principios de diseño SOLID . Entiendo su causa y beneficios, pero no logro aplicarlos a un proyecto más pequeño que quiero refactorizar como ejercicio práctico para usar los principios SOLID. Sé que no hay necesidad de cambiar una aplicación que funcione perfectamente, pero quiero refactorizarla de todos modos para ganar experiencia en diseño para futuros proyectos.
La aplicación tiene la siguiente tarea (en realidad, mucho más que eso, pero vamos a simplificarla): tiene que leer un archivo XML que contenga definiciones de tabla / columna / vista de base de datos, etc. y crear un archivo SQL que pueda usarse para crear un esquema de base de datos ORACLE.
(Nota: absténgase de discutir por qué lo necesito o por qué no uso XSLT, etc., hay razones, pero están fuera de tema).
Como comienzo, elegí mirar solo las Tablas y Restricciones. Si ignora las columnas, puede indicarlo de la siguiente manera:
Una restricción es parte de una tabla (o más precisamente, parte de una instrucción CREATE TABLE), y una restricción también puede hacer referencia a otra tabla.
Primero, explicaré cómo se ve la aplicación en este momento (sin aplicar SOLID):
Por el momento, la aplicación tiene una clase "Tabla" que contiene una lista de punteros a Restricciones propiedad de la tabla y una lista de punteros a Restricciones que hacen referencia a esta tabla. Siempre que se establezca una conexión, también se establecerá la conexión hacia atrás. La tabla tiene un método createStatement () que a su vez llama a la función createStatement () de cada restricción. Dicho método usará las conexiones a la tabla del propietario y la tabla referenciada para recuperar sus nombres.
Obviamente, esto no se aplica a SOLID en absoluto. Por ejemplo, hay dependencias circulares, que hinchan el código en términos de los métodos "agregar" / "eliminar" necesarios y algunos destructores de objetos grandes.
Entonces hay un par de preguntas:
- ¿Debo resolver las dependencias circulares usando la inyección de dependencias? Si es así, supongo que la restricción debería recibir la tabla del propietario (y opcionalmente la referenciada) en su constructor. Pero, ¿cómo podría pasar sobre la lista de restricciones para una sola tabla entonces?
- Si la clase Tabla almacena el estado de sí misma (por ejemplo, el nombre de la tabla, el comentario de la tabla, etc.) y los enlaces a Restricciones, ¿estas son una o dos "responsabilidades", pensando en el Principio de responsabilidad única?
- En el caso 2. es correcto, ¿debería crear una nueva clase en la capa comercial lógica que gestiona los enlaces? Si es así, 1. obviamente ya no sería relevante.
- ¿Deberían los métodos "createStatement" formar parte de las clases Tabla / Restricción o también debería eliminarlos? Si es así, ¿a dónde? ¿Una clase de Administrador por cada clase de almacenamiento de datos (es decir, Tabla, Restricción, ...)? ¿O más bien crear una clase de administrador por enlace (similar a 3.)?
Cada vez que trato de responder una de estas preguntas, me encuentro corriendo en círculos en alguna parte.
Obviamente, el problema se vuelve mucho más complejo si incluye columnas, índices, etc., pero si me ayudan con la simple tabla / restricción, tal vez pueda resolver el resto por mi cuenta.
fuente
Respuestas:
Puede comenzar desde un punto de vista diferente para aplicar el "Principio de responsabilidad única" aquí. Lo que nos ha mostrado es (más o menos) solo el modelo de datos de su aplicación. SRP aquí significa: asegúrese de que su modelo de datos sea responsable solo de mantener los datos, ni más ni menos.
Entonces, cuando va a leer su archivo XML, crear un modelo de datos a partir de él y escribir SQL, lo que no debe hacer es implementar nada en su
Table
clase que sea específico de XML o SQL. Desea que su flujo de datos se vea así:Por lo que el único lugar donde el código XML específico se debe colocar es una clase llamada, por ejemplo,
Read_XML
. El único lugar para el código específico de SQL debería ser una clase comoWrite_SQL
. Por supuesto, tal vez vas a dividir esas 2 tareas en más subtareas (y dividir tus clases en múltiples clases de administrador), pero tu "modelo de datos" no debería asumir ninguna responsabilidad de esa capa. Por lo tanto, no agreguecreateStatement
a ninguna de sus clases de modelo de datos, ya que esto le da a su modelo de datos la responsabilidad del SQL.No veo ningún problema cuando estás describiendo que una tabla es responsable de mantener todas sus partes (nombre, columnas, comentarios, restricciones ...), esa es la idea detrás de un modelo de datos. Pero usted describió que "Tabla" también es responsable de la administración de memoria de algunas de sus partes. Ese es un problema específico de C ++, que no enfrentaría tan fácilmente en lenguajes como Java o C #. La forma en C ++ de deshacerse de esa responsabilidad es utilizando punteros inteligentes, delegando la propiedad a una capa diferente (por ejemplo, la biblioteca de impulso o su propia capa de puntero "inteligente"). Pero tenga cuidado, sus dependencias cíclicas pueden "irritar" algunas implementaciones de punteros inteligentes.
Algo más sobre SOLID: aquí hay un buen artículo
http://cre8ivethought.com/blog/2011/08/23/software-development-is-not-a-jenga-game
explicando SOLID con un pequeño ejemplo. Intentemos aplicar eso a su caso:
necesitará no solo clases
Read_XML
yWrite_SQL
, sino también una tercera clase que gestiona la interacción de esas 2 clases. Vamos a llamarlo aConversionManager
.La aplicación del principio DI podría significar aquí: ConversionManager no debería crear instancias de
Read_XML
yWrite_SQL
por sí mismo. En cambio, esos objetos se pueden inyectar a través del constructor. Y el constructor debería tener una firma como estaConversionManager(IDataModelReader reader, IDataModelWriter writer)
donde
IDataModelReader
es una interfaz de la queRead_XML
hereda, yIDataModelWriter
lo mismo paraWrite_SQL
. EstoConversionManager
abre las extensiones (proporciona muy fácilmente diferentes lectores o escritores) sin tener que cambiarlo, por lo que tenemos un ejemplo para el principio Abierto / Cerrado. Piense en lo que tendrá que cambiar cuando desee admitir a otro proveedor de bases de datos; en realidad, no tiene que cambiar nada en su modelo de datos, solo proporcione otro Escritor SQL.fuente
Bueno, debe aplicar la S de SÓLIDO en este caso.
Una tabla contiene todas las restricciones definidas en ella. Una restricción contiene todas las tablas a las que hace referencia. Modelo simple y llano.
En lo que te aferras a eso, es en la capacidad de realizar búsquedas inversas, es decir, averiguar por qué restricciones se hace referencia a alguna tabla.
Entonces, lo que realmente quieres es un servicio de indexación. Esa es una tarea completamente diferente y, por lo tanto, debe ser realizada por un objeto diferente.
Para desglosarlo en una versión muy simplificada:
En cuanto a la implementación del índice, hay 3 formas de hacerlo:
getContraintsReferencing
método realmente podría rastrear todoDatabase
paraTable
instancias y rastrear susConstraint
s para obtener el resultado. Dependiendo de lo costoso que sea y con qué frecuencia lo necesite, puede ser una opción.Table
eConstraint
instancias, cuando cambian. Una solución un poco más simple seríaIndex
crear un "índice de instantáneas" del conjuntoDatabase
para trabajar, que luego descartaría. Por supuesto, eso solo es posible si su aplicación hace una gran distinción entre "tiempo de modelado" y "tiempo de consulta". Si es bastante probable que haga esos dos al mismo tiempo, entonces esto no es viable.fuente
La cura para las dependencias circulares es jurar que nunca, nunca las creará. Encuentro que la prueba de codificación primero es un fuerte elemento disuasorio.
De todos modos, las dependencias circulares siempre se pueden romper introduciendo una clase base abstracta. Esto es típico para las representaciones gráficas. Aquí las tablas son nodos y las restricciones de clave externa son aristas. Cree una clase de tabla abstracta y una clase de restricción abstracta y quizás una clase de columna abstracta. Entonces todas las implementaciones pueden depender de las clases abstractas. Puede que esta no sea la mejor representación posible, pero es una mejora sobre las clases acopladas mutuamente.
Pero, como sospecha, la mejor solución a este problema puede no requerir ningún seguimiento de las relaciones de objeto. Si solo desea traducir XML a SQL, entonces no necesita una representación en memoria del gráfico de restricción. El gráfico de restricción sería bueno si quisieras ejecutar algoritmos gráficos, pero no lo mencionaste, así que supondré que no es un requisito. Solo necesita una lista de tablas y una lista de restricciones y un visitante para cada dialecto SQL que desee admitir. Genere las tablas, luego genere las restricciones externas a las tablas. Hasta que los requisitos cambiaran, no tendría ningún problema al acoplar el generador SQL al DOM XML. Ahorra mañana para mañana.
fuente