Nei nostri corsi e nei nostri articoli non manchiamo mai di ripetere quanto sia importante, anzi fondamentale, scrivere del codice chiaro, riutilizzabile, flessibile e facile da mantenere. Nella programmazione ad oggetti (OOP) esistono dei concetti fondamentali per ottenere questi risultati: i principi SOLID. Questo termine è stato coniato da un programmatore, Robert Martin, nel suo paper chiamato Design Principles and Design Patterns, pubblicato nel 2000.

Significato di SOLID

Qual’è  il significato della parola SOLID? L’ovvia traduzione è in realtà un acronimo formato dalle iniziali (in inglese) dei seguenti 5 concetti, che andiamo ad analizzare singolarmente:

  • Single responsibility
  • Open/closed
  • Liskov substitution
  • Interface segregation
  • Dependency inversion

Principio della Single responsibility

Il nome di questo primo principio spesso trae in inganno. Si potrebbe presumere che ogni classe debba essere responsabile di effettuare una singola operazione, ma non è così. Possiamo “parafrasare” il concetto con: in una classe dovrebbe essere responsabile solo un attore. Con la parola “attore” intendiamo un utente o un gruppo di utenti che utilizzano quella classe e che quindi sono “responsabili” delle sue eventuali modifiche.

Il concetto è abbastanza astratto: mettiamo il caso che sia l’utente X che l’utente Y debbano accedere alla risorsa A. Questa risorsa ha varie proprietà, alcune che interessano X e altre che interessano Y. Secondo il principio della Single Responsibility sarebbe necessario creare due funzionalità distinte per i due utenti, perché eventuali modifiche richieste da X potrebbero avere effetti su Y che, invece, non ha richiesto nessuna modifica.

Principio Open/closed

Il nome di questo principio può sembrare contraddittorio. In realtà, gli aggettivi Open e Closed si riferiscono a due aspetti diversi del codice, che deve essere “aperto” per essere esteso ma “chiuso” alle modificato . Questo principio ci viene in soccorso specialmente quando le specifiche del nostro progetto cambiano spesso (e, come tutti gli sviluppatori sanno, questo accade fin troppo spesso).

Mettiamo il caso di dover creare una semplice login con username e password. Dopo qualche tempo, ci viene chiesto di aggiungere a questa login base la possibilità di effettuare il login con Facebook. Invece di modificare la classe “originale”, questo principio ci suggerisce di creare una nuova classe per implementare la nuova funzionalità, mantenendo inalterata quella già esistente.

Per questo esempio, la soluzione ottimale è quella di avere un’interfaccia che effettua effettivamente il login e una classe per ogni tipologia: login con username e password, login con Facebook, login con Google e chissà quante altre tipologie potranno aggiungersi in futuro. Quindi il nostro codice può implementare sempre nuove funzionalità ma quelle esistenti sono ben distinte e separate, quindi non c’è il rischio di “spaccare” ciò che già funziona.

Principio della Liskov Substitution

Nel 1987 la scienziata Barbara Liskov dichiarò che gli oggetti in un programma dovrebbero essere sostituibili con istanze dei loro sottotipi senza alterare la correttezza di quel programma.

Cosa vuol dire questo enunciato? Fondamentalmente che le sottoclassi devono essere sostituibili con le loro classi primitive senza causare errori. L’ereditarietà deve essere utilizzata solo se serve davvero e solo se il comportamento di una sottoclasse riflette quello della classe da cui viene estesa, altrimenti meglio prendere altre strade.

Principio della Interface segregation

L’enunciato di questo principio, ideato proprio da Robert Martin, è che molte interfacce specifiche del client sono migliori di un’interfaccia generica. In altre parole, un client non deve essere costretto a implementare un’interfaccia che non la utilizza nella sua interezza, piuttosto meglio creare più interfacce simili tra di loro ma diverse per le funzionalità che si prefiggono. Avere un’unica interfaccia che si occupi di tutte le attività è quindi un errore da evitare.

Principio della Dependency inversion

L’ultimo ma non meno importante principio sostiene che le classi devono dipendere dalle classi astratte e non dalle classi concrete. In altre parole, i moduli di alto livello non dovrebbero mai dipendere da moduli di basso livello. Questo principio è abbastanza criptico nella sua formulazione, cerchiamo di chiarirlo con un esempio classico.

Il nostro sito web utilizza un database MySQL e per effettuare il login abbiamo bisogno di effettuare questa connessione:

class MySQLConnect {

public function connect() {}

}

 

class Login {

private $dbConnect;

public function __construct(MySQLConnect $dbConnect) {

$this->dbConnect = $dbConnect;

}

}

 

Con questo codice, però, saremo sempre costretti ad usare la connessione al database MySQl per effettuare il login. E se in futuro si presentasse la necessità di cambiare tipo di database al quale connetterci?

La soluzione al nostro problema è quella di creare un’interfaccia e implementarla nella classe MySqlConnect. Quindi, invece di passare direttamente un oggetto MySqlConnect alla classe Login, utilizziamo invece  qualsiasi classe che implementa l’interfaccia DbConnectInterface:

interface DbConnectInterface {

public function connect();

}

 

class MySqlConnect implements DbConnectInterface {

public function connect() {}

}

 

class Login {

private $dbConnect;

public function __construct(DbConnectInterface $dbConnect) {

$this->dbConnect = $dbConnect

}

}

 

Conoscere ed applicare i principi SOLID è davvero molto importante per tutti gli sviluppatori. Sono utilizzati in tutto il mondo per creare un buon codice utilizzando tecniche di progettazione moderne e funzionali, rispettando gli standard del nostro settore, è essenziale utilizzarli.

Implementarli potrebbe all’inizio sembrare piuttosto difficile ma lavorare regolarmente con essi e comprendere le differenze tra il codice che è conforme ai principi SOLID e il codice che non lo è contribuisce a rendere più semplice ed efficiente il nostro lavoro.