LโISP si concentra sulla struttura e lโorganizzazione delle interfacce allโinterno di unโapplicazione, promuovendo l'uso di interfacce piรน piccole e specifiche invece di interfacce grandi e generiche.
LโISP รจ stato introdotto da Robert C. Martin e afferma che "le classi che implementano interfacce non dovrebbero essere costrette a implementare metodi di cui non hanno bisogno". In altre parole, le interfacce dovrebbero essere suddivise in componenti piรน piccoli e specifici per garantire che le classi che le implementano abbiano solo le funzionalitร di cui hanno effettivamente bisogno.
Seguendo lโISP, gli sviluppatori possono evitare di creare interfacce โgrasseโ che combinano molte responsabilitร diverse, rendendo cosรฌ piรน difficile la manutenzione e la modifica del codice. Invece, lโISP promuove la creazione di interfacce โsnelleโ che hanno una singola responsabilitร o un gruppo di responsabilitร strettamente correlate.
Questo principio รจ strettamente legato agli altri principi SOLID, in particolare al Principi SOLID. Single responsibility (SRP) e al Principi SOLID. Dependency Inversion Principle (DIP). LโSRP si concentra sulla divisione delle classi in modo che abbiano una singola responsabilitร , mentre lโISP si concentra sulla divisione delle interfacce. Il DIP, dโaltra parte, suggerisce che le classi dovrebbero dipendere da astrazioni piuttosto che da implementazioni concrete, il che รจ facilitato dallโuso di interfacce specifiche.
Vantaggi
- Facilitร di manutenzione: suddividere le interfacce in parti piรน piccole e specifiche facilita il processo di manutenzione del codice. In questo modo, รจ possibile apportare modifiche a unโinterfaccia senza influenzare altre parti del codice che non utilizzano la parte modificata.
- Minore accoppiamento: lโISP riduce lโaccoppiamento tra le classi, poichรฉ le classi dipendono solo dalle interfacce che utilizzano effettivamente. Questo rende il sistema piรน flessibile e modulare.
- Maggiore coesione: LโISP promuove una maggiore coesione tra le classi e le interfacce, poichรฉ ciascuna interfaccia ha una responsabilitร ben definita. Avere interfacce piรน specifiche contribuisce a mantenere il codice organizzato e comprensibile.
- Sostituibilitร : Seguendo lโISP, le classi diventano piรน intercambiabili, poichรฉ le nuove implementazioni possono essere facilmente sostituite senza interrompere il codice esistente. Questo incoraggia lo sviluppo di componenti riutilizzabili.
Limiti
- Aumento del numero di interfacce: Come menzionato in precedenza, lโadozione dellโISP puรฒ portare ad un aumento del numero di interfacce nel sistema. Un numero eccessivo di interfacce puรฒ aumentare la complessitร e rendere il codice piรน difficile da gestire se non viene mantenuto correttamente.
- Sovraprogettazione: Seguire lโISP puรฒ portare alla sovraprogettazione del sistema. Creare interfacce troppo specifiche e granulari puรฒ rendere il codice piรน difficile da mantenere e capire, soprattutto se i membri dellโinterfaccia sono strettamente correlati tra loro (KISS).
- Maggiore curva di apprendimento: Con un numero maggiore di interfacce, gli sviluppatori potrebbero avere difficoltร a capire la struttura e le relazioni tra le classi e le interfacce. Questo puรฒ portare a una maggiore curva di apprendimento per i nuovi membri del team di sviluppo.
- Difficoltร nella gestione delle dipendenze: Sebbene lโISP riduca lโaccoppiamento tra le classi, la gestione di un gran numero di interfacce puรฒ complicare la gestione delle dipendenze allโinterno del sistema. Gli sviluppatori potrebbero dover prestare maggiore attenzione a come le dipendenze sono organizzate e iniettate tra le classi, il che potrebbe richiedere un investimento maggiore di tempo e sforzo nella progettazione del sistema.
- Potenziale ridondanza del codice: Lโadozione dellโISP potrebbe portare a una potenziale ridondanza del codice in alcuni casi. Quando diverse interfacce hanno funzionalitร simili o condividono metodi comuni, gli sviluppatori potrebbero finire per duplicare il codice tra diverse classi che implementano tali interfacce. Questo puรฒ rendere il codice meno efficiente e aumentare il rischio di errori e inconsistenze.
Esempi
Esempio 1
Immaginiamo di avere un sistema che gestisce diversi tipi di stampanti, alcune delle quali supportano anche la scansione. Un approccio non conforme allโISP potrebbe utilizzare unโinterfaccia generale come questa:
public interface IDevice
{
void Print();
void Scan();
}
Tuttavia, seguendo lโISP, divideremmo lโinterfaccia in interfacce piรน specifiche:
public interface IPrinter
{
void Print();
}
public interface IScanner
{
void Scan();
}
Ora, le classi che implementano solo la funzionalitร di stampa o scansione possono implementare lโinterfaccia appropriata, senza dover implementare metodi non necessari:
public class Printer : IPrinter
{
public void Print()
{
// Logica di stampa
}
}
public class Scanner : IScanner
{
public void Scan()
{
// Logica di scansione
}
}
public class AllInOnePrinter : IPrinter, IScanner
{
public void Print()
{
// Logica di stampa
}
public void Scan()
{
// Logica di scansione
}
}
In questo esempio, possiamo vedere come lโISP renda il codice piรน pulito e flessibile. Le classi Printer
e Scanner
implementano solo le interfacce pertinenti alle loro funzionalitร , evitando lโimplementazione di metodi inutili.
La classe AllInOnePrinter
, che supporta sia la stampa che la scansione, implementa entrambe le interfacce.
Esempio 2
In questo caso, stiamo suddividendo lโinterfaccia piรน grande in parti piรน piccole e piรน specifiche per garantire che le nostre classi implementino solo ciรฒ di cui hanno effettivamente bisogno.
// Violates ISP
public interface IWorker
{
void Work();
void Eat();
}
public class HumanWorker : IWorker
{
public void Work()
{
// Working
}
public void Eat()
{
// Eating in the break
}
}
public class RobotWorker : IWorker
{
public void Work()
{
// Working much more efficiently
}
public void Eat()
{
throw new NotImplementedException("Robots do not eat.");
}
}
// Adheres to ISP
public interface IWork
{
void Work();
}
public interface IEat
{
void Eat();
}
public class HumanWorker : IWork, IEat
{
public void Work()
{
// Working
}
public void Eat()
{
// Eating in the break
}
}
public class RobotWorker : IWork
{
public void Work()
{
// Working much more efficiently
}
}