Linea guida: Classe di progettazione
Una classe di progettazione rappresenta un elemento della progettazione che viene mappato direttamente nel codice. Queste linee guida spiegano come sviluppare una classe di progettazione.
Relazioni
Descrizione principale

Definizione

Una classe di progettazione rappresenta un'astrazione di una o più classi nell'implementazione del sistema; a cosa corrisponda esattamente dipende dal linguaggio dell'implementazione. Ad esempio, in un linguaggio orientato sugli oggetti come C++, una classe corrisponde ad una semplice classe. Oppure in Ada, una classe corrisponde ad un tipo con tag definito nella parte visibile di un pacchetto.

Le classi definiscono degli oggetti, che a loro volta realizzano (implementano) i casi d'uso. Una classe ha origine dai requisiti che che le realizzazioni dei casi d'uso crea sugli oggetti necessari nel sistema, oltre che da qualunque modello oggetto sviluppato precedentemente.

Se una classe è valida o meno dipende in larga misura dall'ambiente di implementazione. La dimensione appropriata alla classe e ai suoi oggetti dipende dal linguaggio di programmazione, ad esempio. Quello che si ritiene giusto quando si utilizza Ada potrebbe essere sbagliato con Smalltalk. Le classi devono essere associate ad un particolare fenomeno nel linguaggio di implementazione e devono essere strutturate in modo tale che la mappatura risulti in un codice valido.

Anche se le peculiarità del linguaggio di implementazione influenzano il modello di progettazione, è necessario che la struttura della classe sia facile da capire e da modificare. È necessario progettare come se si disponesse di classi ed incapsulazione, anche se il linguaggio di implementazione non le supporta.

Operazioni

L'unico modo in cui altri oggetti possono accedere o influenzare gli attributi e le relazioni di un oggetto è tramite le sue operazioni. Le operazioni di un oggetto vengono definite dalla sua classe. Un comportamento specifico può essere eseguito tramite le operazioni, che possono influire sugli attributi e sulle relazioni di cui l'oggetto dispone, e provocare l'esecuzione di altre operazioni. Un'operazione corrisponde ad una funzione membro in C++ o ad una funzione o procedura in Ada. Quale comportamento viene assegnato ad un oggetto dipende dal ruolo che svolge nelle realizzazione di casi d'uso.

Parametri

Nella specifica di un'operazione, i parametri costituiscono i parametri formali. Ogni parametro ha un nome ed un tipo. È possibile utilizzare la sintassi e la semantica del linguaggio di implementazione, per specificare le operazioni ed i loro parametri, in modo tale che saranno già specificati nel linguaggio di implementazione quando viene avviata la codifica.

Esempio:

Nel Sistema della macchina per il riciclaggio, gli oggetti di una classe Base di ricezione tengono traccia di quanti articoli di deposito di un certo tipo il cliente ha restituito. Il comportamento di un oggetto Base di ricezione include l'aumento del numero di oggetti restituiti. L'operazione insertItem, che riceve un riferimento per l'articolo restituito, serve allo scopo.

Diagramma descritto nel testo di accompagnamento.

Utilizzare la sintassi e la semantica del linguaggio di implementazione quando si specificano le operazioni.

Operazioni della classe

Un'operazione denota quasi sempre il comportamento di un oggetto. Anche un'operazione può denotare il comportamento di una classe, nel qual caso si tratta di un'operazione di classe. Può essere modellato in UML definendo un tipo per l'operazione.

Visibilità dell'operazione

Sono possibili le seguenti visibilità su un'operazione:

  • Pubblica: l'operazione è visibile per modellare gli elementi tranne la classe stessa.
  • Protetta: l'operazione è visibile solo alla classe stessa, alle sottoclassi o per gli amici della classe (dipende dal linguaggio)
  • Privata: l'operazione è visibile alla classe stessa e agli amici della classe
  • Implementazione: l'operazione è visibile solo all'interno della classe stessa.

La visibilità Pubblica deve essere utilizzata molto di rado, solo quando un'operazione è necessaria ad un'altra classe.

