Modificatori di accesso e incapsulamento

Finora:

Modificatori di accesso: possono impedire l'accesso (modifica/uso) delle componenti e l'invocazione dei metodi.


I modificatori

Si applicano a: classi, componenti e metodi.

Dicono chi può usare una certa cosa.

public
tutti possono accedere
protected
possono accedere la classi dello stesso pacchetto (directory, nel nostro caso) e tutte le sottoclassi
nessun modificatore
possono accedere tutte le classi dello stesso pacchetto (incluse le sottoclassi, ma solo se stanno nello stesso pacchetto)
private
si può accedere solo all'interno della classe

Finora, non abbiamo usato modificatori

Di solito, si usano solo public e private


Usare i modificatori

Tipicamente: si usano sulle componenti.

class Esempio {
  private int x;
}

Un programma non può accedere alla componente x dell'oggetto:

class Prova {
  public static void main(String args[]) {
    Esempio e;
    e=new Esempio();

    e.x=12;

    System.out.println(e.x);
  }
}

Genera due errori:

Prova.java:6: x has private access in Esempio
    e.x=12;
     ^
Prova.java:8: x has private access in Esempio
    System.out.println(e.x);
                        ^
2 errors

Componenti private

Se una componente è privata, si può accedere solo all'interno della classe.

Come faccio a mettere un intero in e.x?

Come faccio a vedere il valore memorizzato?

Posso accedere a this.x dentro i metodi della classe.


Incapsulamento

class Esempio {
  private int x;

  void setX(int n) {
    this.x=n;
  }

  int getX() {
    return this.x;
  }
}

Impedisco l'accesso diretto alla componente dell'oggetto

Permetto la modifica/uso della componente attraverso i metodi.


Usare un classe incapsulata

Ogni volta che voglio memorizzare un valore in e.x, faccio e.setX(valore).

Per vedere il valore di e.x, uso e.getX()

class Prova {
  public static void main(String args[]) {
    Esempio e;
    e=new Esempio();

    // e.x=12;
    e.setX(12);

    // System.out.println(e.x);
    System.out.println(e.getX());
  }
}

Le linee commentate davano errore.


Vantaggi dell'incapsulamento


Azioni sulla modifica

Verifica tipica: non tutti i valori sono accettabili.

Esempio: l'età deve essere un numero positivo.

class Studente {
  private String nome;
  private int eta;
  private double media;

  void setEta(int n) {
    if(n>0)
      this.eta=n;
    else {
      System.out.println("Errore");
      System.exit(1);
    }
  }

  int getEta() {
    return this.eta;
  }

  ...
}

Oggetti immutabili

Posso decidere che, una volta costruito, l'oggetto non si può più cambiare:

class Studente {
  private String nome;
  private int eta;
  private double media;

  Studente(String nome, int eta, double media) {
    this.nome=nome;
    this.eta=eta;
    this.media=media;
  }

  // metodi getNome, getEta, getMedia
}

Quando creo l'oggetto, posso decidere i valori delle sue componenti.

Da questo momento in poi, non si possono più modificare.


Alcune modifiche

Posso vietare le modifiche solo su singoli campi, e permettere modifiche a più campi insieme:

class Studente {
  private String nome;
  private int eta;
  private double media;

  ...

  void setMedia(double m) {
    this.media=m;
  }

  void setNomeEta(String nome, int eta) {
    this.nome=nome;
    this.eta=eta;
  }

  // metodi getNome, getEta, getMedia
}

In questo modo, posso cambiare nome ed eta insieme, ma non uno solo dei due.

Invece, posso cambiare media da solo.


Permettere l'accesso solo in certe condizioni

Esempio: non posso inserire la media se non ho scritto il nome.

class Studente {
  private String nome;
  private int eta;
  private double media;

  ...

  void setMedia(double m) {
    if(this.nome!=null)
      this.media=m;
    else {
      System.out.println("Errore");
      System.exit(1);
    }
  }

  ...
}

Nota: gli errori in genere si gestiscono con le eccezioni.


Esercizio

Modificare la classe Segmento con le coordinate di un punto che è il mediano.

class Segmento {
  int x1;
  int y1;

  int x2;
  int y2;

  void mediano() {
    xm=(this.x1+this.x2)/2;
    ym=(this.y1+this.y2)/2;
  }
}

Assicurarsi che il punto sia sempre il mediano fra gli altri due.


Soluzione

Aggiungo due campi privati

Aggiungo il metodo per metterci le coordinate del mediano.

class Segmento {
  int x1;
  int y1;

  int x2;
  int y2;

  private int xm;
  private int ym;
}

I programmi non possono usare xm ed ym

Il problema è che potrei fare modifiche ad x1 senza aggiornare il centro.


Incapsulamento

Tutte le componenti sono private.

L'accesso (lettura e scrittura) avviene attraverso metodi.

class Segmento {
  private int x1;
  private int y1;

  private int x2;
  private int y2;

  private int xm;
  private int ym;

  int getX1() {
    return this.x1;
  }

  void setX1(int val) {
    this.x1=val;
    this.mediano();
  }

  ...

  void mediano() {
    xm=(this.x1+this.x2)/2;
    ym=(this.y1+this.y2)/2;
  }
}

Vantaggio dell'incapsulamento

Posso modificare le componenti solo attraverso metodi.

Nei metodi, ci posso scrivere operazioni ausiliare da fare.

Nell'esempio, ogni volta che una componente viene modificata, si calcola di nuovo il mediano.

  void setX1(int val) {
    this.x1=val;
    this.mediano();
  }

Senza incapsulamento, non c'era modo di obbligare chi scrive il programma a invocare il metodo mediano ogni volta che fa s.x1=qualcosa


Accesso ed ereditarietà

Cosa succede se cambio i diritti di accesso in una sottoclasse?

variabili
quando definisco una variabile con lo stesso nome, sto creando un'altra variabile: si può fare
metodi
quando definisco un metodo con lo stesso nome, sto ridefinendo un metodo; si può dare un diritto di accesso più liberale ma non più restrittivo

Estendere i diritti

Si può sempre fare:

class Sopra {
  private int y;

  private int getY() {
    return this.y;
  }
}
class Sotto extends Sopra {
  public int y;

  public int getY() {
    return this.y;
  }
}

Funziona tutto.


Restringere i diritti

Si può fare con le variabili, perchè tanto sono variabili nuove (e le vecchie esistono ancora)

Non si può fare con i metodi:

class Sopra {
  public int x;

  public int getX() {
    return this.x;
  }
}
class Sotto extends Sopra {
  private int x;	// ok

  private int getX() {  // errore!
    return this.x;
  }
}

La definizione del metodo getX in Sotto dà errore.