La programmazione ad oggetti, basi e concetti fondamentali - parte 3

La programmazione ad oggetti, basi e concetti fondamentali - parte 3

Di Francesco Grotta


programmazione ad oggetti classi astratte interfacce

Nei precedenti articoli abbiamo visto i pilastri della programmazione ad oggetti:

  1. Incapsulamento
  2. Astrazione
  3. Ereditarietà
  4. Polimorfismo.

Per chiudere questa serie di articoli parleremo delle Classi Astratte, della loro utilità e vedremo a supporto il codice Java.

Obiettivo della programmazione ad oggetti

Uno degli obiettivi principali della programmazione ad oggetti è quello di rendere agevole, snella ed efficiente la manutenzione del software; più il software cresce, più è importante strutturare correttamente la gerarchia delle classi.

Quando abbiamo parlato di polimorfismo, ereditarietà ed incapsulamento abbiamo evidenziato i vantaggi derivanti da tali caratteristiche nella gestione di possibili modifiche da apportare successivamente alla fase di rilascio del software stesso.

Il concetto di “astrazione dei dati” serve a rafforzare ulteriormente questi aspetti, in particolare per quanto riguarda il riutilizzo del codice, fondamentale nella programmazione di software semplici e complessi.

Astrazione dei Dati

L’Astrazione dei Dati viene applicata per decomporre sistemi software in componenti più piccoli e semplici che possono essere gestiti con maggiore facilità ed efficienza, un po’ come accade in matematica quando scomponiamo le equazioni in operazioni più semplici, nella scrittura degli algoritmi l'obiettivo è quello di semplificare il codice, snellire i problemi complessi in problemi più piccoli da analizzare più facilmente.

Possiamo definire l’astrazione dei dati nel seguente modo: 

Un’astrazione deve definire le caratteristiche principali di un oggetto che lo distinguono da tutti gli altri oggetti fornendo, in tal modo, dei confini concettuali ben precisi relativamente alla prospettiva dell’osservatore.

Per chiarire meglio il concetto riprendiamo un esempio utilizzato nel precedente articolo, quello degli animali.

Consideriamo la classe Animale che incapsula al suo interno tutte le caratteristiche (metodi e proprietà) comuni a tutti gli animali.

Si potranno definire i metodi mangia(), respira(), cammina() e le proprietà altezza e peso (solo per citarne alcuni). È evidente che tutti gli animali hanno queste caratteristiche, ma appare chiaro che creare una istanza di un oggetto "animale" ha poco senso visto che è certamente più consono definire istanze di oggetti specifici poiché non avremo mai un oggetto generico di tipo animale, ma sempre una sua “incarnazione” più specifica.

La classe Animale, non potendo essere direttamente istanziata, rappresenta un dato astratto ed è denominata appunto "classe astratta", ovvero una classe che rappresenta un base di classe che deve essere dettagliata da classi derivate più specifiche.

Una classe astratta può contenere sia proprietà che metodi. I metodi possono anche essere definiti come abstract, ovvero non essere implementati, delegando alle classi figlie questo compito. I metodi non implementati (che sono definiti nella classe astratta) prendono il nome di metodi astratti

L’utilizzo dell’astrazione dei dati (unito al concetto di ereditarietà) facilita il riutilizzo del codice e snellisce il disegno di un sistema software. Infatti, qualora si presentasse la necessità, sarà agevole poter definire delle altre classi intermedie che possano avvalersi delle definizioni già presenti nelle classi astratte. Inoltre, risulterà di enorme utilità poter riutilizzare le classi astratte già definite, anche in altri progetti.

Vediamo qualche esempio di Java:

public abstract class Animale
{
    abstract void mangia( );
public Integer peso;
}
public class Pesce extends Animale
{   
public boolean mangia( )
    {        ...;   }
}

Come si vede dall'esempio, la classe astratta Animale, che in Java si definisce con il comando “abstract”, serve a dichiarare che la classe avrà dei metodi abstract, ovvero senza implementazione.

La classe Pesce, che Estende Animale, deve per forza implementare il metodo mangia() per essere compilata. Ovviamente, essendo una classe figlia, essa eredita anche tutte le altre proprietà della classe Animale (come la proprietà peso) e può avere delle proprie implementazioni. 

Valendo tutti gli altri principi della programmazione ad oggetti, è possibile avere una gerarchia di classi astratte sempre più dettagliate e specializzate.

Avvicinandoci quindi alla realtà, potremmo specificare meglio l’esempio di prima.

Partendo dalla Classe Astratta Animale, possiamo definire una nuova classe astratta Pesce, poi la classe astratta Squalo, poi la classe Squalo Bianco.

Interfacce 

In alcuni linguaggi di programmazione, come Java, esistono dei costrutti ancora più generici delle classi astratte come appunto le Interfacce.

In alcune situazioni l’interfaccia è un "contratto" che spiega come interagisce il loro software, o che definisce i metodi implementati, o che devono esserlo.

Nel linguaggio di programmazione Java, un'interfaccia è un tipo di riferimento, simile a una classe che può contenere solo costanti, firme di metodo, metodi predefiniti, metodi statici e tipi nidificati. Da Java 8 è possibile trovare anche le implementazioni dei metodi, ma solo per i metodi predefiniti e i metodi statici. 

Cosa differenzia una Interfaccia da una Classe?

  • Mentre una classe viene estesa, una classe viene implementata. Questo significa che la Classe accetta il contratto dell’Interfaccia e implementa tutti i metodi definiti;
  • Una classe può implementare più Interfacce, ma estendere solo una Classe (non in tutti i linguaggi di programmazione questa cosa è vera);
  • Le interfacce non possono essere istanziate. Una classe ha uno stato che può essere modificato, le interfacce no, in quanto definisce il contratto ma non lo implementano;
  • Dato che non possono essere istanziate, un'interfaccia non può avere un costruttore, mentre una classe astratta può essere definito.

Usare classi astratte o interfacce?

Puoi utilizzare le classi astratte se una di queste affermazioni si applica alla tua situazione:

  • Se vuoi condividere il codice tra diverse classi strettamente correlate;
  • ti aspetti che le classi che estendono la tua classe astratta abbiano molti metodi o campi comuni o richiedano modificatori di accesso diversi da public (come protected e private);
  • vuoi dichiarare campi non statici o non finali, ciò consente di definire metodi che possono accedere e modificare lo stato dell'oggetto a cui appartengono.

Puoi utilizzare le interfacce se una di queste affermazioni si applica alla tua situazione:

  • ti aspetti che le classi non correlate implementino la tua interfaccia. Ad esempio, le interfacce Comparable e Cloneable sono implementate da molte classi non correlate;
  • vuoi specificare il comportamento di un particolare tipo di dati, ma non preoccuparti di chi implementa il suo comportamento;
  • ti interessa sfruttare l'ereditarietà multipla del tipo.

Con questo articolo chiudiamo la parte introduttiva alla programmazione ad oggetti. I concetti espressi possono sembrare semplici o banali in alcune loro applicazione, ma se usati correttamente possono essere grandi strumenti per semplificare la struttura ed il codice di un software.

Impara a programmare in 3 mesi con il Corso di Coding Hackademy su Laravel PHP

Diventa Sviluppatore web in 3 mesi

Scopri il coding bootcamp Hackademy

Programma Completo