TIL - Principio di Segregazione delle Interfacce


Oggi ho letto un articolo facile da capire sul principio di segregazione delle interfacce, chiamato anche ISP1. L’articolo in questione è di Fanis Despoudis.

L’ISP fa parte di una serie di principi che insieme formano il SOLID:

  • S -> SRP (Single Responsability Principle)
  • O -> OCP (Open Closed Principle)
  • L -> LSP (Liskov substitution Principle)
  • I -> ISP (Interface Segregation Principle)
  • D -> DIP (Dependency Inversion Principle)

I clients non dovrebbero esser forzati a dipendere da metodi che non usano

Segregazione dell’interfaccia

In poche parole questo significa che si dovrebbe sempre disegnare le astrazioni in modo tale che i clients possano usare solo i metodi di cui hanno bisogno anziché constringerli ad implementare metodi a loro non utili.

Un piccolo esempio in Go riportato da Fenis è il seguente:

Creiamo una interfaccia User per un ATM che deve gestire le richieste di deposito e prelievo. Pensiamo ad una possibile libreria contenente metodi utili per gestire le operazioni sui byte

type ByteUtils interface {
  Read(...)(....)
  Write(...)(...)
  Trim(...)(...)
}

Il nome, troppo generico, non fornisce alcuna specifica in più questa interfaccia costringe ad implementare i metodi richiesti, se per esempio si bisogno di una differente ottimizzazione da fare sulla parte di trimming, quando leggi in un buffer e, in modo opposto quando scrivi in un buffer, si ha bisogno di scrivere due differenti implemetazioni dell’interfaccia nella quale i metodi Read and Write sono identici portando ridondanze indesiderate e aumentando la complessità. WRONG!

Per ovviare a questo problema possiamo pensare di dividere questa interfaccia paffuta in 3 interfacce più piccole chiamate Interfacce di Ruolo o Role Interfaces, più semplici da gestire, implementare e decisamente più specifiche:

type Reader interface {
  Read(...)(...)
}

type Writer interface {
  Write(...)(...)
}

type Trimmer interface {
  Trim(...)(...)
}

Possiamo persino combinarle per avere risultati ancora più specifici usando le potenzialità del Go

type ReadWriter interface {
  Reader
  Writer
}

type TrimReader interface {
  Trimmer
  Reader
}

Grosse interfacce portano inesorabilmente ad un accoppiamento involontario fra classi e ciò dovrebbe essere evitato, quando si disegna una interfaccia è sempre meglio chiedersi “ho bisogno davvero di tutti i metodi contenuti in essa? Se no, come posso rendere l’interfaccia più piccola?

Questo principio va a braccetto con il Principio di Singola Responsabilità la quale ci permette di avere un codice chiaro ma questo principio merita di esser discusso in un articolo a sé.


  1. ISP