Los principios SOLID son un conjunto de cinco principios que se utilizan en el diseño de software para crear sistemas más mantenibles, escalables y flexibles. Juntos, estos principios proporcionan una guía para crear software que sea fácil de entender, modificar y mantener a medida que evoluciona con el tiempo.

1. Principios de diseño software

Los principios de diseño de software son un conjunto de directrices y enfoques, podríamos llamarlo guías, que ayudan a los desarrolladores a crear software con alta calidad, código limpio, eficiente, mantenible y escalable. Estos principios son considerados mejores prácticas y se utilizan para guiar la toma de decisiones en el diseño de arquitecturas, la escritura de código y la creación de soluciones tecnológicas.

El objetivo de los principios de diseño es reducir las dependencias entre los diferentes componentes software, como clases, para que cuando se produzca un cambio en el software sea más sencilla su modificación. Hablando claro, se pretende que el diseño del software sea el mejor posible, es decir, con la mayor calidad posible.

Cuando hablamos de diseño software lo haremos refiriéndonos al paradigma Orientado a Objetos.

2. Principios de diseño SOLID

Los principios SOLID podríamos considerarlos como el ABC de estos principios de diseño, ya que son los más básicos y fundamentales. Todo perfil que trabaje con Java debería conocer y aplicar estos principios como la palma de su mano.

Fueron creados por Robert C. Martin en el año 2000 en su archiconocido ensayo Design Principles and Desing Patters (el los llamo Principles of Object Oriented Class Design). Te animo a que le eches un vistazo, es muy interesante y aporta un gran valor. Posteriormente Michael Feathers acuñó el acrónimo SOLID para referirse a esos principios.

A continuación se muestra en una tabla, de forma resumida, los cinco principios SOLID junto con la descripción y un ejemplo a modo de guía:

Abv.Principio SOLIDDescripción
SRPPrincipio de Responsabilidad ÚnicaCada clase debe tener una única responsabilidad y un motivo para cambiar
OCPPrincipio de Abierto/CerradoLas clases deben estar abiertas para la extensión pero cerradas para la modificación
LSPPrincipio de Sustitución de LiskovLos objetos de una clase derivada deben poder sustituirse por objetos de la clase base sin afectar la corrección del programa
ISPPrincipio de Segregación de InterfacesLas interfaces deben ser específicas para cada cliente, evitando interfaces grandes y poco cohesivas
DIPPrincipio de Inversión de DependenciasLos módulos de alto nivel no deben depender de los módulos de bajo nivel, ambos deben depender de abstracciones

3. Ejemplo de principios SOLID para Samuráis

Diseño

Y como esto se está volviendo un poco teórico, vamos a ver un poco de código y cómo podríamos implementarlo en Java usando un ejemplo de Samuráis. Primero veamos el diagrama de clases para verlo desde arriba:

Omitimos constructores, junto con los getters y setters por simplificar.

¿Cómo estamos aplicando los principios SOLID a este diseño de Diagrama de Clases? Veámoslo:

  1. SRP: la clase Samurai representa la información de un samurái y tiene la responsabilidad única de gestionar sus datos.
  2. OCP: la clase SamuraiArquero extiende de la clase Samurai para agregar funcionalidades de un arquero sin modificar la implementación de la clase base Samurai.
  3. LSP: la clase SamuraiArquero debe poder ser usada en lugar de la clase Samurai sin afectar la corrección del programa.
  4. ISP: la interfaz Combatiente define solo la funcionalidad necesaria para un combatiente, evitando interfaces grandes y poco cohesivas.
  5. DIP: la clase Batalla depende de la abstracción de la interfaz Combatiente en lugar de la implementación concreta de Samurai, siguiendo el principio de invertir la dependencia.

Implementación

El código quedaría de la siguiente forma:

// Interfaz Combatiente que define su funcionalidad
// Principios SOLID aplicados: ISP (Principio de Segregación de Interfaces)
public interface Combatiente {
    void atacar();
    void defender();
}
// Clase Samurai que implementa la interface previa
// Principios SOLID aplicados: SRP (Principio de Responsabilidad Única)
@Data
public class Samurai implements Combatiente {

    private String nombre;
    private int edad;
    
    public Samurai(String nombre, int edad) {
        this.nombre = nombre;
        this.edad = edad;
    }

