Hola de nuevo mis estimadísimos programadores.
Hoy abordaremos un principio arquitectónico de gran relevancia, el principio de inversión de dependencias. Forma parte de los denominados principios SOLID acerca de los cuales ya les comenté en alguna publicación anterior.
De entre toda la documentación que pueden encontrar acerca de estos principios, les voy a reseñar una web que los explica con gran claridad y bonitos ejemplos: Principios SOLID.
En este artículo vamos a adaptar, al menos parte de nuestra plataforma, al quinto de los principios, que reza así:
1. Las clases de alto nivel no deberían depender de las clases de bajo nivel. Ambas deberían depender de las abstracciones.
2. Las abstracciones no deberían depender de los detalles. Los detalles deberían depender de las abstracciones.
La principal finalidad de la adopción de este principio de diseño es evitar los acoplamientos. ¿Qué significa esto? Bueno, supongamos una clase cualquiera, que es instanciada dentro de otra clase con la fórmula tradicional var obj = new myclass(). La clase que invoca myclass queda fuertemente acoplada a la clase donde se instancia.
Un ejemplo típico, para que Uds. terminen de entender a la perfección el acoplamiento: tenemos una capa de datos que accede a un servidor SQL Server -como es nuestro caso-, y repentinamente deseamos migrar a un sistema de consolidación distinto. Estaríamos forzados a reescribir código no solo en la capa de datos, sino tal vez también en la capa de negocio que la invoca. La idea es que la capa de negocio no conozca de la capa de datos más que una abstracción, que pueda después implementarse de formas distintas.
Dos elementos nos ayudarán a cumplir con el principio de inversión de dependencia:
1. Las interfaces. Potente herramienta que establece, digamos, "un contrato" de qué propiedades y métodos va a implementa una clase. Es lo que consideramos una abstracción. Para más información: Interfaces en .net. Tus nuevas mejores amigas
2. La inyección de dependencias: se trata de un patrón de diseño que nos evita la necesidad de construir una instancia de una clase dentro de otra, como enseguida vamos a ilustrar. Un ejemplo claro y lleno de acción aquí: Ejemplo de inyección de dependencias
Otra ventaja muy relevante de la adopción de este principio en nuestros desarrollos es la capacidad que nos brinda de implementar los test unitarios, aspecto éste que también desearía abordar en próximas publicaciones.
Sin más preámbulos, vamos a proceder a mancharnos las manos:
Para hacer las mediciones, implementamos una función que realizaría una tarea más o menos costosa, el cálculo de N elementos de la serie Fibonacci. ¿Recuerdan?
Les prometí además que veríamos alguna implementación más elegante para resolver esta tarea, y sepan amigos que yo casi siempre cumplo mis promesas.
Ambos códigos hacen exactamente lo mismo. Y sepan, a modo de curiosidad, que hay infinidad de fórmulas distintas de implementar el cálculo de esta hermosa serie: Stackoverflow: create fibonacci series using lambda operator
Hoy abordaremos un principio arquitectónico de gran relevancia, el principio de inversión de dependencias. Forma parte de los denominados principios SOLID acerca de los cuales ya les comenté en alguna publicación anterior.
De entre toda la documentación que pueden encontrar acerca de estos principios, les voy a reseñar una web que los explica con gran claridad y bonitos ejemplos: Principios SOLID.
En este artículo vamos a adaptar, al menos parte de nuestra plataforma, al quinto de los principios, que reza así:
1. Las clases de alto nivel no deberían depender de las clases de bajo nivel. Ambas deberían depender de las abstracciones.
2. Las abstracciones no deberían depender de los detalles. Los detalles deberían depender de las abstracciones.
La principal finalidad de la adopción de este principio de diseño es evitar los acoplamientos. ¿Qué significa esto? Bueno, supongamos una clase cualquiera, que es instanciada dentro de otra clase con la fórmula tradicional var obj = new myclass(). La clase que invoca myclass queda fuertemente acoplada a la clase donde se instancia.
Un ejemplo típico, para que Uds. terminen de entender a la perfección el acoplamiento: tenemos una capa de datos que accede a un servidor SQL Server -como es nuestro caso-, y repentinamente deseamos migrar a un sistema de consolidación distinto. Estaríamos forzados a reescribir código no solo en la capa de datos, sino tal vez también en la capa de negocio que la invoca. La idea es que la capa de negocio no conozca de la capa de datos más que una abstracción, que pueda después implementarse de formas distintas.
Dos elementos nos ayudarán a cumplir con el principio de inversión de dependencia:
1. Las interfaces. Potente herramienta que establece, digamos, "un contrato" de qué propiedades y métodos va a implementa una clase. Es lo que consideramos una abstracción. Para más información: Interfaces en .net. Tus nuevas mejores amigas
2. La inyección de dependencias: se trata de un patrón de diseño que nos evita la necesidad de construir una instancia de una clase dentro de otra, como enseguida vamos a ilustrar. Un ejemplo claro y lleno de acción aquí: Ejemplo de inyección de dependencias
Otra ventaja muy relevante de la adopción de este principio en nuestros desarrollos es la capacidad que nos brinda de implementar los test unitarios, aspecto éste que también desearía abordar en próximas publicaciones.
Sin más preámbulos, vamos a proceder a mancharnos las manos:
Creando abstracciones e inyectando dependencias:
Vamos a repasar un momento nuestro primer test, Multithreading Vs Singlethreading.Para hacer las mediciones, implementamos una función que realizaría una tarea más o menos costosa, el cálculo de N elementos de la serie Fibonacci. ¿Recuerdan?
public List<ulong> CalcFibo(int numelements)
{ List<ulong> lstelements = new List<ulong>(); int cont = 0; ulong anterior = 0; ulong anterioranterior = 0; while (cont < numelements) { if (cont == 0) { lstelements.Add(0); anterioranterior = 0; } else if (cont == 1) { lstelements.Add(1); anterior = 1; } else { ulong newelem = anterioranterior + anterior; anterioranterior = anterior; anterior = newelem; lstelements.Add(newelem); } cont++; } return lstelements; }
Les prometí además que veríamos alguna implementación más elegante para resolver esta tarea, y sepan amigos que yo casi siempre cumplo mis promesas.
Vamos a desarrollar esta nueva implementación prometida, no en un nuevo método dentro de la misma clase, sino en una clase alternativa, que nos ayudará a entender la introducción de abstracciones o interfaces.
Lo prometido es deuda:
public List<ulong> CalcFibo(int numelements)
{ List<ulong> fibonacciNumbers = new List<ulong>(); Enumerable.Range(0, numelements) .ToList() .ForEach(k => fibonacciNumbers.Add(k <= 1 ? 1 : fibonacciNumbers[k - 2] + fibonacciNumbers[k - 1])); return fibonacciNumbers.Take(numelements).ToList(); }
Vamos ahora a fijarnos desde dónde llamamos al método CalcFibo de la clase fibo. Para ello pulsamos botón derecho sobre la definición de la clase y seleccionamos "Buscar todas las referencias"
Encontramos 3 referencias. Las tres se encuentran en la clase test1_multithreading_vs_singlethreading, tal como era de esperar, y hay tres, una por cada testcase implementado en este test.
Bueno, lo primero que debemos hacer es crear una Interface, y nuestras dos clases fibo, y fibo_linq implementarán esta interface, a la que llamaremos IFibo en un gran alarde de originalidad.
A continuación en el constructor de la clase TEST1 admitiremos un parámetro de entrada que hará referencia a esta interface:
public interface IFibo { List<ulong> CalcFibo(int numelements); } public class fibo_linq : contracts.IFibo { //primera implementación } public class fibo : contracts.IFibo { //segunda implementación }
A continuación en el constructor de la clase TEST1 admitiremos un parámetro de entrada que hará referencia a esta interface:
private static IFibo FiboCalc; public test1_multithreading_vs_singlethreading(test_info.test_functions_base p_testfunctions, IFibo p_FiboCalc) : base(p_testfunctions) { FiboCalc = p_FiboCalc; }
Y volviendo a nuestras tres referencias hacia la clase Fibo, dentro de la clase Test1, sustituiremos la llamada a la función CalcFibo como ahora les muestro:
Puesto que hemos cambiado la definición del constructor de la clase TEST1, en el lugar en el que la instanciamos debemos informar el valor del nuevo parámetro.
private static void CalcFibo1(int index) { try { //functions.fibo.CalcFibo(200); FiboCalc.CalcFibo(200); _lst_process_control[index].Estado = objects.process_control.enumEstadoProceso.Finalizado; } catch (Exception) { _lst_process_control[index].Estado = objects.process_control.enumEstadoProceso.Erroneo; } }
Puesto que hemos cambiado la definición del constructor de la clase TEST1, en el lugar en el que la instanciamos debemos informar el valor del nuevo parámetro.
Nótese que en este punto podemos pasar tanto la concreción fibo, como la concrecion fibo_linq, o tantas otras que quisiéramos desarrollar, siempre que implementen la interfaz IFibo, de modo que la clase TEST1 queda desacoplada de la concreción fibo, ya que solo conoce la abstracción IFibo.
res = new test1_multithreading_vs_singlethreading(_functions, new fibo());
Epílogo
En lo referente a los principios SOLID ya pueden Uds. imaginar que hay quien piensa que deben usarse a rajatabla sí o sí, y hay quien piensa que en según qué proyectos pueden representar una pérdida de tiempo y un aumento innecesario de la complejidad.
Como me gusta ofrecer varios puntos de vista para que mis lectores saquen sus propias conclusiones, aquí les dejo un artículo bastante interesante, que razona sobre el quinto principio que hoy hemos expuesto: Las ventajas de no usar inyección de dependencias
Estado de la plataforma i - Inversión de dependencias
Dentro del proyecto (que como ya saben está accesible para todos ustedes en GitHub), he incorporado la inversión de dependencias, por el momento, en la sección "functions" de nuestra lógica de negocio. Son aquellas funciones que usan los test, en concreto para las clases fibo, parte_horas y quijote.
Les invito a visitar los cambios en el cálculo del parte de horas anual, correspondiente al TEST3 ¿Son eficientes los ORM? ya que se trata, por su construcción, de un caso particular que no ha quedado completamente resuelto. He implementado una interfaz, he inyectado dependencias y aun así, las clases no han quedado plenamente desacopladas. Amigos, les propongo el reto de averiguar como es esto posible y, si encuentran la solución, no duden en compartirla.
Estado de la plataforma ii - Refactorización
Refactorizar un proyecto es una tarea que puede resultar ardua a veces (no ha sido el caso), pero necesaria cada cierto tiempo. En este caso me he dedicado a cambiar los nombres de las clases de dominio y los modelos EF para adoptar una nomenclatura estandarizada, aportando con ello una mayor claridad para el código. Ello me ha permitido también simplificar algunos nombres.
Vean a modo de ejemplo como ha quedado la configuración de los mapeos dentro del repositorio:
config_map = new MapperConfiguration(cfg => {
cfg.CreateMap<CategoriesModel, CategoriesDomain>().ReverseMap();
cfg.CreateMap<TestsModel, TestDomain>().ReverseMap();
cfg.CreateMap<TestCasesModel, TestCasesDomain>().ReverseMap();
cfg.CreateMap<ExecutionsModel, TestCaseExecutionsDomain>().ReverseMap();
});
Estado de la plataforma iii - Bugs identificados en UpdateDatabase
Para darle vidilla a la plataforma en GitHub e ir usando sus funcionalidades de control del ciclo de desarrollo, he informado dos bugs detectados en la actualización de la base de datos, que se ejecuta al arranque de la aplicación:
La segunda incidencia la tengo controlada y en breve la corregiré y subiré los cambios. La primera no tengo en principio idea de cómo poder arreglarla, de modo que les planteo a Uds. sabios generadores de código, el reto de ayudarme con sus propuestas de solución. ¡Anímense!
Próximos artículos
Ya para terminar les mantengo al tanto de las que serán las próximas publicaciones:- Incorporando el control de errores: habrán apreciado que hasta el momento nuestra plataforma cuenta con un control de errores precario, casi nulo. ¡Esto es un error que hay que controlar!
- ADO.NET vs Entity Framework 2ª parte: seguiremos adelante con nuestro ejemplo de partes de horas anuales, e implementaremos un informe de absentismo poniendo a prueba distintas técnicas de acceso y generación de información. ¡No se lo pierdan!
Salud amigos. Hasta pronto.
Comentarios
Publicar un comentario