La visibilità Protetta deve essere l'impostazione predefinita; protegge l'operazione dall'utilizzo da parte di classi esterne, che promuove l'accoppiamento libero e l'incapsulamento del comportamento.

La visibilità Privata deve essere utilizzata nei casi in cui si desidera impedire alle sottoclassi di ereditare l'operazione. Ciò fornisce un modo per annullare l'accoppiamento delle sottoclassi con la superclasse e di ridurre la necessità di rimuovere o escludere le operazioni ereditate non utilizzate.

La visibilità Implementazione è la più restrittiva; viene utilizzata nei casi in cui solo la classe stessa è in grado di utilizzare l'operazione. E' una variante della visibilità Privata, che per la maggior parte dei casi è adatta.

Stati

Un oggetto può reagire in modi diversi ad uno specifico messaggio a seconda dello stato in cui si trova; il comportamento di un oggetto in base allo stato è definito da un diagramma di stati associato. Per ciascun stato in cui l'oggetto può trovarsi, il diagramma di stati descrive quali messaggi può ricevere, quali operazioni verranno eseguite e in quale stato si troverà l'oggetto da quel momento in poi. Per ulteriori informazioni fare riferimento a Tecnica: Diagramma di stati.

Collaborazioni

Una collaborazione è un insieme di interazioni di oggetti dinamico in cui un insieme di oggetti che comunicano inviando messaggi reciprocamente. L'invio di messaggi è diretto in Smalltalk; in Ada viene effettuato come chiamata di un programma secondario. Un messaggio viene inviato ad un oggetto destinatario che richiama un'operazione all'interno dell'oggetto. Il messaggio indica il nome dell'operazione da eseguire, insieme ai parametri richiesti. Quando i messaggi vengono inviati, vengono forniti i parametri effettivi (i valori dei parametri formali) per tutti i parametri.

Le trasmissioni di messaggi fra gli oggetti in una realizzazione di caso d'uso ed il punto focale di controllo che gli oggetti seguono quando vengono richiamate le operazioni, sono descritti nei diagrammi di interazioni. Per informazioni su questi diagrammi consultare Tecnica: Diagramma di sequenza e Tecnica: Diagrammi di comunicazione.

Un attributo è una proprietà denominata di un oggetto. Il nome di un attributo è un sostantivo che descrive il ruolo dell'attributo in relazione all'oggetto. Un attributo può avere un valore iniziale quando viene creato l'oggetto.

Modellare gli attributi solo se questo rende un oggetto più comprensibile. Modellare la proprietà di un oggetto come attributo solo se si tratta di una proprietà relativa unicamente a quell'oggetto. Altrimenti modellare la proprietà con una relazione di associazione o di aggregazione ad una classe i cui oggetti rappresentano la proprietà.

Esempio:

Diagramma descritto nel testo di accompagnamento.

Un esempio di come viene modellato un attributo. Ogni membro di una famiglia ha un nome e un indirizzo. Qui gli attributi mio nome e indirizzo di casa di tipo Nome e Indirizzo sono stati identificati rispettivamente:

Diagramma descritto nel testo di accompagnamento.

In questo esempio, viene utilizzata un'associazione invece di un attributo. La proprietà mio nome probabilmente è univoca per ogni membro di una famiglia. Quindi è possibile modellarla come attributo con tipo attributo Nome. Un indirizzo, però, è condiviso da tutti i membri di una famiglia, quindi viene modellato meglio da un'associazione fra la classe Membro famiglia e la classe Indirizzo.

Non è sempre facile decidere immediatamente se modellare un concetto come oggetto separato o come attributo di un altro oggetto. Avere nel modello di oggetti degli oggetti non necessari porta ad un eccesso di documentazione e di sviluppo inutili. Quindi è necessario stabilire determinati criteri per determinare l'importanza di un concetto per il sistema.

  • Accessibilità. Ciò che governa la scelta fra oggetto e attributo non è l'importanza del concetto nella vita reale ma l'esigenza di accedervi durante il caso d'uso. Se si accede spesso all'unità, modellarla come oggetto.
  • Separazione durante l'esecuzione. Concetti del modello gestiti separatamente come oggetti durante l'esecuzione del caso d'uso.
  • Legami ad altri concetti. Concetti del modello strettamente legati a determinati altri concetti e mai utilizzati separatamente, ma sempre tramite un oggetto come attributo dell'oggetto.
  • Richieste da relazioni. Se, per qualche motivo, è necessario correlare un'unità da due direzioni, riesaminare l'unità per vedere se dovrebbe essere un oggetto separato. Due oggetti non possono associare la stessa istanza di un tipo di attributo.
  • Frequenza di ricorrenza. Se un unità esiste solo durante un caso d'uso, non modellarla come oggetto. Modellarla invece come attributo dell'oggetto che esegue il comportamento in questione o semplicemente menzionarlo nella descrizione dell'oggetto interessato.
  • Complessità. Se un oggetto diventa troppo complicato a causa dei suoi attributi, è possibile estrarre alcuni degli attributi in oggetti separati. Utilizzarlo questo metodo con moderazione, in modo da non avere troppi oggetti. D'altro canto le unità potrebbero essere molto dirette. Ad esempio, classificate come attributi si considerino le (1) unità sufficientemente semplici da essere supportate direttamente da tipi primitivi nel linguaggio di implementazione, ad esempio i numeri interi in C++, e (2) unità abbastanza semplici per essere implementate utilizzando i componenti, indipendenti dall'applicazione, dell'ambiente di implementazione, ad esempio Stringa in C++ e Smalltalk-80.

Probabilmente un concetto si modella in modi diversi per sistemi diversi. In un sistema, il concetto può essere così vitale da modellarlo come oggetto. In un altro sistema potrebbe essere di minore importanza e quindi verrebbe modellato come attributo di un oggetto.

Esempio:

Ad esempio, per una compagnia aerea si svilupperebbe un sistema che supporta le partenze.

Diagramma descritto nel testo di accompagnamento.

Un sistema che supporta le partenze. Si supponga che il personale all'aeroporto desideri un sistema che supporti le partenze. Per ogni partenza è necessario definire l'ora della partenza, la compagnia aerea e la destinazione. È possibile modellarlo come oggetto della classe Partenza, con attributi ora della partenza, compagnia aerea e destinazione.

Se invece il sistema viene sviluppato per un'agenzia di viaggi, la situazione potrebbe essere in qualche modo diversa.

Diagramma descritto nel testo di accompagnamento.

'Destinazioni di volo' forma il proprio oggetto Destinazione.

L'ora della partenza, la compagnia aerea e la destinazione naturalmente saranno ancora necessarie. Tuttavia vi sono degli altri requisiti, poiché l'agenzia di viaggi è interessata a individuare una partenza con una specifica destinazione. È necessario quindi creare un oggetto separato per Destinazione. Gli oggetti Partenza e Destinazione naturalmente devono essere a conoscenza l'uno dell'altro, cosa che viene abilitata con un'associazione fra le loro classi.

L'argomento per l'importanza di determinati concetti è valido anche per determinare quali attributi devono essere definiti in una classe. La classe Auto senza dubbio definirà attributi diversi se i suoi oggetti fanno parte di un sistema di registrazione della motorizzazione o se fanno parte di un sistema di fabbricazione di automobili.

Infine, le regole su cosa rappresentare come oggetti e cosa come attributi non sono assolute. In teoria è possibile modellare tutto come oggetti ma è scomodo. Una regola pratica è di visualizzare un oggetto come qualcosa che ad un certo momento viene utilizzato a prescindere da altri oggetti. Inoltre non è necessario modellare ogni proprietà di oggetto con un attributo, solo le proprietà necessarie per capire l'oggetto. Non modellare dei dettagli che sono talmente specifici per l'implementazione da essere gestiti meglio direttamente dall'implementatore.

Attributi della classe

Un attributo quasi sempre denota le proprietà dell'oggetto. Un attributo può anche denotare le proprietà di una classe, nel qual caso si tratta di un attributo della classe. Può essere modellato in UML definendo un tipo per l'attributo.

