DEV Community

Cover image for SOLID: Principio de Responsabilidad Única
g-esman
g-esman

Posted on

SOLID: Principio de Responsabilidad Única

Buenas, buenas, a vos que estás curioseando del otro lado. Bienvenido al primer post de esta serie donde vamos a estar hablando sobre SOLID (y no, no estoy hablando de Solid Snake). Hoy analizaremos qué es SOLID y veremos el primer principio.

¿De qué se trata SOLID?
Es una serie de principios de diseño de software, reunidos y presentados por primera vez en conjunto por Robert "Uncle Bob" Martin. Estos principios tienen como objetivo sentar unas bases sobre las cuales podamos crear un código mas fácil de mantener, escalar y entender. Son un "Must" si queremos escribir código limpio.
SOLID es un acrónimo de los 5 principios que representa:

  • Single Responsibility Principle
  • Open-Close Principle
  • Liskov Substitution Principle
  • Interface Segregation Principle
  • Dependency Inversion Principle

Single Responsibility Principle (SRP) - Principio de Responsabilidad Única
La premisa de este principio es que cada módulo debe tener una única responsabilidad, es decir que debe tener una única razón para cambiar.
¿A qué nos referimos con esto?
Supongamos un módulo de que tenga por objetivo imprimir en pantalla el total facturado.
Suena algo único ¿no? Pero internamente puede involucrar procesos de obtención de datos, procesamiento y generación del reporte.
Entonces, se presentan 3 situaciones en las que nuestro módulo podría verse modificado. Si nos preguntamos por las razones que podría tener para cambiar, resulta más intuitiva la separación de responsabilidades,
"cambiar si se modifica el formato en que quiero mostrar el total",
"cambiar si aplica un nuevo impuesto al cálculo del total",
"cambiar si la obtención de facturación lo requiere"
Los factores de cambio son más sencillos de especificar y nos dan ese feedback que nos ayuda a determinar si estamos dando demasiada responsabilidad a un módulo.

Aplicar este principio hace que las clases sean más fáciles de mantener y probar. Podemos modificarlas sin efectos secundarios, si algo no sale bien solo tenemos que buscar en un módulo. Entonces, aplicando el principio de responsabilidad única (SRP), reducimos el acoplamiento, evitando clases muy estrechamente vinculadas.

Llevemos a código el ejemplo anterior. Creamos un Invoice que contiene una lista de InvoiceItems y un FakeStorage, para emular luego una interacción con una base de datos.

public class Invoice
{
    public List<InvoiceItem> Items { get; set; } = new List<InvoiceItem>();
}
public class InvoiceItem
{
    public string Name { get; set; }
    public decimal Price { get; set; }
    public int Quantity { get; set; }
}
public class FakeStorage<T>
{
    private ObservableCollection<T> collection;

    public FakeStorage()
    {
        collection = new ObservableCollection<T>();
    }

    public T Add(T item)
    {
        collection.Add(item);
        return item;
    }

    public IEnumerable<T> GetAll()
    {
        return collection;
    }
}
Enter fullscreen mode Exit fullscreen mode

A continuación, con esta base ya resuelta tenemos la clase InvoiceRepository y analicemos cuál es su comportamiento.

public class InvoiceRepository
{
    private static FakeStorage<Invoice> storage;

    public InvoiceRepository()
    {
        storage = new();
        InitData();
    }
    private const decimal iva = 1.21M;
    private void InitData()
    {
        storage.Add(new Invoice
        {
            Items = new List<InvoiceItem>
            {
                new InvoiceItem{ Name = "vino", Price = 54, Quantity= 2}
            }
        });
        storage.Add(new Invoice
        {
            Items = new List<InvoiceItem>
            {
                new InvoiceItem{ Name = "atun", Price = 80, Quantity= 5}
            }
        });
    }
    public void ShowTotal()
    {
        var invoices = storage.GetAll();
        decimal total = 0;
        foreach (var invoice in invoices)
        {
            total += invoice.Items.Sum(item => item.Price * item.Quantity) * iva;
        }
        Console.WriteLine($"Factura generada en PDF. para el total {total}");
    }
}
Enter fullscreen mode Exit fullscreen mode

