Logotipo SOLID

Si en el capítulo anterior vimos como una mala herencia puede provocar que haya clases con métodos que no deberían implementar, para este cuarto principio tenemos un ejemplo muy parecido, pero con el caso de las interfaces.

Su nacimiento se remonta a los tiempos en los que Robert C. Martin trabajaba en la empresa Xerox. Dicha empresa había creado un software que llevaba a cabo la mayor parte de tareas que puede llevar a cabo un sistema de impresión. El problema es que había una sola clase que hacía todo el trabajo, algo que ya también incumplía varios de los principios que hemos visto (como el Principio de Responsabilidad Única). Pues bien, tras ver como cada cambio implicaba cada un mayor tiempo de desarrollo, se optó por crear varias interfaces que acotaran las tareas: grapado, envío de fax, impresión, etc. Y de ahí salió la definición del Principio de Segregación de Interfaces:

Los clientes no deberían estar forzados a depender de interfaces que no van a usar.

Robert C. Martin ~ The Interface Segregation Principle

Y como siempre, vamos a intentar definir de una forma más "mundana" este principio. En este caso, lo que viene a decir, es que las clases no deberían implementar métodos de interfaces que no van a usar; algo muy parecido al problema que provocaba el que tuviéramos que usar el Principio de Sustitución de Liskov, pero aplicado a relaciones con interfaces, en lugar de con herencia de clases.

En nuestro ejemplo, partiremos de un ejemplo videojueguil que casi todo el mundo conoceréis.

En Super Mario tenemos entre otros estos enemigos:

Goomba Goombas Son los champiñones que sólo avanzan.
Koopa Troopa Koopa Troopa Las tortugas que al saltar sobre ellas se esconden.
Koopa Paratroopa Koopa Paratroopa Tortugas con alas que van dando saltos, también al saltar sobre ellas se esconden.
Hermano martillo Hermanos Martillo Con forma de tortuga. Lanzan martillos y dan saltos

Vale, pues imaginad que tenemos esta interfaz para los enemigos:

interface IEnemigo {
  avanzar(): void;
  esconder(): void;
  saltar(): void;
}

Si usáramos esa interfaz con todos los personajes tendríamos un problema, ya que el código quedaría tal que así:

class Goomba implements IEnemigo {
  public avanzar(): void {
    console.log("Goomba avanza");
  }

  public esconder(): void {
    throw new Error("Los Goomba no se esconden");
  }

  public saltar(): void {
    throw new Error("Los Goomba no saltan");
  }
}

class KoopaTroopa implements IEnemigo {
  public avanzar(): void {
    console.log("Koopa Troopa avanza");
  }

  public esconder(): void {
    console.log("Koopa Troopa se esconde");
  }

  public saltar(): void {
    throw new Error("Los Koopa Troopa no saltan");
  }
}

class KoopaParatroopa implements IEnemigo {
  public avanzar(): void {
    console.log("Koopa Paratroopa avanza");
  }

  public esconder(): void {
    console.log("Koopa Paratroopa se esconde");
  }

  public saltar(): void {
    console.log("Koopa Paratroopa salta");
  }
}


class HermanoMartillo implements IEnemigo {
  public avanzar(): void {
    console.log("Hermano Martillo avanza");
  }

  public esconder(): void {
    throw new Error("Los Hermanos Martillo no se esconden");
  }

  public saltar(): void {
    console.log("Hermano Martillo salta");
  }
}

Como se puede apreciar, hay una interfaz que obliga a las clases (los clientes según el término de este principio) a implementar métodos que no tiene sentido que usen. Esto a la larga dará muchos quebraderos de cabeza, tal y como se vio con el principio anterior. ¿Cómo se podría solventar este problema? Pues creando interfaces más específicas que obliguen a desglosar las tareas (o en este caso acciones) a realizar por las clases:

interface IEnemigo {
  avanzar(): void;
}

interface IEnemigoCobarde {
  esconder(): void;
}

interface IEnemigoSalarin {
  saltar(): void;
}

Como anotación importante, en nuestro ejemplo quedan tres interfaces, cada una con un método, esto no significa que en todos los casos se tengan mini interfaces "uni-metodales", sino que dependerá del caso.

Tras la aclaración anterior, ahora nuestros enemigos quedarían con esta definición:

class Goomba implements IEnemigo {
  public avanzar(): void {
    console.log("Goomba avanza");
  }
}

class KoopaTroopa implements IEnemigo, IEnemigoCobarde {
  public avanzar(): void {
    console.log("Koopa Troopa avanza");
  }

  public esconder(): void {
    console.log("Koopa Troopa se esconde");
  }
}

class KoopaParatroopa implements IEnemigo, IEnemigoCobarde, IEnemigoSaltarin {
  public avanzar(): void {
    console.log("Koopa Paratroopa avanza");
  }

  public esconder(): void {
    console.log("Koopa Paratroopa se esconde");
  }

  public saltar(): void {
    console.log("Koopa Paratroopa salta");
  }
}


class HermanoMartillo implements IEnemigo, IEnemigoSaltarin {
  public avanzar(): void {
    console.log("Hermano Martillo avanza");
  }

  public saltar(): void {
    console.log("Hermano Martillo salta");
  }
}

Ahora las clases sólo implementan lo que necesitan, evitando en este caso añadir métodos vacíos, que devuelvan valores "sin sentido", o que lancen excepciones. Aparte se han dejado mejor agrupadas las acciones u operaciones a las que representa cada interfaz, dando algo más de semántica al código.

Como extra seguramente se podría añadir una clase base que implemente la interfaz IEnemigo, ya que todas las clases tienen un avanzar que en el ejemplo de este artículo, hace prácticamente lo mismo, pero tampoco era el objetivo salir de la explicación del principio en sí, y así os lo podía comentar mejor.

Pues básicamente este es el Principio de Segregación de Interfaces. En el próximo artículo terminaremos la serie de principios SOLID. Ya sabéis que podéis encontrar un resumen de este principio en Instagram.


Más artículos de esta serie:
Capítulo S: Single Responsibility Principle
Capítulo O: Open/Closed Principle
Capítulo L: Liskov Substitution Principle
Capítulo D: Dependency Inversion Principle

Si quieres leer algo más de Robert C. Martin, aparte de su web, tiene varios libros interesantes:

Comparte este artículo con quien quieras
Principios SOLID. Capítulo L: Liskov Substitution Principle
Principios SOLID. Capítulo D: Dependency Inversion Principle

Leave a Comment

Your email address will not be published. Required fields are marked *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.