Fondamenti di Object Oriented Programming

_Logo Penso che un buon punto di partenza per la sezione coding sia parlare di programmazione ad oggetti, non solo perché i linguaggi che utilizzo quotidianamente sono C# e VB.NET, ma anche perché OOP significa analizzare e progettare le applicazioni seguendo ben definiti principi.

Partiamo subito dall'obiettivo che bisogna porsi quando si affronta la scrittura di un software, sia essa una semplice App o un sistema client-server dalle molteplici funzionalità. L'obiettivo è quello di realizzare un'applicazione ben costruita (adoro il termine well-crafted), flessibile, naturale, mantenibile e testabile.

La programmazione ad oggetti incoraggia e facilità questo compito, grazie anche a principi e pratiche consolidate da anni ma comunque in evoluzione. Posso citare ad esempio: Clean Code, Defensive Programming, Design Patterns, Domain Driven Development... tutti argomenti che tratterò in questa sezione. Non bisogna inoltre dimenticare che per realizzare un software ci vuole organizzazione, soprattutto lavorando in team: qui si apre il vasto mondo delle metodologie Agile.

La cosa fondamentale da comprendere è che tali concetti possono appesantire il lavoro nelle prime fasi ma garantiscono risultati stabili e di qualità. Inoltre possono essere applicati, ovviamente in modo contestualizzato, a progetti di piccole dimensioni così come progetti più grandi, nonché di tipologie diverse: non solo applicazioni desktop, server o web, ma anche App, API e servizi. Tutto ciò che è codice, insomma.

Alla base della programmazione ad oggetti vi sono le classi da cui si creano gli oggetti (istanze di classi). La classe non è altro che la definizione di un particolare tipo di oggetto. Spesso le classi del business layer, quindi di ciò che rappresenta il mondo reale, sono chiamate Business Object. Si tende comunque a definire entità tutto ciò che c'è nel mondo reale e che può essere rappresentato da una classe (il tipico esempio è la classe Customer).

Quindi, come suggerisce il nome, la programmazione ad oggetti ruota intorno alle classi e all'iterazione tra i diversi oggetti. Identificare gli oggetti è uno dei primi passi per creare un software, naturalmente dopo aver raccolto i requisiti. Va detto che anche la raccolta dei requisiti è soggetta a principi e linee guide: quella che più si adegua alle nuove metodologie prevede una continua iterazione tra lo sviluppatore e il committente... situazione che può degenerare se i due ruoli sono svolti dalla stessa persona… :-)).

Vediamo come iniziare a definire e implementare la propria applicazione:

  • Raccogliere i requisiti (chiamati anche business requirements) e analizzare il business problem.
  • Identificare le classi, in particolare identificando i nomi utilizzati per identificare persone, luoghi, sistemi o processi.
  • Definire le proprietà delle entità trovate, quindi i comportamenti di tale entità (in questo caso si analizzano i verbi presenti nei requisiti). Da notare che è un processo abbastanza naturale.
  • Ripensare agli oggetti trovati e quindi applicare il fondamentale principio di singola responsabilità, che si traduce in una classe, uno scopo. Tale principio è fondamentale: ogni classe deve essere ridotta al minimo evitando sovrapposizione tra gli oggetti.
  • Bisogna anche considerare il tempo: non tanto per prevedere il futuro, quanto per verificare se le entità definite vanno bene oggi e anche domani.
  • Definire le relazioni tra oggetti: gli oggetti possono usare o essere composti da altri oggetti.

Bisogna inoltre considerare che progettare e scrivere il proprio applicativo è un processo iterativo: si parte da alcune classi e applicando il principio di singola responsabilità ci si ritrova a crearne altre.

Abbiamo quindi ottenuto una bozza del nostro software con una manciata di classi. Nel definire o per definire meglio quanto estratto dai requisiti dobbiamo applicare i principi base dell'OOP:

  • Astrazione (abstraction): anche se alcuni considerano l'astrazione un concetto più che una tecnica di programmazione, viene spesso citato come primo pilastro dell’OOP. Significa pensare ad una classe focalizzando l'attenzione sul suo scopo semplificando la realtà. In un sistema di contabilità un cliente è considerato un contatto più che una persona nel senso più ampio del termine. Se voglio invece realizzare un prodotto dedicato all’advertisement allora del mio Customer mi serviranno molti più dettagli personali.
  • Incapsulamento (encapsulation): i dati e l'implementazione sono nascosti nella classe e questa espone al mondo un'interfaccia pubblica chiamata contract. In C# avremo ad esempio field privati per i dati, esposti tramite proprietà, così come un insieme di metodi privati di supporto a quelli pubblici. Ciò permette di proteggere e validare i dati, nonché di definire autenticazioni e autorizzazioni. Inoltre espone al mondo solo ciò che al mondo serve, semplificando l’intero sistema.
  • Ereditarietà (inheritance): è una tecnica fondamentale per il riutilizzo del codice, altro principio dell'OOP. In pratica una classe può derivare da un’altra parte delle sue caratteristiche e diventarne una specializzazione. Vedremo tale principio parlando di relazione tra oggetti nel prossimo post dedicato al coding.
  • Poliformismo (polymorphism): letteralmente significa dalle molte forme e in C# significa poter implementare un metodo in modo distinto in classi diverse, utilizzando però lo stesso nome. Il primo esempio di poliformismo è quello legato alla derivazione: il metodo ToString è comune a tutti gli oggetti perché tutti gli oggetti derivano da Object. Ma ovviamente posso specializzare ToString in ogni mia classe tramite overriding e nonostante questo l'uso del metodo non cambia: è uniforme. Cambia l'implementazione. In C# posso inoltre implementare una stessa interfaccia in più classi distinte, ma i membri dell’interfaccia non cambiano la loro signature: cambia l’implementazione a seconda della classe istanziata.

Una volta estratte le prime classi bisogna porsi una domanda: ogni classe definita ha un unico scopo? Oppure ha troppe responsabilità? Un tipico errore è quello di includere la parte di accesso ai dati direttamente nelle classi entità, oppure di rendere predominante una classe entità rispetto alle altre e gestire da questa uno o più processi che interagiscono con più classi. Vedremo che in questi casi è meglio creare classi aggiuntive: alcune dedicate all'accesso ai dati e altre dedicata ai processi. Nel primo caso applicheremo ciò che viene chiamato Repository Pattern, quindi uno dei tanti design pattern disponibili. Di che cosa si tratta? Di best practices, linee guida di comprovata utilità.

Tag: OOP