Torna alla pagina di Ingegneria del Software
:: Ingegneria del Software - Appunti del 28 Aprile 2009 ::
Attributi di stato e di configurazione
Gli attributi di stato già sappiamo che cosa sono: sono quegli attributi dal cui valore dipende l'output dei metodi che chiamerò ad una data istanza di quella classe.
Attenzione: stiamo parlando dell'output dei metodi di una particolare istanza della classe. Questo significa che se ho due diverse istanze della stessa classe ScambioFerroviario, il metodo riceviTreno(Treno t), presente in entrambe, darà per ciascuna istanza un risultato che dipende dallo stato di quell'istanza stessa.
Se lo scambio ferroviario 1 è libero, riceviTreno(Treno t) mi darà true. Se lo scambio ferroviario 2 è occupato, riceviTreno(Treno t) mi darà false.
Sembra una banalità, e probabilmente lo è. Tuttavia, come già abbiamo visto, nei diagrammi si parla di classi, ma in realtà saranno gli oggetti ad essere interpellati nel mondo reale. Occorre aver presente la distinzione tra classe e istanza della classe.
Infatti, gli attributi di configurazione sono attributi che giocano proprio sulla distinzione tra classe e istanza. Un attributo di configurazione ha ovviamente un tipo e un valore, ma il suo valore non dipende dalla singola istanza, bensì è comune a tutte le istanze: in altre parole, è relativo alla classe.
In Java, li ottengo così:
public class Ambaraba {
public final static int numeroGatti = 100;
public Ambaraba() {
...
}
...
...
}
Altrove, invece, avrò per esempio diverse istanze di Ambaraba:
...
Ambaraba numeroUno = new Ambaraba();
Ambaraba numeroDue = new Ambaraba();
...
Avendo scritto l'attributo numeroGatti in quel modo, allora ho che numeroUno.numeroGatti sarà uguale a numeroDue.numeroGatti, ovvero 100. In tutte le istanze di Ambaraba, numeroGatti sarà sempre 100, e non sarà modificabile (è final).
ATTENZIONE: in realtà Java si lamenterebbe un pochino se io cercassi di accedere al valore numeroGatti tramite un'istanza, ovvero:
int risultato = numeroUno.numeroGatti * 2;
Trattandosi di un attributo relativo alla classe, e non alla singola istanza, concettualmente è più corretto farvi riferimento in questo modo:
int risultato = Ambaraba.numeroGatti * 2;
Come vedete, numeroGatti è attributo della classe Ambaraba, e questa scrittura lo mette bene in evidenza.
Visibilità degli attributi
Abbiamo già visto che gli attributi di una classe vanno acceduti dall'esterno preferibilmente con dei metodi appositi, al fine di poterne tracciare le modifiche in un diagramma e di poter controllare la liceità di queste variazioni a runtime (ovvero non permettere impunemente a chiunque di cambiarmi un attributo magari per me vitale).
Trascurando il fatto che, a livello implementativo, potrò avere differenze tra un linguaggio ed un altro per l'implementazione di questi metodi e attributi, UML mi permette, già nel diagramma delle classi, di stabilire la visibilità degli attributi di una classe.
Per visibilità di un attributo, o di un metodo, si intende la possibilità dall'esterno di fare appello o meno a quel particolare metodo o attributo. Un metodo pubblico sarà accessibile a chiunque, mentre un metodo privato sarà accessibile solamente ai metodi interni alla classe, e dall'esterno nessuno potrà accedervi direttamente. Al massimo, posso prevedere un metodo per pubblicarne il valore (il famoso check), e magari per modificarlo (il set).
public class CicciCocco {
private int valorePrivato;
...
public int checkValorePrivato() {
return this.valorePrivato;
}
public Boolean setValorePrivato(int p) {
if (p > 0) && (p < 100) then {
this.valorePrivato = p;
return true;
}
return false;
}
}
Ecco come si segna in UML la visibilità:
- + = attributi pubblici
- - = attributi privati
- # = attributi protetti
Da dove diavolo saltano fuori gli attributi protetti? Un attributo è protetto quando una classe esterna non può vederlo, ma invece tutte le classi che ereditano da me sì.
public class Padre {
protected int valoreProtetto;
}
public class Figlio extends Padre {
...
public void metodo() {
// Posso cambiare il valoreProtetto perché eredito da Padre
this.valoreProtetto = 100;
}
}
public class Estraneo {
private Padre unPadre;
...
public void metodo() {
// ERRORE! Questa riga non è valida, perché l'Estraneo
// non è parente del Padre! La compilazione darà errore.
this.unPadre.valoreProtetto = 100;
}
}
Navigabilità ed implementazione
Il concetto di navigabilità, già visto qui, viene implementato nel linguaggio scelto dal programmatore. Spetta quindi a lui stabilire come realizzare correttamente e concretamente la navigabilità, ovvero dove mettere gli attributi, quando inizializzarli e così via.
Sinteticamente, se c'è una freccia che va da A a B, allora A avrà un attributo di tipo B, ma B non avrà un attributo di tipo A. Il programmatore baluba, sprovvisto di buon senso, farà invece cose arbitrarie e sicuramente sbagliate.
E attenzione: spesso più baluba del programmatore è il generatore automatico di codice che parte dal diagramma UML e mi produce le classi! Per far sì che questo generatore automatico di codice faccia le cose per bene, occorre specificare correttamente la cardinalità delle mie relazioni, altrimenti potrebbe decidere di fare come vuole lui.
Per esempio, una cardinalità 1-* potrebbe essere realizzata, nella classe dal lato di "1", come un Vector<Destinazione>, proprio perché posso avere 0, 1 o 1000 associazioni con istanze diverse della classe che sta dal lato del "*".
Le cardinalità sono le seguenti:
La dicitura 1..n mi dice che ho un vincolo superiore: posso essere a conoscenza al minimo di una classe, ma al massimo ne posso avere n.
Ricordiamo inoltre che le relazioni ternarie, per quanto siano rappresentabili con un diagramma UML, non sono ben viste. Il motivo è che dal punto di vista implementativo le relazioni si esprimono con una coppia di attributi. Gestire una relazione ternaria o n-aria con gli attributi diventa veramente impestato. Creare una classe intermedia solo per rappresentare le relazioni potrà essere una cosa gradita nei database, ma consumerebbe tempo e risorse.
Torna alla pagina di Ingegneria del Software