Principio de Segregación de Interfaces (ISP)
¿Qué dice este principio?
El Principio de Segregación de Interfaces (ISP) establece que los clientes no deben estar obligados a depender de servicios que no utilizan.
En otras palabras, se debe dividir la funcionalidad en interfaces más específicas y coherentes, de modo que cada cliente solo implemente o utilice los métodos que realmente necesita.
¿En qué nos beneficia?
- Creamos interfaces más específicas y fáciles de entender.
- Evitamos que los clientes trabajen con recursos innecesarios.
- Probamos únicamente lo necesario, reduciendo complejidad en los tests.
- Ganamos flexibilidad y capacidad de reutilización de métodos y componentes.
¿Qué problemas causa no aplicarlo?
- Los clientes deben manejar o condicionar métodos que no usan.
- Se crean controles innecesarios dentro del código.
- Aumenta la cantidad de código repetido o sin propósito claro.
- Se genera una interfaz demasiado grande y poco coherente, difícil de mantener.
Analogía: el restaurante
Imagina un restaurante donde un solo empleado debe:
- Cocinar
- Limpiar
- Atender a los clientes
- Servir las mesas
Esto genera problemas evidentes:
- El chef no debería hacer entregas a domicilio.
- El mesero no debería cocinar.
- El cajero no necesita lavar platos.
Solución:
Dividir las responsabilidades según el rol de cada empleado:
-
ICocinero
para cocinar -
IConserje
para limpiar -
ICajero
para cobrar
De esta forma, cada persona cumple su función sin verse obligada a realizar tareas innecesarias. Incluso podríamos tener un empleado multifuncional, como un aguatero que además pueda cobrar, simplemente implementando ambas interfaces según sea necesario.
El problema clásico: la impresora
Escenario:
En una oficina existen distintos dispositivos:
- Una impresora simple (solo imprime)
- Una impresora multifuncional (imprime, escanea y envía fax)
- Una máquina de fax (solo envía fax)
Ejemplo incorrecto:
// Interfaz demasiado grande: obliga a implementar todo
public interface IDispositivoOficina
{
void Imprimir(string documento);
void Escanear(string documento);
void EnviarFax(string documento);
void Fotocopiar(string documento);
}
// ImpresoraSimple se ve OBLIGADA a implementar métodos que no usa
public class ImpresoraSimple : IDispositivoOficina
{
public void Imprimir(string documento)
{
Console.WriteLine($"Imprimiendo: {documento}");
}
// Implementaciones innecesarias o con errores
public void Escanear(string documento)
{
throw new NotSupportedException("Esta impresora no escanea");
}
public void EnviarFax(string documento)
{
throw new NotSupportedException("Esta impresora no tiene fax");
}
public void Fotocopiar(string documento)
{
throw new NotSupportedException("Esta impresora no fotocopia");
}
}
// El cliente que solo necesita imprimir depende de toda la interfaz
public class ServicioImpresion
{
private IDispositivoOficina _dispositivo;
public ServicioImpresion(IDispositivoOficina dispositivo)
{
_dispositivo = dispositivo;
}
public void ImprimirDocumento(string doc)
{
_dispositivo.Imprimir(doc);
}
}
¿Por qué esto está mal?
Solemos crear clases que abarcan más de lo necesario, pensando en cubrir todas las posibles funcionalidades desde el inicio. Sin embargo, cuando otros clientes tienen necesidades similares pero no idénticas, terminamos forzando implementaciones innecesarias o poco limpias para poder cumplir con el contrato.
Problemas principales:
- La clase ImpresoraSimple implementa métodos que no necesita.
- El cliente depende de métodos que nunca utilizará.
- Se genera código frágil, con excepciones innecesarias.
- Es difícil de mantener y extender en el tiempo.
Solución correcta
La solución es dividir la interfaz en varias más pequeñas y específicas:
public interface IImpresora
{
void Imprimir(string documento);
}
public interface IEscáner
{
void Escanear(string documento);
}
public interface IFax
{
void EnviarFax(string documento);
}
public interface IFotocopiadora
{
void Fotocopiar(string documento);
}
// Cada clase implementa solo lo que necesita
public class ImpresoraSimple : IImpresora
{
public void Imprimir(string documento)
{
Console.WriteLine($"Imprimiendo: {documento}");
}
}
public class ImpresoraMultifuncional : IImpresora, IEscáner, IFax, IFotocopiadora
{
public void Imprimir(string documento) => Console.WriteLine($"Imprimiendo: {documento}");
public void Escanear(string documento) => Console.WriteLine($"Escaneando: {documento}");
public void EnviarFax(string documento) => Console.WriteLine($"Enviando fax: {documento}");
public void Fotocopiar(string documento) => Console.WriteLine($"Fotocopiando: {documento}");
}
Ahora cada cliente utiliza únicamente la interfaz que necesita, y el código es más claro, mantenible y escalable.
Conclusión
El Principio de Segregación de Interfaces es una herramienta poderosa para mantener un código limpio, modular y adaptable.
Permite organizar mejor las implementaciones, evitando dependencias innecesarias y mejorando la reutilización de componentes.
Aunque su impacto puede parecer mínimo en proyectos pequeños, en sistemas grandes y complejos su aplicación marca una diferencia significativa, haciendo el código más estable, flexible y fácil de evolucionar.
Top comments (0)