    @Override
    void atacar() {
        System.out.println("El samurai ataca con su espada");
    }

    @Override
    void defender() {
        System.out.println("El samurai defiende con su espada");
    }
}
// Clase SamuraiArquero que extiende de la clase Samurai
// para agregar funcionalidades de un arquero
// Principios SOLID aplicados: OCP (Principio de Abierto/Cerrado)
@Data
public class SamuraiArquero extends Samurai {

    private int cantidadFlechas;
    
    public SamuraiArquero(String nombre, int edad, int cantidadFlechas) {
        super(nombre, edad);
        this.cantidadFlechas = cantidadFlechas;
    }

    @Override
    void atacar() {
        System.out.println("El samurai arquero dispara una flecha");
    }

    @Override
    void defender() {
        System.out.println("El samurai arquero defiende con el su escudo");
    }

    // Métodos específicos de un arquero relacionados con el arco y flechas
}
// Clase Batalla que depende de la abstracción de la interfaz Combatiente
// en lugar de la implementación concreta de Samurai
// Principios SOLID aplicados: DIP (Principio de Inversión de Dependencias)
public class Batalla {

    private Combatiente samurai1;
    private Combatiente samurai2;
    
    public Batalla(Combatiente samurai1, Combatiente samurai2) {
        this.samurai1 = samurai1;
        this.samurai2 = samurai2;
    }

    pelear() {
        System.out.println("El samurai1 y samurai2 pelean");
    }
}
// Principios SOLID aplicados: LSP (Principio de Sustitución de Liskov)
public class Main {
    public static void main(String[] args) {

        Samurai samurai1 = new Samurai();
        Samurai samurai2 = new SamuraiArquero(); // Uso de polimorfismo

        samurai1.atacar(); // El samurai ataca con su espada.
        samurai2.atacar(); // El samurai arquero dispara una flecha.
    }
}

A tener en cuenta que en la clase Batalla solo contemplamos dos samuráis, ya que consideramos que las batallas son únicamente entre dos Combatientes. Además, se ha utilizado una clase main para mostrar el principio LSP.

En próximas entradas se detallarán cada uno de estos principios con los ejemplos más desarrollados. Valga esto como introducción, aunque pueda ser complicado de ver en primera instancia.

4. Principios SOLID y patrones de diseño

Los principios de diseño software están relacionados con los patrones de diseño, ya que estos últimos se basan en los primeros. Además, el objetivo de ambos es crear software de calidad y mantenible. Podríamos decir que los principios de diseño son los padres de los patrones de diseño.

Un ejemplo de como un principio SOLID se aplica en un patrón de diseño es el uso del Principio de Responsabilidad Única (SRP) en el patrón factory. SRP indica que una clase sólo debe tener una responsabilidad. El patrón fábrica usa el principio de forma que cada clase factory tendrá la única responsabilidad de crear objetos de un tipo específico.

Implementación

Añadiendo el siguiente código a las clases anteriormente descritas, implementamos el patrón factory:

// Implementamos la clase SamuraiFactory que utiliza un método de fábrica estático
// para crear objetos Combatiente
public class SamuraiFactory {
    public static Combatiente createSamurai(SamuraiType type, String name, int edad) {
        switch (type) {
            case NINJA:
                return new Samurai (name, edad);
            default:
                throw new IllegalArgumentException("Tipo de samurái no soportado");
        }
    }
}
public enum SamuraiType {
    NINJA
}

En este ejemplo, el principio SRP se aplica manteniendo la responsabilidad de la creación de objetos Samurai en la clase SamuraiFactory, que es una clase específica encargada exclusivamente de la creación de samuráis.

5. Conclusiones

En conclusión, los principios SOLID son un conjunto de principios de diseño de software que proporcionan una guía para escribir software de alta calidad, flexible y mantenible. Por tanto, al seguir estos principios, se pueden obtener muchos beneficios, como la facilidad de mantenimiento, la reducción de errores, la capacidad de ampliación y la facilidad de comprensión del código.

De la misma forma reducimos el acoplamiento entre módulos o clases, a la vez que aumentamos la cohesión de los mismos. También podemos destacar que se fomenta la reutilización del código y las pruebas serán más sencillas.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *