✂️ Principio de Segregación de Interfaces (ISP)
¿Qué es?
El Interface Segregation Principle es uno de los cinco principios SOLID de la programación orientada a objetos. Este principio dice que ningún cliente debe verse obligado a depender de métodos que no utiliza. Es decir, una clase no debe implementar una interfaz gigante con métodos que no va a usar, sino que debe implementar solo las interfaces con los métodos que realmente necesita.
La formulación original de Robert C. Martin (Uncle Bob) es:
“Clients should not be forced to depend upon interfaces that they do not use.”
Traducido a cristiano: mejor muchas interfaces pequeñas y específicas que una interfaz gorda que lo haga todo. Si una clase se ve obligada a implementar métodos que no necesita, algo está mal diseñado.

¿De dónde sale este principio?
El ISP fue formulado por Robert C. Martin (Uncle Bob) a principios de los 90 mientras trabajaba como consultor para Xerox. La historia es bastante interesante.
Xerox tenía un sistema de impresoras donde una sola interfaz gigante gestionaba todas las funcionalidades: imprimir, grapar, enviar por fax, escanear… Cada vez que añadían una funcionalidad nueva o modificaban una existente, todas las clases que implementaban esa interfaz se veían afectadas, aunque no usaran esa funcionalidad. Si cambiabas algo del fax, la clase de la impresora básica (que no tenía fax) se tenía que recompilar.
Uncle Bob propuso dividir esa interfaz gorda en interfaces más pequeñas y específicas. Cada impresora implementaría solo las interfaces de las funcionalidades que realmente tenía. Así nació el ISP tal y como lo conocemos hoy.
La idea clave
La clave del ISP es que las interfaces deben diseñarse desde la perspectiva del cliente (quien las usa), no del proveedor (quien las implementa). Si un cliente solo necesita leer datos, no debería verse obligado a depender de una interfaz que también incluye escritura, borrado y exportación.
Una sola interfaz con muchos métodos. Las clases se ven obligadas a implementar cosas que no necesitan. Métodos vacíos, excepciones, caos.
Varias interfaces pequeñas y específicas. Cada clase implementa solo lo que realmente necesita. Limpio, claro y sin desperdicio.
¿Por qué es tan importante?
Aplicar el ISP te da ventajas muy claras en el día a día:
- Bajo acoplamiento: Las clases no dependen de métodos que no usan. Un cambio en un método no afecta a las clases que ni siquiera lo implementan.
- Claridad: Interfaces pequeñas son mucho más fáciles de entender. Abres una interfaz y en 2 segundos sabes qué contrato define.
- Mantenibilidad: Cambiar una interfaz pequeña afecta a pocas clases. Cambiar una interfaz gorda puede tener un efecto dominó en medio proyecto.
- Testabilidad: Mockear una interfaz con 2 métodos es trivial. Mockear una con 15 métodos es un dolor.
- Reutilización: Interfaces específicas se pueden reutilizar en contextos diferentes. Una interfaz
Readablesirve para ficheros, APIs, bases de datos… Una interfazFileManagercon 20 métodos no la reutilizas en ningún sitio.
Ejemplo
Alguna vez lo habrás visto, o lo verás, una interfaz que tiene una serie de métodos y se implementa porque quizá son necesarios la gran mayoría pero alguno no. Por ejemplo, una interfaz llamada Animal:
interface Animal
{
public function walk(): void;
public function fly(): void;
public function swim(): void;
}
Luego verás, por ejemplo, que hay clases como Dog que implementan esta interfaz pero que cuando llega el momento de implementar el método fly, lanzan una excepción o hacen cualquier cosa para anular el método. Obviamente los perros no pueden volar, bueno igual si le atas al perro un jet pack con cinta americana sí, pero no es el caso más común.
class Dog implements Animal
{
public function walk(): void {
echo "El perro camina feliz";
}
public function fly(): void {
throw new Exception("¡Los perros no volan, bro!");
}
public function swim(): void {
echo "El perro nada como un campeón";
}
}
Ese throw new Exception en fly() es una red flag enorme. Significa que Dog está siendo obligado a implementar algo que no puede hacer. La interfaz Animal es demasiado gorda.
Refactor
La solución es deshacer la interfaz Animal en interfaces más pequeñas para que cada tipo de animal implemente solo las que necesita:
interface Walker
{
public function walk(): void;
}
interface Flyer
{
public function fly(): void;
}
interface Swimmer
{
public function swim(): void;
}
Ahora Dog implementa solo las interfaces que le corresponden:
class Dog implements Walker, Swimmer
{
public function walk(): void {
echo "El perro camina feliz";
}
public function swim(): void {
echo "El perro nada como un campeón";
}
}
Un pato, que sí puede hacer las tres cosas:
class Duck implements Walker, Flyer, Swimmer
{
public function walk(): void {
echo "El pato camina con estilo";
}
public function fly(): void {
echo "El pato vuela bajo pero vuela";
}
public function swim(): void {
echo "El pato nada de maravilla";
}
}
Y es más, luego imagina que es un Chihuahua 🐕 en cuestión. Podrías hacer lo siguiente, porque ya sabemos que esos temblones tienen su propio talento:
interface Shaker
{
public function shake(): void;
}
class Chihuahua extends Dog implements Shaker
{
public function shake(): void {
echo "Temblando como solo un Chihuahua sabe";
}
}
Cada clase implementa solo lo que realmente puede hacer. Cero métodos vacíos, cero excepciones forzadas.
Ejemplo más real: la impresora de Xerox
Vamos a ver el caso que inspiró el principio. Imagina que tienes una interfaz para una máquina multifunción:
// MAL: interfaz gorda al estilo Xerox original
interface MultiFunctionDevice
{
public function print(Document $doc): void;
public function scan(Document $doc): Image;
public function fax(Document $doc, string $number): void;
public function staple(Document $doc): void;
public function photocopy(Document $doc, int $copies): void;
}
Ahora tienes que crear una impresora básica de casa:
class BasicPrinter implements MultiFunctionDevice
{
public function print(Document $doc): void {
// Esto sí lo hace
echo "Imprimiendo documento...";
}
public function scan(Document $doc): Image {
throw new Exception("Esta impresora no escanea");
}
public function fax(Document $doc, string $number): void {
throw new Exception("Esta impresora no tiene fax");
}
public function staple(Document $doc): void {
throw new Exception("Esta impresora no grapa");
}
public function photocopy(Document $doc, int $copies): void {
throw new Exception("Esta impresora no fotocopia");
}
}
Un método útil y cuatro excepciones. Eso es un desastre. La solución es segregar:
interface Printer
{
public function print(Document $doc): void;
}
interface Scanner
{
public function scan(Document $doc): Image;
}
interface FaxMachine
{
public function fax(Document $doc, string $number): void;
}
interface Stapler
{
public function staple(Document $doc): void;
}
interface Photocopier
{
public function photocopy(Document $doc, int $copies): void;
}
Ahora cada dispositivo implementa solo lo que puede hacer:
// Impresora básica de casa
class BasicPrinter implements Printer
{
public function print(Document $doc): void {
echo "Imprimiendo documento...";
}
}
// La bestia multifunción de la oficina
class OfficeMachine implements Printer, Scanner, FaxMachine, Stapler, Photocopier
{
public function print(Document $doc): void { /* ... */ }
public function scan(Document $doc): Image { /* ... */ }
public function fax(Document $doc, string $number): void { /* ... */ }
public function staple(Document $doc): void { /* ... */ }
public function photocopy(Document $doc, int $copies): void { /* ... */ }
}
// Impresora con escáner de casa
class HomePrinterScanner implements Printer, Scanner
{
public function print(Document $doc): void { /* ... */ }
public function scan(Document $doc): Image { /* ... */ }
}
Limpio. Cada clase implementa exactamente lo que puede hacer, ni más ni menos.
Otro ejemplo real: repositorios de datos
Este caso te lo vas a encontrar seguro en proyectos reales. Imagina una interfaz de repositorio para una entidad:
// MAL: interfaz de repositorio gorda
interface UserRepository
{
public function findById(int $id): User;
public function findAll(): array;
public function save(User $user): void;
public function update(User $user): void;
public function delete(int $id): void;
public function findByEmail(string $email): User;
public function findActiveUsers(): array;
public function exportToCsv(): string;
public function importFromCsv(string $csv): void;
public function sendWelcomeEmail(User $user): void;
}
Esta interfaz es un festival. Tiene mezclados CRUD, queries específicas, exportación y hasta envío de emails. Si un servicio solo necesita buscar usuarios, se ve obligado a depender de exportToCsv() y sendWelcomeEmail(). Y eso sin contar que sendWelcomeEmail() ni siquiera es responsabilidad de un repositorio (eso viola el SRP también).
La solución:
interface ReadableUserRepository
{
public function findById(int $id): User;
public function findAll(): array;
public function findByEmail(string $email): User;
public function findActiveUsers(): array;
}
interface WritableUserRepository
{
public function save(User $user): void;
public function update(User $user): void;
public function delete(int $id): void;
}
interface ExportableRepository
{
public function exportToCsv(): string;
}
interface ImportableRepository
{
public function importFromCsv(string $csv): void;
}
Ahora un servicio que solo necesita leer usuarios depende únicamente de ReadableUserRepository. Si mañana añades un método nuevo de exportación, las clases que solo leen no se enteran ni se ven afectadas. Y el sendWelcomeEmail() se va a su propio servicio de notificaciones, donde pertenece.
La relación entre ISP y otros principios
El ISP no trabaja solo. Está muy conectado con el resto de principios SOLID y se refuerzan mutuamente:
Si una interfaz tiene demasiados métodos, probablemente tenga múltiples responsabilidades. Segregar la interfaz a menudo significa aplicar el SRP a nivel de contrato.
Las interfaces gordas causan violaciones de Liskov: las clases implementan métodos vacíos o lanzan excepciones, rompiendo el contrato de la interfaz.
Interfaces pequeñas facilitan la extensión. Añadir una interfaz nueva no afecta a las existentes. Abierto a extensión, cerrado a modificación.
El DIP dice que dependas de abstracciones, pero si esa abstracción es una interfaz gorda, no has ganado nada. El ISP asegura que las abstracciones sean útiles.
¿Cómo detectar que estás violando el ISP?
Hay varias señales de alarma que te avisan de que algo huele mal:
Métodos no utilizados o anulados
La señal más clara de todas. Si una clase implementa un método de la interfaz pero lo deja vacío, devuelve null, lanza una excepción tipo NotImplementedException o simplemente no hace nada… red flag 🚩 definitiva. Ese método no debería estar en esa interfaz para ese cliente.
Interfaces con demasiados métodos
No hay un número mágico, pero si una interfaz tiene más de 5-7 métodos, conviene revisarla. Pregúntate: ¿todos los clientes que la implementan usan todos estos métodos? Si la respuesta es no, hay que segregar.
Clases que implementan una interfaz pero solo usan la mitad
Si ves que una clase implementa 10 métodos pero solo 4 tienen lógica real y los otros 6 son stubs, es una señal clara.
Cambios en la interfaz que afectan a clases que no deberían verse afectadas
Si añades un método a la interfaz y tienes que tocar 15 clases de las cuales 12 no necesitan ese método, la interfaz es demasiado gorda.
Nombres de interfaz demasiado genéricos
Nombres como Manager, Handler, Processor o Service suelen esconder interfaces que hacen demasiadas cosas. Una buena interfaz tiene un nombre que describe exactamente lo que hace: Readable, Printable, Cacheable…
Errores comunes al aplicar el ISP
- Sobre-segregar: Crear una interfaz por cada método es pasarse. El ISP dice “no obligues a implementar lo que no se usa”, no “cada método en su propia interfaz”. Si dos métodos siempre van juntos y se usan juntos, pueden estar en la misma interfaz perfectamente.
- Segregar por implementación en vez de por uso: Las interfaces se diseñan desde la perspectiva del cliente (quien las usa), no del proveedor (quien las implementa). No segregues pensando en cómo se implementa, sino en cómo se consume.
- Ignorar la cohesión: Métodos que están fuertemente relacionados y siempre se usan juntos deben estar en la misma interfaz.
read()yreadLine()tienen sentido juntos en una interfazReadable. No los separes enReadableyLineReadablesi siempre van de la mano. - No considerar la evolución: Piensa en cómo van a crecer las interfaces. Si hoy tienes 3 métodos pero sabes que mañana serán 10 con funcionalidades muy diferentes, quizá conviene segregar desde ya.
¿Cuándo NO hace falta segregar?
Como con todos los principios SOLID, el ISP tiene sus matices:
- Interfaces pequeñas que ya son cohesivas: Si tu interfaz tiene 3-4 métodos que siempre se usan juntos, no la rompas solo por el gusto de tener interfaces más pequeñas.
- Código interno de un solo equipo: Si una interfaz la usas solo tú en un módulo pequeño y controlas todos los puntos de uso, el riesgo de una interfaz algo más gorda es bajo.
- Prototipos: Cuando estás explorando una idea, no te preocupes por tener la segregación perfecta. Ya refactorizarás cuando el código se estabilice.
- Cuando segregar añade más complejidad que la que resuelve: Si para segregar una interfaz necesitas crear 8 interfaces y cada clase implementa 6 de ellas, quizá la cura es peor que la enfermedad.
Recuerda: El ISP es una herramienta, no una religión. Segregar interfaces mola, pero con sentido común.
Conclusión
El Principio de Segregación de Interfaces se resume en una idea sencilla pero muy potente: las interfaces deben ser pequeñas, específicas y diseñadas para el cliente que las usa. Si una clase se ve obligada a implementar métodos que no necesita, la interfaz es demasiado gorda y hay que partirla.
Y recuerda: los métodos de interfaces que se anulan mediante excepciones o se dejan vacíos, matan gatitos en cualquier parte del mundo cada 10 segundos. Así que cada vez que veas algo así es que está mal implementado y requiere de un poco de cariño para solventarlo.
La clave técnica es pensar siempre desde la perspectiva del cliente: ¿qué necesita quien va a usar esta interfaz? No le des más de lo que pide. Interfaces finas, clases felices.
Espero que se haya entendido ejeejjeje EA nos beermos! 🍻
Pon a prueba lo aprendido
1. ¿Quién formuló el Principio de Segregación de Interfaces?
2. ¿Qué dice el ISP en lenguaje sencillo?
3. ¿Qué problema tenía el sistema de impresoras de Xerox que inspiró el ISP?
4. En el ejemplo del Animal, ¿por qué Dog implementando la interfaz Animal viola el ISP?
5. ¿Cuál es la señal más clara de que estás violando el ISP?
6. ¿Desde qué perspectiva se deben diseñar las interfaces según el ISP?
7. ¿Qué principio SOLID se viola frecuentemente como consecuencia de violar el ISP?
8. ¿Cuál de estos es un error común al aplicar el ISP?