Cargando...

Principios SOLID en C#

Aplicación de los principios SOLID en C# con ejemplos: código más flexible, mantenible y comprobable.

En el desarrollo de software, los principios SOLID, definidos por Robert C. Martin, son cinco reglas fundamentales que buscan crear sistemas más flexibles, legibles y mantenibles en la programación orientada a objetos (POO). En C#, estos principios se aplican ampliamente como guías en el diseño de clases.


1. Single Responsibility Principle (SRP) – Principio de responsabilidad única

Una clase debe tener una sola responsabilidad. Es decir, debe existir una única razón para que cambie. Si una clase realiza múltiples tareas (ej. guardar datos + generar reportes), su mantenimiento se vuelve difícil y aumenta la probabilidad de errores.


// Incorrecto: Guarda pedidos e imprime facturas
public class OrderManager
{
    public void SaveOrder(Order order) { /* guardar */ }
    public void PrintInvoice(Order order) { /* imprimir */ }
}

// Correcto: Cada clase tiene una sola responsabilidad
public class OrderRepository
{
    public void Save(Order order) { /* guardar */ }
}

public class InvoicePrinter
{
    public void Print(Order order) { /* imprimir */ }
}

2. Open/Closed Principle (OCP) – Principio Abierto/Cerrado

Las clases deben estar abiertas a la extensión, pero cerradas a la modificación. Esto significa que se deben poder agregar nuevas funcionalidades sin cambiar el código existente. Generalmente se logra mediante abstract class o interface.


// Incorrecto: Agregar un nuevo método de pago obliga a modificar la clase
public class PaymentService
{
    public void Pay(string type)
    {
        if (type == "CreditCard") { /* ... */ }
        else if (type == "PayPal") { /* ... */ }
    }
}

// Correcto: Basta con crear una nueva clase
public interface IPayment
{
    void Pay();
}

public class CreditCardPayment : IPayment
{
    public void Pay() { /* ... */ }
}

public class PayPalPayment : IPayment
{
    public void Pay() { /* ... */ }
}

3. Liskov Substitution Principle (LSP) – Principio de sustitución de Liskov

Las clases derivadas deben poder usarse en lugar de sus clases base. Es decir, una subclase debe funcionar correctamente en cualquier lugar donde se espere su superclase. Una subclase no debe violar el contrato de su clase base.


public abstract class Bird
{
    public abstract void Fly();
}

public class Sparrow : Bird
{
    public override void Fly() { Console.WriteLine("El gorrión vuela"); }
}

// Incorrecto: Los pingüinos no vuelan, pero Fly() es obligatorio
public class Penguin : Bird
{
    public override void Fly() { throw new NotImplementedException(); }
}

Aquí la clase Penguin viola el LSP. Porque la clase abstracta Bird impone la suposición de que “todas las aves vuelan”. En su lugar, se deben definir interfaces separadas como IFlyingBird e INonFlyingBird.


4. Interface Segregation Principle (ISP) – Principio de segregación de interfaces

Una clase no debe verse obligada a implementar interfaces grandes con métodos que no necesita. En su lugar, se deben usar interfaces más pequeñas y específicas.


// Incorrecto: Todos los animales deben caminar, volar y nadar
public interface IAnimal
{
    void Walk();
    void Fly();
    void Swim();
}

// Correcto: Interfaces más pequeñas
public interface IWalkable { void Walk(); }
public interface IFlyable { void Fly(); }
public interface ISwimmable { void Swim(); }

public class Dog : IWalkable, ISwimmable
{
    public void Walk() { /* ... */ }
    public void Swim() { /* ... */ }
}

5. Dependency Inversion Principle (DIP) – Principio de inversión de dependencias

Las clases de alto nivel no deben depender directamente de las clases de bajo nivel. Ambas deben depender de abstracciones (interfaces o clases abstractas). Este principio es la base de la inyección de dependencias.


// Incorrecto: Service depende directamente de ConsoleLogger
public class Service
{
    private readonly ConsoleLogger _logger = new ConsoleLogger();
}

// Correcto: Depende de una abstracción
public interface ILogger
{
    void Log(string msg);
}

public class ConsoleLogger : ILogger
{
    public void Log(string msg) => Console.WriteLine(msg);
}

public class Service
{
    private readonly ILogger _logger;
    public Service(ILogger logger) { _logger = logger; }
}

Ventajas


TL;DR

  • SRP: Cada clase debe tener una sola responsabilidad.
  • OCP: Abierto a la extensión, cerrado a la modificación.
  • LSP: Las subclases deben poder sustituir a las superclases.
  • ISP: Interfaces pequeñas y enfocadas en lugar de grandes.
  • DIP: Depender de abstracciones, no de clases concretas.

Artículos relacionados