Principios SOLID

[ Arquitectura  ]

Hola hoy vengo con los principios SOLID. Aunque es un tema que conozco, el pasado sábado asistí a la NET. Conf 2018 y varias personas no tenían conocimiento sobre ellos y eso no se puede quedar así.

Los presentamos:

  • Single Responsability
  • Open Closed
  • Liskow
  • Interface Segregation
  • Dependency Injection

Los detallamos:

  • Single Responsability

Este principio trata de realizar los métodos de una clase con una única función. Es decir, se responsabilizan solo de hacer una cosa, solo una y concreta.

  • Open Closed

Este principio lo que nos dice es que los métodos ya creados no pueden ser modificados y adaptados. En su lugar deben crearse nuevos métodos que realicen esa nueva funcionalidad.

De esta forma, las pruebas seguirán pasando, la aplicación seguirá funcionando y podremos darle más valor a nuestro código con funcionalidades nuevas. De ahí la extensión con el tag de open y cerrado a la modificación con el tag de closed para los métodos ya creados.

  • Liskow

Este principio habla de crear clases derivadas para que puedan ser usarse como una implementación base.

De esta forma, deberemos implementar clases derivadas sin volver a reimplementar lógica y asegurando que no rompemos nada al usar la clase base.

  • Interface Segretation

Este principio describe que, usando la S de los principios, debemos definir interfaces específicas a una lógica concreta. Es preferible tener muchas interfaces y poder agruparlas por herencia que no tener interfaces con muchos métodos.

Al definir interfaces, la facilidad de aprovechar la lógica es muy grande y nos facilita el acceso a los métodos de cualquier interfaz desde cualquier clase de nuestro programa.

  • Dependency Injection

La inyección de dependencias no sirve para desacoplar las clases. Aunque es muy difícil evitar el acoplamiento, con este principio el acoplamiento es a un contrato, que podemos substituir en cualquier momento, sin necesidad de conocer las implementaciones del contrato/interfaz.

También fue definido por Robert C. Martin. El objetivo de este principio conseguir desacoplar las clases. En todo diseño siempre debe existir un acoplamiento, pero hay que evitarlo en la medida de lo posible. Un sistema no acoplado no hace nada, pero un sistema altamente acoplado es muy difícil de mantener.

Y como siempre un ejemplo donde veremos como vamos aplicando los principios.

El problema:

using System;
namespace CalcToSolid
{
    public class NoSolidCalc
    {

        public int Operation(OperationType operationType, string operand1, string operand2)
        {

            var parsed1 =  int.TryParse(operand1, out int operandOneParsed);
            var parsed2 = int.TryParse(operand2, out int operandTwoParsed);

            if(parsed1 && parsed2){
                if (operationType.Equals(OperationType.DIV))
                {
                    return operandOneParsed / operandTwoParsed;
                }

                if (operationType.Equals(OperationType.MUL))
                {
                    return operandOneParsed * operandTwoParsed;
                }
                if (operationType.Equals(OperationType.SUB))
                {
                    return operandOneParsed - operandTwoParsed;
                }

                return operandOneParsed + operandTwoParsed;
            }
            return 0;
        }
    }

    public enum OperationType {
        SUM,
        SUB,
        DIV,
        MUL
    }
}

using System;

namespace CalcToSolid
{
    class Program
    {
        static void Main(string[] args)
        {
            NoSolidOperation();
        }

        private static void NoSolidOperation(){
            Console.WriteLine("Intro First Operand");
            var operand1 = Console.ReadLine();

            Console.WriteLine("Intro Second Operand");
            var operand2 = Console.ReadLine();

            Console.WriteLine("Intro Operation");
            Console.WriteLine("0: SUM");
            Console.WriteLine("1: SUB");
            Console.WriteLine("2: DIV");
            Console.WriteLine("3: MUL");
            var operation = (OperationType)Console.Read();

            var result = new NoSolidCalc().Operation(operation, operand1, operand2);
            Console.WriteLine(result);
        }
    }
}

La solución:


namespace CalcToSolid.SolidCalc.Interfaces
{
    public interface ICalc
    {
        int DoOperation(OperationType operation, int operand1, int operand2);
    }
}

namespace CalcToSolid.SolidCalc.Interfaces
{
    public interface IConverter
    {
        int ConvertStringToInt(string value);
        float ConvertStringToFloat(string value); // Open-Close
    }
}

namespace CalcToSolid.SolidCalc.Interfaces
{
    public interface IMenu
    {
        string PrintMenuOperand1();
        string PrintMenuOperand2();
        OperationType PrintMenuOperation();
    }
}

namespace CalcToSolid.SolidCalc.Interfaces
{
    public interface IOperation 
    {
        int Execute(int operand1, int operand2);
    }
}

using CalcToSolid.SolidCalc.Interfaces;

namespace CalcToSolid.SolidCalc.Logic
{
    public class Converter : IConverter // Open - Close
    {
        int IConverter.ConvertStringToInt(string value) //Single responsibility
        {
            var isConverted = int.TryParse(value, out int intValue);
            return isConverted ? intValue : 0;
        }