Un oggetto può incapsulare qualcosa il cui valore può cambiare senza che l'oggetto esegua alcun comportamento. Potrebbe trattarsi di qualcosa che in realtà è un'unità esterna ma che non è stata modellata come attore. Ad esempio, potrebbero essere stati scelti dei margini di sistema e all'interno esservi incluso qualche genere di sensore. Il sensore può essere quindi incapsulato all'interno dell'oggetto in modo che il valore che misura costituisce un attributo. Questo valore può cambiare continuamente o a determinati intervalli senza che l'oggetto venga influenzato da nessun altro oggetto del sistema.

Esempio:

È possibile modellare un termometro come oggetto; l'oggetto ha un attributo che rappresenta la temperatura e cambia il il valore in risposta alle variazioni di temperatura dell'ambiente. Altri oggetti possono chiedere la temperatura corrente eseguendo un'operazione sull'oggetto termometro.

Diagramma descritto nel testo di accompagnamento.

Il valore dell'attributo temperatura cambia spontaneamente nell'oggetto Termometro.

È anche possibile modellare un valore incapsulato che cambia in questo modo come un normale attributo, si deve descrivere nella classe dell'oggetto che cambia spontaneamente.

Visibilità dell'attributo

La visibilità dell'attributo assume uno dei seguenti valori:

  • Pubblico: l'attributo è visibile sia all'interno che all'esterno del pacchetto che contiene la classe.
  • Protetto: l'attributo è visibile solo alla classe stessa, alle sue classi secondarie o agli amici della classe (dipende dal linguaggio)
  • Privato: l'attributo è visibile solo alla classe stessa e agli amici della classe
  • Implementazione: l'attributo è visibile alla classe stessa.

La visibilità Pubblica deve essere utilizzata molto di rado, solo quando un attributo è accessibile direttamente un'altra classe. La definizione di visibilità pubblica è una notazione stenografata per definire la visibilità dell'attributo come protetto, privato o implementazione, con operazioni pubbliche associate per ottenere ed impostare il valore dell'attributo. La visibilità pubblica può essere utilizzata come dichiarazione per un generatore di codice che le operazioni get/set devono essere generate automaticamente, risparmiando tempo durante la definizione della classe.

La visibilità Protetta deve essere l'impostazione predefinita; protegge l'attributo dall'utilizzo da parte di classi esterne, che promuove l'accoppiamento libero e l'incapsulamento del comportamento.

La visibilità Privata deve essere utilizzata nei casi in cui si desidera impedire alle sottoclassi di ereditare l'attributo. Ciò fornisce un modo per annullare l'accoppiamento delle sottoclassi con la superclasse e di ridurre la necessità di rimuovere o escludere gli attributi ereditati non utilizzati.

La visibilità Implementazione è la più restrittiva; viene utilizzata nei casi in cui solo la classe stessa è in grado di utilizzare l'attributo. E' una variante della visibilità Privata, che per la maggior parte dei casi è adatta.

Struttura interna

Alcune classi possono rappresentare delle astrazioni complesse e avere una struttura complessa. Durante la modellazione di una classe, il progettista potrebbe voler rappresentare i suoi elementi interni e le loro relazioni, per far sì che l'implementatore implementi di conseguenza le collaborazioni che si verificano all'interno della classe.

In UML 2.0, le classi vengono definite come classi strutturate, con la capacità di avere una struttura interna e delle porte. Le classi possono essere scomposte in raccolte di parti connesse che possono ulteriormente essere scomposte a loro volta. Una classe può essere incapsulata forzando le comunicazioni dall'esterno a passare attraverso le porte che obbediscono alle interfacce dichiarate.

Quindi, oltre ad utilizzare i diagrammi di classe per rappresentare le relazioni di classe (ad es. associazioni, composizioni ed aggregazioni) e gli attributi, il progettista può utilizzare un diagramma di struttura composita. Questo diagramma fornisce al progettista un meccanismo per visualizzare come le istanze delle parti interne svolgono il loro ruolo in una istanza di una data classe.

Per ulteriori informazioni su questo argomento e degli esempi di diagrammi di struttura composita, consultare Concetto: Classe strutturata.