Visitor

Da Wikipedia, l'enciclopedia libera.
bussola Disambiguazione – Se stai cercando altri significati del termine, vedi The Visitor.

Il Visitor è un design pattern comportamentale utilizzato in informatica nella programmazione orientata agli oggetti. Permette di separare un algoritmo dalla struttura di oggetti composti a cui è applicato, in modo da poter aggiungere nuove operazioni e comportamenti senza dover modificare la struttura stessa.

Applicabilità[modifica | modifica wikitesto]

Visitor è utile quando

  • una struttura di oggetti è costituita da molte classi con interfacce diverse ed è necessario che l'algoritmo esegua su ogni oggetto un'operazione differente a seconda dalla classe concreta dell'oggetto stesso,
  • è necessario eseguire svariate operazioni indipendenti e non relazionate tra loro sugli oggetti di una struttura composta, ma non si vuole sovraccaricare le interfacce delle loro classi. Riunendo le operazioni correlate in ogni Visitor è possibile inserirle nei programmi solo dove necessario,
  • le classi che costituiscono la struttura composta sono raramente suscettibili di modifica, ma è necessario aggiungere spesso operazioni sui rispettivi oggetti. Ogni intervento sulle operazioni richiede la modifica o l'estensione di un Visitor, mentre ogni modifica alle classi della struttura vincola alla ridefinizione delle interfacce di tutti i Visitor, compito che può risultare estremamente complesso nei progetti di una certa dimensione.

Struttura[modifica | modifica wikitesto]

Il diagramma delle classi in UML rappresenta una struttura esemplificativa in Java.

La classe Client, pur non essendo parte integrante del pattern, è comunque illustrata per mostrare come possa interagire con l'interfaccia Visitor e la struttura di oggetti ObjectStructure. In questo caso definisce due metodi walk che si occupano di iterare su ogni oggetto della struttura, visitandolo rispettivamente con un ConcreteVisitor1 e un ConcreteVisitor2 utilizzati attraverso l'interfaccia comune Visitor.

Visitor UML class diagram.svg

Visitor[modifica | modifica wikitesto]

Visitor dichiara un metodo visit per ogni ConcreteElement appartenente alla struttura di oggetti, in modo che ogni oggetto della struttura possa invocare il metodo visit appropriato passando un riferimento a sé (this) come parametro.

Questo permette al Visitor di identificare la classe che ha chiamato il metodo visit, eseguire il comportamento corrispondente e accedere all'oggetto attraverso la sua specifica interfaccia.

ConcreteVisitor[modifica | modifica wikitesto]

ConcreteVisitor implementa le operazioni visit dichiarate da Visitor perché agiscano come desiderato sulle rispettive classi. Inoltre fornisce il contesto dell'algoritmo e ne mantiene in memoria lo stato, che spesso accumula i risultati parziali ottenuti durante l'attraversamento della struttura.

Element[modifica | modifica wikitesto]

Element definisce un'operazione accept utilizzata per "accettare" un Visitor passato come parametro.

ConcreteElement[modifica | modifica wikitesto]

ConcreteElement implementa la accept definita da Element. In generale accept chiama il metodo visit del Visitor ricevuto, passando come parametro un riferimento a sé.

ObjectStructure[modifica | modifica wikitesto]

ObjectStructure contiene ed elenca gli elementi. Quando necessario può fornire un'interfaccia d'alto livello che permetta al Visitor di visitare i singoli Element. Può essere implementata applicando il pattern Composite, oppure utilizzando una collezione come ad esempio un array o qualsiasi altra struttura dati.

Funzionamento[modifica | modifica wikitesto]

Un client che voglia utilizzare un Visitor deve creare un oggetto ConcreteVisitor e utilizzarlo per attraversare la struttura, chiamando il metodo accept di ogni oggetto. Ogni chiamata invoca nel ConcreteVisitor il metodo corrispondente alla classe dell'oggetto chiamante, che passa sé stesso come parametro per fornire al Visitor un punto d'accesso al proprio stato interno.

Conseguenze[modifica | modifica wikitesto]

Flessibilità delle operazioni[modifica | modifica wikitesto]

L'applicazione di questo design pattern permette di avere un'amplia flessibilità nell'aggiunta di nuove operazioni relative agli oggetti contenuti nella struttura. Per aggiungere operazioni è sufficiente creare un nuovo Visitor che le definisca e i ConcreteVisitor che le implementino, oppure creare direttamente un nuovo ConcreteVisitor che implementi un'interfaccia Visitor già esistente. Senza applicare il pattern si avrebbero una serie di funzionalità sparse in svariate classi, situazione che obbligherebbe a modificarle tutte ogni qualvolta si debba aggiungere un'operazione.

