Programmazione ad Oggetti: Incapsulamento, Ereditarietà, Polimorfismo [Java]

Nell’articolo precedente avevamo parlato di programmazione ad oggetti base con Java, introducendo la maggior parte degli aspetti fondamentali. Tuttavia non abbiamo parlato delle 3 caratteristiche di una vera e propria programmazione ad oggetti, caratteristiche che vedremo in quest’articolo. Parliamo ovviamente di Incapsulamento, Ereditarietà e Polimorfismo.

Incapsulamento

Con il termine Incapsulamento si intende la possibilità di un linguaggio di programmazione ad oggetti di accorpare metodi e proprietà all’interno di un unica area, ovvero all’interno dell’Oggetto. In questo modo il nostro programma verrà ridotto a tante piccole parti, ognuna che incapsula una qualche funzionalità.

Riprendendo l’esempio del conto corrente, noterete che tutta la gestione di esso sta nella classe ContoCorrente. Tramite l’incapsulamento se volessimo aggiungere o modificare qualche funzionalità, non dovremo andare dentro al programma con molte righe di codice, cercare tutti i punti interessati ed eseguire le nostre modifiche. Basterà infatti modificare soltanto la classe ContoCorrente. È in sostanza una gestione centralizzata del codice.

Ereditarietà

Nella programmazione ad oggetti l’ereditarietà è un concetto un po’ più elaborato dell’incapsulamento, ma comunque intuibile. Si tratta di poter far “ereditare” tutti i metodi e le proprietà di una classe ad un’altra. La classe figlia viene chiamata sottoclasse, e la classe madre viene chiamata superclasse. Sembra difficile ma è molto semplice se guardiamo ad un esempio.

// superclasse
class Animale {
  private String verso = "";

  private parla() {
    System.out.println(verso);
  }
}

// sottoclasse n.1
class Cane extends Animale {
  public Cane() {
    this.verso = "bau";
  }
}

// sottoclasse n.2
class Gatto extends Animale {
  public Gatto() {
    this.verso = "miao";
  }
}

Come potete vedere abbiamo aggiunto la parola chiave extends alla dichiarazione della classe. Come intuibile, questa parola chiave fa in modo che la classe che stiamo definendo sia un’ “estensione” della classe che segue la parola extends. Detto formalmente la parola chiave extends crea una sottoclasse relativa alla superclasse che segue la parola extends.

L’ereditarietà ci permette anche di riscrivere parte del codice della superclasse. Prendiamo l’esempio che segue:

class Pappagallo extends Animale {
  private String[] versi = {"Ciao", "Hello", "Hola"};

  @Override
  public void parla() {
    int numeroCasuale = (int)(Math.random()*versi.length);
    System.out.println(versi[numeroCasuale]);
  }
}

In questo codice definiamo innanzitutto un array di stringhe, ossia una lista. In java questo si fa aggiungendo le parentesi quadre dopo il tipo (String[]).

Di seguito arriva la parte interessante. Innanzitutto definiamo l’annotazione @Override. Questa riga non fa altro che dire all’IDE e al compilatore che il metodo, che esisteva già nella superclasse, qui è stato riscritto. Non è necessario inserire questa riga, ma rende il codice molto più leggibile, sia per l’uomo che per l’IDE.

Quindi riscriviamo il metodo parla, che ora prende un numero a caso (di tipo double) tra zero e la lunghezza dell’array (ossia tre), lo fa diventare un int (si veda: casting) e stampa a schermo la stringa con indice numeroCasuale.

Quindi l’Ereditarietà ci permette di scrivere codice riutilizzabile ed estendibile.

Polimorfismo

Il polimorfismo, come definisce la parola stessa, è l’abilità, per esempio da parte di una variabile, di poter essere vista come di tipologie diverse. Ad esempio un Numero può essere visto allo stesso tempo come int, come float, come double, e così via.

int n1 = 5;
float n2 = 1;
double n3 = 1.0/3;
 
Number n = n1+n2+n3;
System.out.println(n);

In questo esempio la variabile n può essere usata come int, come float o come double.

Ma vediamo un esempio più interessante.

NB: Quest’esempio non ha niente a che vedere con quelli precedenti, non confondetevi sebbene utilizzi due volte l’esempio sugli animali.

interface Animale {
  public void parla();
}

class Cane implements Animale {
  public void parla() {
    System.out.println("bau");
  }
}

class Gatto implements Animale {
  public void parla() {
    System.out.println("miao");
  }
}

class Mucca implements Animale {
  public void parla() {
    System.out.println("muu");
  }
}

class Test {
  public static void main(String[] args) {
    Animale cane = new Cane();
    Animale gatto = new Gatto();
    Animale mucca = new Mucca();

    faiParlare(cane);
    faiParlare(gatto);
    faiParlare(mucca);
  }
 
  public static void faiParlare(Animale a) {
    a.parla();
  }
}

Questo esempio è lungo ma semplice. Innanzitutto definiamo una nuova interfaccia.

Cos’è un’interfaccia? È come se fosse una classe, ma completamente astratta. Definisce solo la struttura, i metodi non hanno corpo.

A cosa servono? A standardizzare un tipo, per esempio Animale. Qui stiamo dicendo che ogni Animale definito tale, deve avere il metodo parla. A seguito capiremo meglio.

Poi definiamo 3 animali, ognuno che implementa l’interfaccia animale con la parola chiave implements. Questo concetto è molto simile ad extends, ma una sottoclasse in java può estendere soltanto un’unica superclasse, mentre una classe può implementare infinite interfaccie.

Notare come sia necessario implementare TUTTI i metodi delle interfaccie. Se così non fosse, il compilatore ci darebbe un errore simile a:

Cane is not abstract and does not override abstract method parla() in Animale

Ora analizziamo la classe di Test, in cui si trova il main.

Il main non fa altro che creare 3 variabili di tipo Animale, ma istanziando 3 diverse classi: Cane, Gatto e Mucca. Questo è possibile grazie al polimorfismo, così come sono possibili le righe di codice che seguono: definiamo infatti un metodo faiParlare, che accetta una variabile di tipo Animale.

A questo metodo non interessa la classe in modo particolare, gli basta sapere che l’oggetto passato come parametro sia istanza di una classe che implementa l’interfaccia Animale, perché sa che qualunque classe che implementi l’interfaccia Animale, implementa anche il metodo parla().

Conclusioni

Abbiamo visto in breve in cosa consistono Incapsulamento, Ereditarietà e Polimorfismo, e come si possono osservare in Java. Questi tre aspetti però, indipendentemente dall’implementazione, sono sempre presenti (anche in modo diverso) in ogni linguaggio di programmazione ad oggetti.

Seguiteci anche su Facebook, Google Plus, Tumblr e Twitter, per restare sempre in contatto con noi e con le nostre guide.



Cerca

Seguici

Live da Facebook
Live da Twitter
Seguici su Telegram
Canale InsiDevCode Telegram