        public float ConvertStringToFloat(string value) //  Single responsibility
        {
            var isConverted = float.TryParse(value, out float floatValue);
            return isConverted ? floatValue : 0.0f;
        }
    }
}

using System;
using CalcToSolid.SolidCalc.Interfaces;

namespace CalcToSolid.SolidCalc.Logic
{
    public class Menu : IMenu
    {
        private readonly IConverter _converter = new Converter();
        public string PrintMenuOperand1() //Single responsibility)
        {
            Console.WriteLine("Intro First Operand");
            return Console.ReadLine();
        }

        public string PrintMenuOperand2() //Single responsibility)
        {
            Console.WriteLine("Intro Second Operand");
            return Console.ReadLine();
        }

        public OperationType PrintMenuOperation() //Single responsibility)
        {
            Console.WriteLine("Intro Operation");
            Console.WriteLine("0: SUM");
            Console.WriteLine("1: SUB");
            Console.WriteLine("2: DIV");
            Console.WriteLine("3: MUL");
            Console.WriteLine("4: NExpM");
            var operation = Console.ReadLine();

            return (OperationType) _converter.ConvertStringToInt(operation);
        }
    }
}

using CalcToSolid.SolidCalc.Interfaces;

namespace CalcToSolid.SolidCalc.Logic
{
    public class OperationDiv : IOperation
    {
        public int Execute(int operand1, int operand2) //Single responsibility
        {
            return operand1 / operand2;
        }
    }
}

using CalcToSolid.SolidCalc.Interfaces;

namespace CalcToSolid.SolidCalc.Logic
{
    public class OperationMul : IOperation
    {
        public int Execute(int operand1, int operand2) //Single responsibility
        {
            return operand1 * operand2;
        }
    }
}

using CalcToSolid.SolidCalc.Interfaces;

namespace CalcToSolid.SolidCalc.Logic
{
    public class OperationNExpM : IOperation
    {
        private static readonly IOperation _mul = new OperationMul(); //Liskov 

        public int Execute(int operand1, int operand2)
        {
            int result= operand1;

            for (int i=1; i < operand2; i++)
            {
                result = _mul.Execute(result, operand1);
            }
            return result;
        }
    }
}

using CalcToSolid.SolidCalc.Interfaces;

namespace CalcToSolid.SolidCalc.Logic
{
    public class OperationSub : IOperation
    {
        public int Execute(int operand1, int operand2) //Single responsibility
        {
            return operand1 - operand2;
        }
    }
}

using CalcToSolid.SolidCalc.Interfaces;

namespace CalcToSolid.SolidCalc.Logic
{
    public class OperationSum : IOperation
    {
        public int Execute(int operand1, int operand2) //Single responsibility
        {
            return operand1 + operand2;
        }
    }
}

using CalcToSolid.SolidCalc.Interfaces;

namespace CalcToSolid.SolidCalc.Logic
{
    public class SolidCalc : ICalc
    {
        private readonly IOperation _summatory;
        private readonly IOperation _multiplication;
        private readonly IOperation _subtraction;
        private readonly IOperation _division;
        private readonly IOperation _exponential;

        public SolidCalc()
        {
            _summatory = new OperationSum(); //Interface segregation
            _multiplication = new OperationMul(); //Interface segregation
            _subtraction = new OperationSub(); //Interface segregation
            _division = new OperationDiv(); //Interface segregation
            _exponential = new OperationNExpM(); //Interface segregation
        }

        public int DoOperation(OperationType operation, int operand1, int operand2)
        {
            switch (operation)
            {
                case OperationType.Div:
                    return Division(operand1, operand2);
                case OperationType.Mul:
                    return Multiplication(operand1, operand2);
                case OperationType.Sub:
                    return Subtraction(operand1, operand2);
                case OperationType.Sum:
                    return Summatory(operand1, operand2);
                case OperationType.NExpM:
                    return Exponential(operand1, operand2);
                default:
                    return 0;
            }
        }

        private int Division(int operand1, int operand2)
        {
            return _division.Execute(operand1, operand2);
        }

        private int Multiplication(int operand1, int operand2)
        {
            return _multiplication.Execute(operand1, operand2);
        }

        private int Subtraction(int operand1, int operand2)
        {
            return _subtraction.Execute(operand1, operand2);
        }

        private int Summatory(int operand1, int operand2)
        {
            return _summatory.Execute(operand1, operand2);
        }

        private int Exponential(int operand1, int operand2)
        {
            return _exponential.Execute(operand1, operand2);
        }
    }
}

Un saludo.

Gracias: A mis compañeros Erik Piqué, Marçal Montserrat y Omar Amalfi por sus comentarios.

  • En el repositorio veréis que ha varios commits con sus refactorizaciones hasta llegar a un estado final.

Código de ejemplo




@2017/@2024 - JRRN