Scopri i Principi SOLID: Fondamenti per uno Sviluppo con Swift Efficiente

banner

I principi SOLID sono cinque principi fondamentali della programmazione orientata agli oggetti, formulati da Robert C. Martin (Uncle Bob), per scrivere codice più leggibile, manutenibile ed estendibile.

Chi è Robert C. Martin

Robert C. Martin, noto come Uncle Bob, è un ingegnere del software, autore, consulente e formatore nel campo dello sviluppo software. È uno dei principali esponenti della programmazione orientata agli oggetti e dello sviluppo Agile, ed è famoso per aver formalizzato i principi SOLID, fondamentali per scrivere codice pulito e manutenibile. Nato il 5 Dicembre del 1952 Uncle Bob ha scoperto la programmazione a dodici anni. Oggi continua a lavorare dal sito Cleancoder.com e a proporre i suoi principi e insegnamenti tramite il sito Clean Coders. Tiene conferenze e corsi e ha scritto nel tempo libri che sono pilastri della buona programmazione, come Clean Code, Clean Architecture, Clean Agile e Clean Craftsmanship. E’ soprannominato Uncle Bob, zio Bob in quanto nel mondo anglosassone c’è anche un modo di dire che recita Bob’s your uncle, Bob è tuo zio, per dire è tutto ok.

Il nome SOLID è un acronimo che rappresenta cinque concetti chiave:

    1. S - Single Responsibility Principle (SRP) - Principio di Responsabilità Unica
    1. O - Open/Closed Principle (OCP) - Principio Aperto/Chiuso
    1. L - Liskov Substitution Principle (LSP) - Principio di Sostituzione di Liskov
    1. I - Interface Segregation Principle (ISP) - Principio di Segregazione delle Interfacce
    1. D - Dependency Inversion Principle (DIP) - Principio di Inversione delle Dipendenze

Oggi affronteremo il primo dei principi SOLID, il Principio di Responsabilità Unica.

Single Responsibility Principle (SRP)

Una classe dovrebbe avere una e una sola ragione per cambiare. (“A class should have only one reason to change.”) In altre parole: Una classe dovrebbe avere una sola responsabilità.

Ritengo sia uno dei principi più importanti ma anche forse il più bistrattato, il meno “rispettato”.

Un errore comune nello sviluppo è la creazione di classi che gestiscono più responsabilità.

Ogni classe dovrebbe avere un singolo scopo ben definito. Ogni classe, modulo o funzione nel codice deve avere una sola responsabilità e un solo motivo per essere modificata. Se una classe ha più responsabilità, allora ha più di un motivo per cambiare, e questo può portare a problemi di manutenibilità.

Perché è importante il SRP?

    1. Riduce la complessità – Se una classe ha solo una responsabilità, è più facile da capire.
    1. Migliora la manutenibilità – Se una parte del codice deve cambiare, si modifica solo il punto specifico, senza effetti collaterali.
    1. Favorisce il riuso – Classi più piccole e mirate possono essere riutilizzate più facilmente in altri contesti.
    1. Facilita il testing – Una classe con una sola responsabilità è più facile da testare.

Esempio di errata applicazione del principio:

class DatabaseManager {
    static let shared = DatabaseManager ()
    
    private init() {}
    
    func getUsers() -> [User] {
        // Send API request for get all users
    }
    
    func saveDataToDB(users: [User]) {
        // Save user data into database
    }
}

Questa classe “DatabaseManager” si occupa di gestire il salvataggio degli utenti nel database ma al tempo stesso effettua una chiamata API per recuperare gli utenti.

Quali sono i problemi in questa classe DatabaseManager?

  • Gestisce una chiamata di rete (getUsers),
  • Gestisce la persistenza dei dati salvandoli nel database (saveDataToDB).

Se cambiano il metodo di salvataggio nel database o la modalità di recupero degli utenti, dovrai modificare questa classe, aumentando il rischio di introdurre bug.

Esempio di corretta applicazione del principio:

class DatabaseManager {
    static let shared = DatabaseManager ()
    
    private init() {}
    
    func saveDataToDB(users: [User]) {
        // Save user data into database
    }
}
class NetworkManager {
    static let shared = NetworkManager ()
    
    private init() {}
    
    func getUsers() -> [User] {
        // Send API request for get all users
    }
}

In questo caso ogni classe ha una specifica responsabilità.

L’esempio esposto viola inoltre altri principi SOLID, in particolare:

Open/Closed Principle (OCP)

Principio “Aperto/Chiuso”: una classe dovrebbe essere aperta all’estensione ma chiusa alla modifica.

Questo vuol dire che:

  • Se volessimo cambiare la modalità di persistenza dei dati (es. cambiare tipologia di database), dovremmo modificare direttamente la classe DatabaseManager.
  • Non ci sono interfacce o protocolli che permettano di estendere il comportamento senza cambiare il codice esistente.

Immagina di avere una classe per gestire diverse tipologie di notifiche:

enum TipoNotifica {
    case email
    case push
    case sms
}