Organizzazione delle operazioni[modifica | modifica wikitesto]

Un Visitor raggruppa logicamente le operazioni correlate che possono essere eseguite su un gruppo di oggetti. Altri Visitor possono raggruppare ulteriori operazioni, creando in modo semplice una divisione logica delle funzionalità, organizzata gerarchicamente nelle sottoclassi dei vari Visitor. Di conseguenza le classi degli elementi possono essere semplificate e le strutture dati di un algoritmo possono essere nascoste all'interno del Visitor corrispondente.

Rigidità della gerarchia di classi[modifica | modifica wikitesto]

Se l'aggiunta di operazioni è semplificata, così non è per l'aggiunta di sottoclassi di Element: ogni nuovo ConcreteElement obbliga ogni interfaccia Visitor a definire un nuovo metodo visit relativo al nuovo tipo concreto e ogni ConcreteVisitor a implementarlo.

Per questo motivo il pattern Visitor è meglio utilizzabile quando la gerarchia di elementi non è suscettibile di numerose modifiche. In caso contrario, la difficoltà nella manutenzione delle classi Visitor rende probabilmente più conveniente gestire le funzionalità sugli oggetti in modo tradizionale, ovvero incorporandole negli oggetti stessi.

Visita di vari tipi di classe[modifica | modifica wikitesto]

Al contrario del pattern Iterator, un Visitor non è vincolato al tipo degli oggetti presenti nella struttura che deve attraversare. Infatti un Iterator può accedere solo a oggetti di un certo tipo e relative sottoclassi, mentre Visitor può agire anche su oggetti le cui classi non abbiano tra loro alcuna relazione di ereditarietà.

Mantenimento di uno stato[modifica | modifica wikitesto]

Invece di mantenere uno stato come variabile globale o come parametro aggiuntivo passato ai vari metodi di attraversamento, è possibile incapsularlo all'interno di un Visitor e aggiornarlo ad ogni visita.

Violazione dell'incapsulamento[modifica | modifica wikitesto]

Poiché su ogni ConcreteElement deve essere possibile l'azione di un ConcreteVisitor, è necessario che l'interfaccia degli elementi permetta l'accesso e la modifica dello stato interno, implementando metodi pubblici. In questo modo diventa responsabilità dei programmatori non modificare un oggetto Element in un punto del programma in cui non dovrebbe essere visibile. In Java è possibile ovviare parzialmente al problema utilizzando la visibilità package, raggruppando sia i visitor che gli elementi nello stesso package.

Implementazione[modifica | modifica wikitesto]

Ogni struttura ad oggetti deve avere una classe Visitor corrispondente, che definirà un metodo visit per ogni classe ConcreteElement facente parte della struttura. I metodi visit possono utilizzare la tecnica dell'overloading e quindi avere lo stesso nome e differire solo per il tipo dell'argomento,

visit (ConcreteElementA a){ }
visit (ConcreteElementB a){ }

oppure specificare nel nome stesso il tipo su cui agiscono

visitConcreteElementA (ConcreteElementA a){ }
visitConcreteElementB (ConcreteElementB a){ }

L'overloading può risultare più comodo ed elegante, anche se meno chiaro dal punto di vista della leggibilità del codice.

Inoltro doppio[modifica | modifica wikitesto]

Il pattern Visitor è utile per ottenere un comportamento di inoltro doppio (double dispatch) nei linguaggi di programmazione che non lo supportano nativamente, ovvero i linguaggi a inoltro singolo.

L'inoltro singolo prevede che il risultato di una operazione dipenda da due criteri: il nome dell'operazione e il tipo del destinatario. Nell'inoltro doppio l'operazione viene determinata dal suo nome e dai tipi di due destinatari (da qui il nome).

Nell'esecuzione di un Visitor, l'operazione chiamata dipende dal proprio nome e da due destinatari: il tipo di ConcreteVisitor utilizzato e il tipo ConcreteElement che viene visitato.

I linguaggi che implementano direttamente il double dispatch non hanno necessità di un pattern Visitor.

Attraversamento della struttura[modifica | modifica wikitesto]

L'attraversamento della struttura può essere effettuato da varie componenti del pattern. Nell'esempio rappresentato dall'immagine è il Client che si occupa di iterare con un ciclo for su tutti gli elementi di ObjectStructure, comodo quando la struttura è semplice. Più in generale l'iterazione può essere responsabilità di

  • un Visitor,
  • un ObjectStructure,
  • un Iterator esterno.

Bibliografia[modifica | modifica wikitesto]

Voci correlate[modifica | modifica wikitesto]

Altri progetti[modifica | modifica wikitesto]

Collegamenti esterni[modifica | modifica wikitesto]