Se volete ampliare le classi già presenti in un software orientato agli oggetti con nuove fun­zio­na­li­tà, avete due modi per farlo. La soluzione più facile, ma che può ra­pi­da­men­te com­pli­car­si, riguarda l’im­ple­men­ta­zio­ne di sot­to­clas­si che estendano le classi base con ciò di cui avete bisogno. L’al­ter­na­ti­va riguarda invece l’utilizzo di un’istanza de­co­ra­to­re secondo il co­sid­det­to decorator design pattern. Questo modello, facente parte dei 23 design pattern della GoF, permette l’esten­sio­ne dinamica delle classi durante l’ese­cu­zio­ne del software. Senza, tra l’altro, che si venga a creare una gerarchia di ere­di­ta­rie­tà infinita e di difficile com­pren­sio­ne.

Di seguito scoprite che cos’è il decorator pattern e quali vantaggi e svantaggi porta con sé. Inoltre, il­lu­stria­mo il fun­zio­na­men­to del modello av­va­len­do­ci di una rap­pre­sen­ta­zio­ne grafica e di un esempio concreto.

Che cos’è il decorator pattern?

Il decorator design pattern o decorator pattern è un modello pub­bli­ca­to nel 1994 per un’esten­sio­ne chiara delle classi nei software orientati agli oggetti. Con questo modello è possibile ag­giun­ge­re un com­por­ta­men­to de­si­de­ra­to senza in­fluen­za­re il com­por­ta­men­to degli altri oggetti della stessa classe. Da un punto di vista strut­tu­ra­le, il decorator pattern ricorda molto il chain of re­spon­si­bi­li­ty pattern. No­no­stan­te, di­ver­sa­men­te da quest’ultimo dotato di un pro­ces­so­re centrale, sono le classi a prendere in consegna le richieste.

La com­po­nen­te software che deve essere ampliata viene per così dire “decorata” con una o più classi decorator, che, secondo il principio del decorator pattern, la rac­chiu­do­no. Ogni decorator è dello stesso tipo della com­po­nen­te posta al suo interno e dispone delle stesse in­ter­fac­ce. In questo modo può delegare senza problemi l’in­vo­ca­zio­ne dei metodi, prima o dopo l’ese­cu­zio­ne del suo com­por­ta­men­to. In teoria anche l’ela­bo­ra­zio­ne diretta di un’in­vo­ca­zio­ne è possibile con il decorator.

A cosa serve il decorator design pattern?

Come per altri modelli GoF, come lo strategy pattern o il builder pattern, il decorator pattern ha l’obiettivo di gestire le com­po­nen­ti dei software orientati agli oggetti in modo che possano essere riu­ti­liz­za­te in maniera più semplice e fles­si­bi­le. A questo scopo il modello offre la soluzione per poter ag­giun­ge­re o eliminare le di­pen­den­ze a un oggetto in maniera dinamica e durante l’ese­cu­zio­ne, se ne­ces­sa­rio. Proprio per questo motivo il pattern rap­pre­sen­ta una buona al­ter­na­ti­va all’utilizzo delle sot­to­clas­si. Se queste sono infatti in grado di com­ple­ta­re una classe in molti modi diversi, non per­met­to­no però di apportare modifiche durante l’ese­cu­zio­ne.

N.B.

Una com­po­nen­te software può essere ampliata da un qualsiasi numero di classi decorator. Tuttavia, queste esten­sio­ni rimangono del tutto in­vi­si­bi­li per le istanze che vi accedono, tanto che queste non si accorgono che alla classe prin­ci­pa­le sono state aggiunte delle sot­to­clas­si.

Fun­zio­na­men­to del decorator pattern: diagramma UML

Il decorator e le classi decorator (Con­cre­te­De­co­ra­tor) di­spon­go­no delle stesse in­ter­fac­ce delle com­po­nen­ti software che hanno il compito di decorare (Con­cre­te­Com­po­nent) e sono dello stesso tipo. Questo è im­por­tan­te per la gestione delle richieste in quanto serve a stabilire se queste debbano essere inoltrate con o senza modifiche, nel caso in cui non sia il decorator a farsi carico della loro ela­bo­ra­zio­ne. Il decorator pattern definisce questa in­ter­fac­cia ele­men­ta­re come “component”, teo­ri­ca­men­te cor­ri­spon­den­te a una su­per­clas­se astratta.

La relazione tra la com­po­nen­te base e il decorator può essere meglio il­lu­stra­ta grazie a una rap­pre­sen­ta­zio­ne grafica sotto forma di diagrammi di classe in UML. Nello schema relativo al decorator design pattern qui sotto abbiamo usato il lin­guag­gio di mo­del­la­zio­ne grafica per la pro­gram­ma­zio­ne orientata agli oggetti.

I vantaggi e gli svantaggi del decorator pattern in sintesi

Con­si­de­ra­re l’utilizzo del modello decorator durante l’ideazione di un software risulta ben giu­sti­fi­ca­to per una serie di motivi. Primo di tutto, emerge il grado di fles­si­bi­li­tà che ottenete adottando una struttura decorator, la quale permette di ampliare le classi con nuovi com­por­ta­men­ti sia durante la fase di com­pi­la­zio­ne che durante l’ese­cu­zio­ne. Inoltre, con l’approccio decorator evitate com­ple­ta­men­te l’ere­di­ta­rie­tà e la gerarchia che ne consegue, il che migliora si­gni­fi­ca­ti­va­men­te la leg­gi­bi­li­tà del codice di pro­gram­ma­zio­ne.

