Ir al contenido principal

Principios SOLID. La inversión de dependencias

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:

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();
}

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

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.

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:

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

Entradas populares de este blog

Test 3: ¿Son eficientes los ORMs?

Bienvenidos amigos. Me complace anunciar que por fin estrenamos la categoría " SQL Server Tips ", y lo hacemos por todo lo alto, entrando de lleno en un aspecto altamente polémico entre programadores. ¿Es eficiente un ORM en los accesos a datos? Ya conocen la filosofía de nuestros Tests, no vamos a teorizar demasiado, pero sí una pequeña base va a ser necesaria para conseguir una buena respuesta a nuestra pregunta. He leído un interesante  artículo de nuestros súper amigos de Deloitte ( cuando usar ORM ) argumentando que el uso o no de un ORM hay que decidirlo en relación a la complejidad de nuestro modelo de datos, y al rendimiento que requeriremos en nuestras soluciones, pero, ¿cuándo no deseamos el mejor rendimiento para nuestro software? Lo cierto es que, como ya hemos visto, el ORM facilita mucho las cosas, aporta claridad al código, de eso no cabe duda, pero, ¿es eficiente? He ahí la cuestión. Sobre este asunto vamos a poner a funcionar nuestros apreciados Test. C

Proyecto GEOW. Implementando el patrón CQRS. 2ª parte

En el anterior post ( Proyecto GEOW. Implementando el patrón CQRS ) nos adentramos en el funcionamiento del proyecto GEOW que nos va a servir de base para implementar un patrón arquitectónico, CQRS , pensado para dar respuesta a sistemas con alta exigencia de lecturas y escrituras simultáneas. Para ello hemos creado una interfaz gráfica con una serie de figuras geométricas en movimiento. Ahora vamos a ir a la parte de EL DATO. Cada vez que uno de estos cuadrados cambia de posición envía una trama con sus propias características, y sus nuevas coordenadas. Cada uno de los cuadrados realiza un movimiento cada 300 milisegundos, y he llegado a probar con hasta unas 700 figuras. En estos niveles el software empieza a sufrir, pero más la parte gráfica. Aparentemente el sistema de grabación de coordenadas se mantiene en buena forma. Vamos a ver, precisamente, este sistema de grabación: Grabando lotes de coordenadas en BBDD En el objeto PointObj que representa cada una de las figuras, en el

Primeros pasos en Enigma Software Labs

En pocas palabras: vamos a probar y estresar distintas soluciones de software para un mismo problema, en busca de realidades matemáticas. Para ello, lo primero que necesitamos en averiguar el modo de publicar código aquí en Blogger . Para ello voy a usar un sistema súper sencillo y eficiente. Ahí va un ejemplo de código C# private void frmMonitor_Load ( object sender, EventArgs e) { TreeNode _tr = new TreeNode( "SQL Server Tips" ); TreeNode _tr2 = new TreeNode( "C# Code" ); treeCatalogo.Nodes.Add(_tr); treeCatalogo.Nodes.Add(_tr2); //splitContainer.Panel1.Focus(); //lstMonitor.Focus(); } Hilarante ¿verdad?   hilite.me  lo hace posible. Bien, paso 2, fácil y sencillo: vamos a crear una plataforma de testeo para C# en una bonita solución que compartiré en el repositorio GIT, para disfrute de todos. Enigma Software - ZM LABS Como posteriormente me dive