class GestoreNotifiche {
    func inviaNotifica(tipo: TipoNotifica, messaggio: String, utente: String) {
        if tipo == .email {
            // Logica per inviare una email
            print("Invio email a \(utente): \(messaggio)")
        } else if tipo == .push {
            // Logica per inviare una notifica push
            print("Invio notifica push a \(utente): \(messaggio)")
        } else if tipo == .sms {
            // Logica per inviare un SMS
            print("Invio SMS a \(utente): \(messaggio)")
        }
    }
}

Ogni volta che devi aggiungere un nuovo tipo di notifica (es. WhatsApp), devi modificare la classe GestoreNotifiche, violando l’OCP. Questo vuol dire che la classe diventa sempre più grande e complessa, rendendo difficile la manutenzione.

Per rendere il codice coerente con il principio di cui sopra, si potrebbe modificare il codice in questo modo:

protocol Notificabile {
    func invia(messaggio: String, utente: String)
}

class EmailNotifica: Notificabile {
    func invia(messaggio: String, utente: String) {
        // Logica per inviare una email
        print("Invio email a \(utente): \(messaggio)")
    }
}

class PushNotifica: Notificabile {
    func invia(messaggio: String, utente: String) {
        // Logica per inviare una notifica push
        print("Invio notifica push a \(utente): \(messaggio)")
    }
}

class SMSNotifica: Notificabile {
    func invia(messaggio: String, utente: String) {
        // Logica per inviare un SMS
        print("Invio SMS a \(utente): \(messaggio)")
    }
}

class GestoreNotifiche {
    func inviaNotifica(notifica: Notificabile, messaggio: String, utente: String) {
        notifica.invia(messaggio: messaggio, utente: utente)
    }
}

Per aggiungere un nuovo tipo di notifica (es. WhatsApp), crei semplicemente una nuova classe che implementa il protocollo Notificabile senza modificare il codice esistente. Il codice è più modulare, flessibile e facile da testare.

Liskov Substitution Principle (LSP)

Il Liskov Substitution Principle riguarda l’ereditarietà. Nell’esempio proposto, essendo una classe singleton, non è possibile creare una sottoclasse di DatabaseManager per cambiarne il comportamento, e quindi si viola questo principio.

Interface Segregation Principle (ISP)

I client non dovrebbero essere costretti a dipendere da interfacce che non usano.

In questo caso:

  • La classe espone metodi che potrebbero non essere sempre necessari per chi la utilizza.
  • Se un client ha bisogno solo di leggere la lista utenti, si trova comunque a dipendere anche da metodi di salvataggio sul database.
class DatabaseManager {
    static let shared = DatabaseManager ()
    
    private init() {}
    
    func saveDataToDB(users: [User]) {
        // Save user data into database <--- metodo non per forza necessario a tutti i client
    }
}

Dependency Inversion Principle (DIP)

I moduli di alto livello non dovrebbero dipendere da moduli di basso livello.

Per correggere l’esempio, entrambi dovrebbero dipendere da astrazioni (es. protocolli).

In questo caso: il DatabaseManager dipende direttamente dall’implementazione concreta (ossia database e API). Non ci sono protocolli o astrazioni. Non è facile sostituire il database o la fonte dei dati (es. per fare mocking nei test).

Conclusioni

In sintesi, i principi SOLID, introdotti da Robert C. Martin, sono un insieme di linee guida fondamentali per lo sviluppo di software orientato agli oggetti, mirati a creare codice più leggibile, manutenibile, testabile e riutilizzabile.

Applicare questi principi, come il Single Responsibility Principle che prevede che ogni classe abbia una sola responsabilità, porta a una maggiore coesione del codice, riducendo la complessità e facilitando la manutenzione nel tempo. In progetti di grandi dimensioni, l’adozione dei principi SOLID si traduce in una migliore scalabilità, tempi di sviluppo più rapidi e una maggiore adattabilità ai cambiamenti.

E tu, quali sfide hai incontrato nell’implementazione dei principi SOLID e come li hai superati?

Post correlati

TheRedCode.it - Il mondo #tech a piccoli #bit

Partners

Community, aziende e persone che supportano attivamente il blog

Logo di Codemotion
Logo di GrUSP
Logo di Python Milano
Logo di Schrodinger Hat
Logo di Python Biella Group
Logo di Fuzzy Brains
Logo di Django Girls
Logo di Improove
Logo del libro open source
Logo di NgRome
Logo de La Locanda del Tech
Logo di Tomorrow Devs
Logo di Coderful
Logo di VueSchool

Non perderti gli ultimi aggiornamenti, iscriviti a TheRedCode Digest!

La tecnologia corre, e tu devi correre più veloce per rimanere sempre sul pezzo! 🚀

Riceverai una volta al mese (o anche meno) con codici sconto per partecipare agli eventi del settore, quiz per vincere dei gadget e i recap degli articoli più interessanti pubblicati sul blog

Ci sto!

#TheRedComics

Edizione di Gennaio - Buon Anno nuovo!

A cura di Sophie Aiello, copy di Chiara Romano

Fumetto di dicembre di Sophie Aiello, Copy di Chiara Romano

Vuoi diventare #tech content creator? 🖊️

Se vuoi raccontare la tua sul mondo #tech con dei post a tema o vuoi condividere la tua esperienza con la community, sei nel posto giusto! 😉

Manda una mail a collaborazioni[at]theredcode.it con la tua proposta e diventa la prossima penna del blog!

Ma sì, facciamolo!