La programmazione ad oggetti OOP

In questo articolo tratteremo la OOP, o meglio conosciuta come programmazione orientata agli oggetti, un insieme di principi ai quali ci si attiene durante la fase di progettazione del software. Preparatevi a scoprire quindi i punti di forza ed i vantaggi di questo paradigma di programmazione nato nel 1967.

Che cosa è la programmazione ad oggetti?

È una filosofia, un pensiero, che fa riferimento a concetti generali senza i quali è impossibile implementare un software. Conoscerla significa sapere programmare in java,C++, C# e PHP: se si conoscono le regole su cui si fonda la OOP, è possibile programmare in qualsiasi linguaggio.

Molto rilevante è anche il tipo di approccio dal punto di vista della collaborazione tra programmatori, poiché ogni sviluppatore può costruire in totale autonomia un singolo oggetto seguendo delle linee guida prestabilite assieme agli altri, e poter procedere in parallelo agli altri. Questo è anche uno dei motivi per cui la programmazione ad oggetti è una filosofia molto apprezzata dalle aziende.

Classi ed Oggetti

  • Classe = Elemento che racchiude le caratteristiche comuni a tutti gli oggetti; ossia è possibile vederla come un insieme di attributi privi di un valore specifico, ossia la definizione delle caratteristiche stesse di un qualcosa.
    Ipotizziamo una classe di nome Matita con attributi quali la marca e la durezza.
  • Oggetto o Istanza = Ogni qualvolta queste caratteristiche indicate dalla classe assumono dei valori precisi. Ad esempio l’oggetto di classe Matita chiamato “Matita_disegno_tecnico” ha le seguenti caratteristiche definite: marca standler; durezza H3.

Possiamo avere più oggetti di una stessa classe quindi, ma avere un oggetto che appartiene a più classi spesso significa avere del codice non ottimizzato, ma ci sono delle eccezioni. Test di debug, prove o per funzionalità particolari di un determinato programma.

Un ulteriore esempio della distinzione tra classi ed oggetti può essere il seguente:

  • Classe Persona, con i seguenti attributi: nome, cognome, altezza, peso, data di nascita;
  • Oggetto della classe Persona Persona1 con le seguenti proprietà: nome Mario, cognome Rossi, altezza 1.70m, peso 65kg, data di nascita 15/6/61.

L’oggetto Persona1 appartiene alla classe Persona poiché in fase di progettazione è stato previsto che l’insieme dei valori necessario a definirlo facessero parte appunto di quella determinata classe e non casuali.

Esempio di codice:

-Java e c#-

//dichiarazione della classe Matita
public class Matita {
   private String marca;
   private String durezza;
}
//creazione di un oggetto della classe
Matita matita = new Matita ( );

-Php-

//dichiarazione della classe Matita
//dichiarazione della classe Matita
public class Matita {
   class Matita {
       private String marca;
       private $marca;
       private String durezza;
       private $durezza;
   }
}

 //creazione di un oggetto della classe
 //creazione di un oggetto della classe
 Matita matita = new Matita ( );
 $matita = new Matita ( );

L’Incapsulamento nella programmazione ad oggetti

Leggendo il precedente esempio di codice avrete sicuramente notato alcune parole interessanti, la cui traduzione nel nostro linguaggio naturale dovrebbe essere sufficientemente chiara: public e private.

Cosa sto cercando di dirvi?
Nella programmazione ad oggetti a volte bisogna evitare che alcuni attributi siano alterabili dall’esterno, questo per rendere alcune caratteristiche invariate, salvo non si usino procedure o comandi ad Hoc. Ossia i metodi.

Riconducendoci all’esempio della matita: Chi ci assicura che nella caratteristica durezza verrà inserito un valore valido, se il codice deve passare per le mani di diversi programmatori?
Utilizzando un metodo apposito, sarà possibile far inserire nell’attributo durezza un valore corretto, od impedire l’esecuzione della procedura ed eviterà il passaggio di parametri illegali.

Un’altra definizione di incapsulamento nella programmazione ad oggetti è quella- anche se un pò ricorsiva- di valori incapsulati nell’oggetto e accessibili tramite i metodi.

Generalmente i metodi si dividono in due categorie:

  • Getter: sono quelli che di solito consentono di leggere i valori di una caratteristica ed iniziano con get.
  • Setter: invece questa è la branca dei metodi che in genere danno il valore ad un determinato attributo; spesso iniziano con set.

Ecco un esempio in Java:

//dichiarazione classe matita
public class Matita {
     private String marca;
     private String durezza;
     public String getMarca ( )  {
         return Marca; 
     }
     public String getDurezza ( ) {
         return Durezza
     }
     public void setMarca (string marca) {
         this.marca=marca;
     }
     public void setDurezza ( string durezza) {
        this.durezza=durezza;
     }
}

Ereditarietà