Con la sud­di­vi­sio­ne della fun­zio­na­li­tà su più classi decorator mi­glio­ra­no anche le pre­sta­zio­ni del software, ottenendo così di poter ri­chia­ma­re ed eseguire qualsiasi funzione di cui si ha bisogno. Non si ha, invece, questa pos­si­bi­li­tà di ot­ti­miz­za­zio­ne delle risorse con una classe di base complessa che mette a di­spo­si­zio­ne tutte le funzioni in modo per­ma­nen­te.

Lo sviluppo basato sul decorator pattern porta con sé anche degli svantaggi. L’in­se­ri­men­to del modello aumenta au­to­ma­ti­ca­men­te la com­ples­si­tà del software. In par­ti­co­la­re, l’in­ter­fac­cia decorator è so­li­ta­men­te molto carica di scritte oltre a usare molte nuove ter­mi­no­lo­gie, il che la rende tutt’altro che adatta ai prin­ci­pian­ti.

Un ulteriore svan­tag­gio consiste nell’elevato numero di oggetti decorator, per i quali è con­si­glia­bi­le una si­ste­ma­tiz­za­zio­ne dedicata, per evitare di avere problemi con la strut­tu­ra­zio­ne, come già accade quando si lavora con le sot­to­clas­si. La catena delle chiamate degli oggetti decorati (le com­po­nen­ti software ampliate) ap­pe­san­ti­sco­no inoltre l’in­di­vi­dua­zio­ne di eventuali errori e l’intero processo di debugging in generale.

Vantaggi Svantaggi
Elevato grado di fles­si­bi­li­tà Elevata com­ples­si­tà del software, in par­ti­co­la­re dell’in­ter­fac­cia decorator
Am­plia­men­to della fun­zio­na­li­tà delle classi senza ere­di­ta­rie­tà Poco adatto agli utenti alle prime armi
Codice di pro­gram­ma­zio­ne di facile lettura Elevato numero di oggetti
Chiamata delle funzioni ot­ti­miz­za­ta per le risorse Processo di debugging ap­pe­san­ti­to

Decorator design pattern: gli scenari d’impiego classici

Il decorator pattern offre la base per oggetti esten­si­bi­li dinamici e tra­spa­ren­ti di un software. In modo par­ti­co­la­re questo modello trova ap­pli­ca­zio­ne nelle com­po­nen­ti delle in­ter­fac­ce grafiche utente, dette anche GUI. Ad esempio: per dotare un campo di testo di un bordo è suf­fi­cien­te uti­liz­za­re un apposito decorator che viene attivato in maniera “in­vi­si­bi­le” tra il campo di testo (l’oggetto) e la chiamata, per ag­giun­ge­re questo nuovo elemento di in­ter­fac­cia.

Un esempio molto co­no­sciu­to di utilizzo del decorator pattern sono le co­sid­det­te classi stream della libreria Java, re­spon­sa­bi­li per la gestione degli input e output di dati. Qui, le classi decorator vengono usate spe­ci­fi­ca­men­te per ag­giun­ge­re nuove ca­rat­te­ri­sti­che e in­for­ma­zio­ni di stato al flusso di dati o mettere a di­spo­si­zio­ne nuove in­ter­fac­ce.

Java non è l’unico lin­guag­gio di pro­gram­ma­zio­ne con il quale viene uti­liz­za­to il decorator pattern. Anche i seguenti linguaggi impiegano il modello di design:

  • C++
  • C#
  • Go
  • Ja­va­Script
  • Python
  • PHP

Esempio concreto di ap­pli­ca­zio­ne del decorator pattern

Il pre­ce­den­te elenco di vantaggi e svantaggi dimostra che il decorator design pattern non è adatto a qualunque tipo di software. Tuttavia, laddove si necessita di apportare modifiche a po­ste­rio­ri a una classe, questo modello di design rap­pre­sen­ta una soluzione di prim’ordine. In un articolo del suo blog ZKMA, Marcel Schöni fornisce un buon esempio d’impiego delle classi “decorate”.

La si­tua­zio­ne di partenza da lui descritta è quella di un software che serve a con­sul­ta­re i nomi di persone at­tra­ver­so una classe astratta de­no­mi­na­ta “di­pen­den­te”. La prima lettera del nome cercato dall’utente è sempre minuscola. Non essendo possibile apportare una modifica a po­ste­rio­ri, viene im­ple­men­ta­ta la classe decorator “Di­pen­den­te­De­co­ra­tor”, che opera tramite la stessa in­ter­fac­cia e che rende possibile l’utilizzo del metodo getName(). Inoltre, il decorator viene dotato di una logica che assicura che la prima lettera sia scritta con la maiuscola. Il codice di esempio appare così:

public class DipendenteDecorator implements Person {
private Dipendente dipendente;
public DipendenteDecorator(Dipendente dipendente){
	this.dipendente = dipendente;
}
public String getName(){
	// invoca il metodo della classe Dipendente
	String name = dipendente.getName();
	// assicura che la prima lettera sia maiuscola
	name = Character.toUpperCase(name.charAt(0)) 
	+ name.substring(1, name.length());
	return name;
}
}
Vai al menu prin­ci­pa­le