Lo primero que hace InvoiceRepository en su constructor es ejecutar InitData, que nos sirve para agregar en nuestro fakeStorage elementos de Invoice; luego, sigue el método ShowTotal, que abarca muchas responsabilidades: desde obtener los invoice, hasta hacer los cálculos e imprimir.Como mencionamos antes, demasiadas razones para cambiar.

Ahora pasemos a la última parte de este código, el Program donde interactuamos con todo el programa en conjunto.

class Program
{
    static void Main(string[] args)
    {
        var repo = new InvoiceRepository();
        repo.ShowTotal()
    }
}
Enter fullscreen mode Exit fullscreen mode

Ya que detectamos que nuestro InvoiceRepository no cumple con el primer principio de SOLID, toca refactorizarlo. Veamos cómo aplicar este principio de responsabilidad única.

Empecemos por el InvoiceRepository

public class InvoiceRepository
{
    private static FakeStorage<Invoice> storage;

    public InvoiceRepository()
    {
        storage = new();
        InitData();
    }
    private void InitData()
    {
        storage.Add(new Invoice
        {
            Items = new List<InvoiceItem>
            {
                new InvoiceItem { Name = "vino", Price = 54, Quantity= 2}
            }
        });
        storage.Add(new Invoice
        {
            Items = new List<InvoiceItem>
            {
                new InvoiceItem { Name = "atun", Price = 80, Quantity= 5}
            }
        });
    }
    public IEnumerable<Invoice> GetAll()
    {
        return storage.GetAll();
    }
}
Enter fullscreen mode Exit fullscreen mode

En esta nueva versión, podemos ver que ahora el InvoiceRepository solo se encarga de conectar nuestro código con los datos de nuestras bases de datos (aquí lo emulamos con el fakeStorage).

Ahora trasladamos la lógica de calcular los totales a un InvoiceCalculator.

public class InvoiceCalculator
{
    private const decimal iva = 1.21M;
    public decimal GetTotal(IEnumerable<Invoice> invoices)
    {
        decimal total = 0;
        foreach (var invoice in invoices)
        {
            total += invoice.Items.Sum(item => item.Price * item.Quantity) * iva;
        }
        return total;
    }
}
Enter fullscreen mode Exit fullscreen mode

Como la responsabilidad del cálculo de totales ahora pasó a una nueva clase, si se aplica un nuevo impuesto o descuento, este es el único lugar donde debemos aplicar modificaciones.

Finalmente, la tercer parte de la lógica: la impresión pasa a estar a cargo de una nueva clase, InvoicePrinter.

public class InvoicePrinter
{
    public void Print(decimal total)
    {
        Console.WriteLine($"El monto total de sus facturas es de {total}");
    }
}
Enter fullscreen mode Exit fullscreen mode

Si se nos pide cambiar la impresión en pantalla por una en pdf o el texto mostrado, ahora tenemos una clase única a ser modificada.

Para finalizar, modifiquemos el Program para ver cómo interactúa con todas las clases.

class Program
{
    static void Main(string[] args)
    {
        var InvoiceRepository = new InvoiceRepository();
        var InvoiceCalculator = new InvoiceCalculator();
        var InvoicePrinter = new InvoicePrinter();
        IEnumerable<Invoice> invoices = InvoiceRepository.GetAll();
        decimal total = InvoiceCalculator.GetTotal(invoices);
        InvoicePrinter.Print(total);
    }
}
Enter fullscreen mode Exit fullscreen mode

Ahora sí tenemos un código mas limpio, modular y escalable, siguiendo el principio de responsabilidad única.

Hemos llegado al final del primer posteo de esta serie sobre los principios SOLID. Este es solo el comienzo: en los siguientes artículos exploraremos cómo los demás principios se complementan con SRP para crear sistemas sólidos, flexibles y robustos. Espero que las explicaciones hayan sido claras, igualmente te invito a dejar tus dudas, sugerencias o ejemplos en los comentarios. Te leo!

Top comments (0)