L’open-closed principle Γ¨ il primo principio di SOLID.

Definizione

Il principio di single responsibility afferma che β€œuna classe deve avere uno e uno solo motivo per cambiare” o anche β€œuna classe deve essere responsabile di un unico attore”. Genericamente una classe dovrebbe avere una sola responsabilitΓ  completamente incapsulata al suo interno, dedicarsi esclusivamente a quella e conseguentemente cambiare solo se tale responsabilitΓ  cambia e per nessun altro motivo.

Inoltre una classe dovrebbe rispondere ad un solo β€œattore” e per attore si intende una entitΓ  astratta che ha delle necessitΓ  all’interno del mio software.

PerchΓ© implementarlo

Come insegna uncle Bob, i requisiti di un software continuano a cambiare nel tempo; conseguentemente devono essere modificare le responsabilitΓ  di almeno una classe. Maggiori sono le responsabilitΓ  della classe in questione piΓΉ spesso vi sarΓ  il bisogno di cambiarla.

Il problema Γ¨ che se una classe ha piΓΉ di una responsabilitΓ , la modifica di parte del codice di una si riflette automaticamente sull’altra, in quanto appartenente alla stessa classe.

Per esempio se la classe fa X e Y e per modificare X devo, per esempio, aggiungere un parametro al costruttore della classe, questa modifica porterΓ  anche a dei cambiamenti sulle classi che la utilizzano per la responsabilitΓ  Y che non centra nulla con X.

PiΓΉ una classe ha responsabilitΓ  piΓΉ spesso dovrΓ  essere modificata e di riflesso verranno modificate le classi che dipendono da questa ultima, quindi tutte le sue dipendenze.

Queste dovranno essere ricompilate e subiranno delle modifiche anche se non centrano nulla con la responsabilitΓ  che Γ¨ effettivamente cambiata.

Per evitare tutti questi problemi Γ¨ indispensabile che ogni classe abbia una e una sola responsabilitΓ  e, come dice uncle Bob, uno e un solo motivo per cambiare.

Non esagerare

Alcuni programmatori prendono il principio all’estremo e arrivano al punto di implementare classi con una sola funzione. Questo ovviamente porta ad una proliferazione di classi che porta solo confusione all’interno del codice.

Il principio di singola responsabilitΓ  deve essere usato con criterio secondo la propria esperienza.

Come capire se una classe viola il SRP

Ci sono varie euristiche per capire se una classe viola il SRP:

  • Il nome della classe contiene parole come Manager o Processor o comunque nomi che indicano che tale classe gestisce varie cose;
  • Non Γ¨ semplice definire il nome della classe stessa (che dovrebbe invece descrivere l’unica responsabilitΓ  della classe) o il nome che vorremmo dare contiene and;
  • Presenta dei metodi privati che si applicano solo ad un piccolo sottoinsieme di metodi della classe;
  • La classe non Γ¨ coesa;

Esempi

Esempio 1

Assumiamo di avere una classe β€œDipendente” con tre metodi:

  • CalcolaPaga: fornisce la paga da dare al dipendente
  • ReportHours: indica il numero di ore lavorate
  • Save: Salva i dati del dipendente sul database

Questi tre metodi sono responsabili di tre attori differenti: il primo metodo viene usato dal settore contabilitΓ , il secondo dal settore risorse umane e il terzo al direttore tecnico.

Avendo i tre metodi nella stessa classe che dipendono da attori diversi puΓ² capitare che una modifica delle esigenze dell’uno si rifletta, senza volerlo, anche sugli altri due.

Per esempio qualora il metodo ReportHours e CalcolaPaga utilizzino lo stesso metodo sottostante per calcolare il numero di ore lavorate, una modifica a questo metodo richiesta dall’ufficio paghe porta, di riflesso, ad una modifica non richiesta al metodo ReportHours gestito dall’ufficio risorse umane.

La soluzione Γ¨ porre ogni metodo in una sua classe dedicata e la classe Dipendente conterrΓ  solo le interfacce di queste classi, senza quindi alcuna riga di codice al suo interno. Il calcolo delle ore lavorate sarΓ  quindi completamente scorporato.

Esempio 2

// Violates SRP
public class UserService
{
    public void AddUser(string userName)
    {
        // Add user to the database
    }
 
    public void SendEmail(string email)
    {
        // Send an email to the user
    }
}
 
// Adheres to SRP
public class UserService
{
    public void AddUser(string userName)
    {
        // Add user to the database
    }
}
 
public class EmailService
{
    public void SendEmail(string email)
    {
        // Send an email
    }
}