Ora mettiamo il caso di aver creato due distinte classi, una che si chiama autoveicolo ed una che si chiama camion. Entrambe hanno certi attributi ed ovviamente dei metodi, ma a ben pensarci… Il camion è un autoveicolo, a grandi linee. Quindi perché non fargli condividere delle caratteristiche con la classe a lui affine?
Anzi nella programmazione ad oggetti, in questo caso sarebbe più proprio parlare di ereditarietà poiché la classe camion eredita da quella autoveicolo sia metodi che attributi.

Questa particolare proprietà della programmazione ad oggetti nasce per avere un risparmio di codice laddove coesistano due o più classi che hanno attributi comuni e di cui si può concettualmente stabilire una gerarchia.

Ora utilizzando un linguaggio più tecnico possiamo definire come:

  • Classe che eredità: sottoclasse;
  • Classe che fornisce: superclasse;

In aggiunta esistono alcune regole generali con le quali approcciarsi a questo aspetto del paradigma ad oggetti, curiosi di sapere quali? 😛

  • “Se due o più classi della nostra applicazione condividono alcune caratteristiche, sarebbe opportuno riflettere sulla possibilità di creare una superclasse con tali caratteristiche e di rendere le nostri classe, sottoclassi di tale superclasse.”
  • Regola is-a
    “Dopo avere trovato due o più classi della nostra applicazione che condividono alcune caratteristiche per potere creare una superclasse dalla quale farle ereditare, dobbiamo chiederci se le ipotetiche sottoclassi “sono” anche una superclasse”. (auto e penna condividono caratteristica come colore → auto è (is-a) veicolo? Ok / penna è (is-a) veicolo?
    No => penna no sottoclasse di veicolo.

Polimorfismo

Ora che vi ho mostrato le regole generali dell’ereditarietà ed alcuni principi della programmazione ad oggetti siete pronti a fare il passo successivo, assimilando un concetto portante di questa scuola di pensiero, e che ammetto di aver trovato leggermente ostico; quindi attenti.

Sfruttando il meccanismo dell’ereditarietà possiamo quindi immaginare le superclassi come una definizione generale degli oggetti che vogliamo descrivere, e le varie sottoclassi delle specializzazioni in cui riusciamo ad avere un maggior grado di precisione nell’elencare le caratteristiche proprie di ciascuna classe di oggetti, risparmiandoci ogni volta di elencare quelle comuni. Ma… se vi fermate a riflettere; ed i metodi?

Ipotizziamo di avere una superclasse autoveicolo e due sottoclassi: berlina e utilitaria.

Sono entrambe autoveicoli per definizione e condividono attributi quali le ruote, direzione, verso, colore, motore, marcia, ecc…

Ecco: soffermiamoci un momento sulle marce: le berline di grande cilindrata oggi giorno arrivano ad avere fino a sei/sette marce, le utilitarie generalmente una in meno. E non pensiamo ai fuoristrada od i camion, anch’essi autoveicoli con un diverso sistema di marce.Se quindi il nostro scopo è quello di dover realizzare un metodo cambia_marcia rischieremo di aggiungere un grosso volume di codice ridondante per ciascuna sottoclasse che usa un sistema diverso.

Fortunatamente i programmatori sono una categoria di persone che nel proprio masochismo, possono definirsi essenzialmente pigri e pieni di necessità, e come ha detto qualcuno “la necessità è la madre della creatività”, quindi tranquillizzatevi: c’è una soluzione per evitare di realizzare metodi specifici per ciascuna classe che condivide un attributo.

Il principio è quello del polimorfismo ossia del rendere i metodi flessibili a seconda delle classi e degli oggetti su cui devono operare, aggiungendo quindi più implementazioni di una stessa funzionalità che si differenziano a seconda del caso. Bada! Nella programmazione ad oggetti è importantissimo avere chiaro cosa si vuole fare e come, altrimenti si potrebbero implementare in modo confusionario le varie implementazioni, quindi prima pensa al come fare e poi scrivi il codice. È un consiglio per esperienza personale 😉

Ritornando al nostro esempio con i vari autoveicoli, immaginiamo di voler realizzare il metodo InserisciMarcia, che altro non fa che aggiungere una marcia a seconda del veicolo indicato, utilizzando una funzione switch – case:

  function inserisciMarcia( )  {                    
        Switch (Veicolo.tipo)                          
       Case ' Auto' 
          Veicolo.inserisciMarciaAuto( )       
       Case 'Moto'
          Veicolo.inserisciMaricaMoto( )
       Case 'Camion'
           Veicolo.inserisciMarciacamion ( )
       End Switch
  }

A seconda del tipo di veicolo il metodo utilizzerà la funzione appropriata tramite un unico modulo di codice, che può essere condiviso tra le varie sottoclassi.

Veicolo veicolo = new Auto
Veicolo.inserisciMarcia ( )

Grazie alla struttura della funzione, non ci dovremo preoccupare di specificare il metodo per la classe Auto, ma riconoscerà da solo quale sia il caso giusto ed utilizzerà la procedura appropriata, permettendo una maggiore flessibilità nel codice senza il bisogno di dover aggiungere ad ogni sottoclasse un metodo simile.


 

Relazioni tra classi

Perché creare diverse classi ed oggetti se poi non è possibile avere interazioni tra esse? Ecco che così vi illustro un aspetto importante della programmazione ad oggetti; ossia le relazioni tra le varie classi che compongono il nostro software. Come detto in principio, questa è una filosofia che apprezza molto la modularità, cioè il poter dividere un problema in sottoproblemi più semplici da risolvere contemporaneamente; consentendo a più persone di lavorare allo stesso progetto senza dover preoccuparsi di ciò che fanno gli altri. Alcuni esempi di relazioni tra classi sono:

        • Aggregazione: “fa parte di” e l’oggetto ha significato anche da solo (hw in un computer).

 

 

      • Composizione: le parti che compongono l’oggetto non hanno significato senza. (ramo).

 

 

      • Associazione: definisce i rapporti (con nome, ruolo e molteplicità) tra oggetti, che non sono uno parte dell’altro.

 

Ovviamente l’ereditarietà ed il polimorfismo sono esempi di relazioni tra classi, quindi questo aspetto non dovrebbe essere difficile da comprendere intuitivamente; ma in ambito professionale esistono dei criteri con cui si valuta il codice prodotto, ebbene, il loro nome è:

        • Accoppiamento: fa riferimento a legami tra classi differenti, che se dipendono l’una dall’altra, esse sono accoppiate. Per una buona qualità del codice bisogna ottenere un basso accoppiamento in modo tale da averne una buona comprensione e non andare a reperire continuamente dati da classi associate. Inoltre le modifiche avranno poche ripercussioni su altre classi.

 

 

      • Coesione: stabilisce i compiti per i quali una classe è stata creata: più una classe ha responsabilità stretta ad un solo compito, più il valore della coesione è alto. Applicabile sia a classi che a metodi. Per avere un codice di buona qualità nella programmazione ad oggetti; deve esserci un’alta coesione, in modo da semplificare la comprensione dei compiti delle singole classi e dei singoli metodi.

 

 

Classi astratte

Può capitare il caso in cui ad esempio si sia realizzata una superclasse così generale da non poter essere implementabile in nessun oggetto; ossia che sia così aspecifica da non essere utile da sola in alcun caso reale, ed in tal caso si parla della realizzazione di una classe astratta. Una classe astratta nella programmazione ad oggetti, per definizione non può essere utilizzata per generare oggetti, ma può solo essere estesa alle varie sottoclassi che permetteranno la creazione delle varie implementazioni reali. Inoltre i metodi delle classi astratte sono privi di corpo, ossia sono delle definizioni generali che ogni sottoclasse può interpretare come più opportuno per lo specifico caso, anche se talvolta possono esistere anche dei metodi già implementati all’interno di una classe astratta; ma in tale evenienza la sottoclasse potrebbe sovrascriverne il corpo per intero o parzialmente; generando del codice override.

public abstract class Animale {
     public abstract void dorme ( ) ; 
     public abstract void mangia ( );
}
/*Classe animale dichiara due metodi astratti, 
 riconsocibili dal ; e dall' assenza della { => classi 
 concrete (cane, gatto) devono scrivere codice dei
 metodi. ---- > Esempio classe cane:*/

 //estensione della classe astratta animale e dei 
 //suoi metodi
 public classe Cane extends Animale { 
        public void dorme ( ) {
             System.out.println (“il cane….”) ;
        }     //stessa cosa per mangia

Le interfaccie

Oltre alle classi astratte, esistono elementi generici come le interfacce, ossia moduli di codice contenenti l’intestazione di diversi metodi ma non la loro implementazione, che andrà invece sviluppata all’interno della classe reale che utilizza una data interfaccia. Per queste ultime, nella programmazione ad oggetti, non è possibile costruire il corpo dei metodi- come detto- ma può avere degli attributi ed è consentito aggiungerne più di una ad una singola classe.

Generalmente i nomi delle interfacce sono costituiti da aggettivi preceduti dalla vocale I maiuscola, per renderle facilmente riconoscibile all’interno del proprio codice.

Ecco alcuni esempi di stesura di un’interfaccia:

 //definizione dell'interfaccia     
  public interface Istampabile  { 
     public void print ( ) ;              
 }
 // implementazione dell'interfaccia da parte dell
 // classe Libro  
  public class Libro implements IStampabile {
     public void print ( )   {
         System.out.println (“Sto stampando un libro”);
     }
 }

 

Per iniziare…

Lascia una risposta

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *