Principi di design delle classi

Si tratta di best practice per facilitare lo sviluppo del software, che se applicate correttamente ci permetteranno di ottenere i seguenti vantaggi :

  • Codice più leggibile
  • Il codice è più semplice da comprendere
  • Il codice è maggiormente riutilizzabile
  • Il codice è più manutenibile ed è più semplice la modifica per recepire cambiamenti di requisti

Vediamo questi principi in dettaglio, partendo dal primo :

Incapsulamento

L’incapsulamento è un principio fondamentale della programmazione ad oggetti, si basa sul concetto di isolare l’accesso alle proprietà di una classe, permettendone la manipolazione solo attraverso metodi, di fatto proteggendoli dall’assumere valori non congruenti con il modello dati.

Per prassi si implementa dichiarando le proprietà di una classe come private ed i metodi di accesso detti anche getter e setter  come public

Immaginiamo di disegnare il nostro oggetto Aereo secondo le seguenti specifiche :

  • il modello dell’aereomobile non può essere null
  • ogni aereo dichiara una velocita massima che deve essere sempre maggiore di 0

Immaginiamo cosa potrebbe succedere se disegnassimo la nostra classe senza l’incapsulamento :

public class Aereo {
  public String costruttore;
  public double velocitaMassima;
}
public static void main(String... args) {
  Aereo md80 = new Aereo();
  md80.velocitaMassima = -100;
}

Questo codice è perfettamente lecito, ma abbiamo permesso ad uno sviluppatore di definire un aereo con velocità massima negativa ! Utilizzando l’incapsulamento ci saremmo protetti da questo :

public class Aereo {
  private String costruttore;
  private double velocitaMassima;
  public Aereo(String costruttore) {
    this.setCostruttore(costruttore);
  }
  public String getCostruttore() { return this.costruttore; }
  public double getVelocitaMassima() { return this.velocitaMassima; }
  public void setCostruttore(String costruttore) {
    if (costruttore == null)
      throw new IllegalArgumentException("devi specificare un costruttore per l'aereo!");
    this.costruttore = costruttore;
  }
  public void setVelocitaMassima(double velocita) {
    if (velocita < 0)
      throw new IllegaArgumentException("un aereo non può avere velocita minore di 0");
    this.velocitaMassima = velocita;
  }
}

Vediamo che abbiamo dichiarate le nostre due variabili come private, abbiamo creato dei metodi getXX per leggere i valori e dei metodi setXX per impostare i valori, in particolare i due metodi set contengono della logica che impedisce di inserire valori errati.
Infine, abbiamo creato un costruttore che richiede di fornire il costruttore dell’aereo, per rispettare il primo requisito.

Ora, in tutta la nostra applicazione, avremmo la certezza di utilizzare solo aereo che rispettano quanto specificato

Creazione di Javabeans

L’incapsulamento è la base per poter creare oggetti per la memorizzazione di dati, chiamati anche JavaBeans, che devono rispettare le seguenti regole :

  • le property devono essere private
  • getter per valori non booleani iniziano per get seguiti dal nome della property con l’iniziale in maiuscolo
  • getter per valori booleani iniziano con is oppure per get seguiti dal nome della property con l’inziale in maiuscolo
  • setter iniziano con set seguiti dal nome della property con l’iniziale in maiuscolo

NB: is si applica solo per valori boolean e non per la classe wrapper Boolean !!

vediamo un esempio :

private boolean fly;
private Boolean landed;

public isFly() ..
public getFly() ...
public isLanded() ...

La prima è corretta, boolean vuole is … la seconda .. anche ! boolean può anche chiamari con get ! la terza è errata, Boolean è un oggetto e quindi non può iniziare con is !

Applicare la relazione is-a

vi ricordate l’operatore instanceof ? che determina se un oggetto è istanza di una particolare classe, interfaccia o superclasse ?

applicare questo operatore equivale a verificare se la relazione is-a è soddisfatta.

se una classe A is-a B, significa che ogni istanza di A può essere trattata come B

Composizione di oggetti

Nella programmazione ad oggetti, la composizione è la proprietà di costruire una classe usando riferimenti ad altre classi al fine di riutilizzare funzionalità, in particolare, la classe che contiene altre classi possiede la relazione has-a e può delegare dei metodi a queste classi.

Può essere utilizzata in alternativa all’ereditarità per simulare un comportamento polimorfico che non può essere realizzato mediante l’ereditarietà singola di Java, vediamone un semplice esempio :

public class Flap {
  public void alzaFlap(int gradi) {
    System.out.println("flap alzati di "+gradi+" gradi");
  }
}
public class Motore {
  public void aumentaPotenza(int percentuale) {
    System.out.println("il motore gira alla "+ percentuale+ " di potenza");
  }
}

Immaginiamo di voler creare un nostro aereo che utilizza questi due metodi già disponibili nella nostra applicazione, con l’ereditarità saremmo costretti a estendere solo 1 di queste classe, vediamo come risolvere con la composizione di oggetti :

public class Aereo {
  private final Flap flap;
  private final Motore motore;
  public Aereo() {
    this.flap = new Flap();
    this.motore = new Motore();
  }
  public void alzaFlap(int gradi) {
    this.flap.alzaFlap(gradi);
  }
  public void aumentaPotenza(int percentuale) {
    this.motore.alzaPotenza(percentuale);
  }
}

Come puoi vedere, la nostra classe Aereo è composta da due istanze di Flap e Motore, e la logica di questi metodi è delegata a queste due classi, è buona norma che i metodi della classe composta che utilizzano funzionalità delle classi siano composti da 1 sola riga di codice.

Un altro vantaggio di utilizzare la composizione di oggetti è il forte riuso del codice, anche in classi non correlate tra loro secondo il modello logico, in questo caso noi potremmo riutilizzare le classi Flap e Motore non sono per gli aerei a reazione, ma anche per un ultraleggero, o pure un idrovolante, etc etc

Ma non bisogna abusarne, è sempre preferibile utilizzare l’ereditarietà per via della sua capacità di implementare il polimorfismo in maniera naturale, di avere il virtual method invocation, di poter sovrascrivere metodi