Le strutture di dati dinamiche come le strutture ad albero di un file ma­na­ge­ment o di un’in­ter­fac­cia di programma ri­chie­do­no una struttura ge­rar­chi­ca chiara e il più possibile ine­qui­vo­ca­bi­le. L’im­ple­men­ta­zio­ne di tali strutture spesso non è per niente semplice. Ad esempio, è im­por­tan­te as­si­cu­rar­si che il tipo di oggetto non debba essere ri­chia­ma­to ogni volta prima dell’effettiva ela­bo­ra­zio­ne dei dati, poiché un tale scenario non sarebbe né ef­fi­cien­te né per­for­man­te. So­prat­tut­to nel caso in cui molti oggetti primitivi in­con­tri­no oggetti compositi, si consiglia l’utilizzo del co­sid­det­to Composite design pattern (in italiano “modello di pro­get­ta­zio­ne composito”). L’approccio di pro­get­ta­zio­ne del software consente ai client di trattare gli oggetti in­di­vi­dua­li e compositi in modo uniforme, rendendo le dif­fe­ren­ze inin­fluen­ti per il client.

Che cos’è il Composite pattern?

Il Composite design pattern, ab­bre­via­to in Composite pattern, è uno dei 23 design pattern per lo sviluppo software che sono stati ri­la­scia­ti nel 1994 da Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides (noti anche come “Gang of Four”). Come il Facade pattern e il Decorator pattern, il Composite design pattern si annovera tra i modelli strut­tu­ra­li la cui funzione fon­da­men­ta­le è quella di riunire oggetti e classi in strutture di maggiori di­men­sio­ni.

Quali problemi risolve il Composite design pattern?

Il fine prin­ci­pa­le del Composite design pattern è, come in tutti i modelli della GoF, quello di af­fron­ta­re al meglio i problemi ri­cor­ren­ti di pro­get­ta­zio­ne nello sviluppo orientato agli oggetti. Il risultato che si desidera ottenere è un software il più possibile fles­si­bi­le che si con­trad­di­stin­gue per oggetti facili da im­ple­men­ta­re, testabili, so­sti­tui­bi­li e riu­ti­liz­za­bi­li. A questo scopo, il Composite pattern descrive una possibile strada per trattare singoli oggetti e oggetti compositi alla stessa maniera. In questo modo si ottiene una struttura a oggetti che è semplice da com­pren­de­re e consente un accesso di massima ef­fi­cien­za al client. Inoltre viene mi­ni­miz­za­ta anche la su­scet­ti­bi­li­tà del codice agli errori.

Composite design pattern: rap­pre­sen­ta­zio­ne grafica (UML)

Per im­ple­men­ta­re le ef­fi­cien­ti gerarchie parte-tutto già men­zio­na­te, il modello Composite prevede l’im­ple­men­ta­zio­ne di un’in­ter­fac­cia dei com­po­nen­ti unitaria per semplici oggetti-parte, chiamati anche oggetti Leaf (inglese per “foglia”, ci si riferisce infatti alla metafora dell’albero dove le foglie sono una parte del tutto) e degli oggetti Composite (appunto compositi). I singoli oggetti Leaf integrano di­ret­ta­men­te questa in­ter­fac­cia, gli oggetti Composite tra­smet­to­no au­to­ma­ti­ca­men­te le concrete richieste del client all’in­ter­fac­cia ai loro com­po­nen­ti su­bor­di­na­ti. Per il client non ha im­por­tan­za con quale tipo di oggetto abbia a che fare (se parte o tutto), perché si tratta sem­pli­ce­men­te di in­di­riz­zar­si all’in­ter­fac­cia.

Il seguente diagramma di classe nel lin­guag­gio di mo­del­la­zio­ne UML aiuta a chiarire i rapporti e le gerarchie in un software basato sul Composite pattern.

Quali sono i vantaggi e gli svantaggi del modello Composite?

Il Composite pattern è una costante nello sviluppo del software. In par­ti­co­la­re i progetti con strutture for­te­men­te annidate possono ap­pro­fit­ta­re dell’approccio pratico per l’or­ga­niz­za­zio­ne di oggetti: non importa che si tratti di un oggetto primitivo o composito, con di­pen­den­ze semplici o complesse, perché la pro­fon­di­tà e larghezza degli an­ni­da­men­ti è fon­da­men­tal­men­te ir­ri­le­van­te nel Composite design pattern. Il client può tran­quil­la­men­te ignorare la dif­fe­ren­za tra i tipi di oggetto e in questo modo non sono ne­ces­sa­rie funzioni separate per l’accesso. In questo modo si ha il vantaggio che il codice del client rimane snello.

Un altro punto a favore del modello Composite è la fles­si­bi­li­tà e l’esten­si­bi­li­tà che il pattern con­fe­ri­sce a un software: l’in­ter­fac­cia dei com­po­nen­ti uni­ver­sa­le consente di collegare nuovi oggetti Leaf e Composite senza va­ria­zio­ni del codice, sia dal lato client che nel caso in cui si tratti di strutture oggetti pre­e­si­sten­ti.

No­no­stan­te il Composite pattern e la sua in­ter­fac­cia unitaria offrano numerosi vantaggi, questa soluzione non è esente da punti deboli: infatti, in par­ti­co­la­re l’in­ter­fac­cia può causare non poche dif­fi­col­tà agli svi­lup­pa­to­ri. Già l’im­ple­men­ta­zio­ne pone grandi sfide per i re­spon­sa­bi­li, perché bisogna per esempio scegliere quali ope­ra­zio­ni devono essere definite nell’in­ter­fac­cia e quali nelle classi Composite. Inoltre eventuali suc­ces­si­ve modifiche delle proprietà del Composite (ad esempio le li­mi­ta­zio­ni, quali elementi figlio sono ammessi) si rivelano com­pli­ca­ti e difficili da rea­liz­za­re.

Vantaggi Svantaggi
Ha tutto ciò che serve per rap­pre­sen­ta­re strutture oggetti for­te­men­te annidate L’im­ple­men­ta­zio­ne dell’in­ter­fac­cia dei com­po­nen­ti è molto com­pli­ca­ta
Codice di pro­gram­ma­zio­ne snello e fa­cil­men­te com­pren­si­bi­le Le suc­ces­si­ve modifiche delle proprietà del Composite sono com­pli­ca­te e difficili da im­ple­men­ta­re
Buona esten­si­bi­li­tà  

Scenari di ap­pli­ca­zio­ne per il Composite pattern

L’utilizzo dei modelli Composite è par­ti­co­lar­men­te van­tag­gio­so quando occorre ef­fet­tua­re ope­ra­zio­ni su strutture di dati dinamici, le cui gerarchie sono di larghezza e/o pro­fon­di­tà complesse. Si parla in questi casi anche di struttura ad albero binaria, che è in­te­res­san­te per i più svariati scenari software e viene uti­liz­za­ta fre­quen­te­men­te. Alcuni tipici esempi sono:

Sistemi di file: i sistemi di file sono tra i com­po­nen­ti più im­por­tan­ti del software dei di­spo­si­ti­vi. Con il Composite pattern si possono mappare in modo ottimale: i singoli file come oggetti Leaf e le cartelle, che a propria volta possono contenere file o ulteriori cartelle, come oggetti Composite.

Menu dei software: anche i menu dei programmi rap­pre­sen­ta­no un caso d’utilizzo tipico per una struttura ad albero binaria basata sul Composite design pattern. Nella barra del menu si trovano una o più voci di base (oggetti Composite) come “file”. Essi con­sen­to­no l’accesso a varie voci del menu che sono di­ret­ta­men­te clic­ca­bi­li (Leaf) o con­ten­go­no ulteriori menu (Composite).

In­ter­fac­ce utente grafiche (GUI): le strutture ad albero e il modello Composite possono giocare un ruolo im­por­tan­te anche nella pro­get­ta­zio­ne di in­ter­fac­ce utente grafiche. Accanto ai semplici elementi Leaf come pulsanti, campi di testo o caselle di controllo, ci sono i con­te­ni­to­ri Composite, come frame o panel, che as­si­cu­ra­no una struttura chiara e una maggiore com­pren­si­bi­li­tà.

Esempio di codice: Composite pattern

Java è si­cu­ra­men­te il lin­guag­gio di pro­gram­ma­zio­ne dove l’approccio del Composite pattern è più sal­da­men­te con­so­li­da­to. Il modello è tra l’altro anche la base degli Abstract Window Toolkits (AWT), un’API pratica e popolare con circa 50 classi Java pronte all’uso per lo sviluppo di in­ter­fac­ce Java mul­ti­piat­ta­for­ma. Nel seguente esempio di codice, che si basa sull’articolo “Composite Design Pattern in Java” su baeldung.com, abbiamo quindi scelto questo amato lin­guag­gio di pro­gram­ma­zio­ne.

Nell’esempio pratico viene mostrata la struttura ge­rar­chi­ca dei reparti (de­part­men­ts) in un’azienda (company). In­nan­zi­tut­to viene definita perciò l’in­ter­fac­cia del com­po­nen­te “de­part­ment”:

public interface Department {
	void printDepartmentName();
}

In seguito vengono definite le due semplici classi LeafFi­nan­cial­De­part­ment” (per il reparto finanze) e “Sa­le­sDe­part­ment” (per il reparto vendite). Entrambe im­ple­men­ta­no il metodo print­De­part­ment­Na­me() dall’in­ter­fac­cia del com­po­nen­te, ma non con­ten­go­no altri oggetti de­part­ment.

public class FinancialDepartment implements Department {
	private Integer id;
	private String name;
	public void printDepartmentName() {
		System.out.println(getClass().getSimpleName());
	}
	// standard constructor, getters, setters
}
public class SalesDepartment implements Department {
	private Integer id;
	private String name;
	public void printDepartmentName() {
		System.out.println(getClass().getSimpleName());
	}
	// standard constructor, getters, setters
}

Una classe Composite cor­ri­spon­den­te alla gerarchia viene infine definita come “Head­De­part­ment”. Questa classe è composta da più com­po­nen­ti De­part­ment e contiene metodi per ag­giun­ge­re ulteriori elementi (add­De­part­ment) o rimuovere elementi esistenti (re­mo­ve­De­part­ment), in aggiunta al metodo print­De­part­ment­Na­me():

public class HeadDepartment implements Department {
	private Integer id;
	private String name;
	private List<department> childDepartments;</department>
	public HeadDepartment(Integer id, String name) {
		this.id = id;
		this.name = name;
		this.childDepartments = new ArrayList<>();
	}
	public void printDepartmentName() {
		childDepartments.forEach(Department::printDepartmentName);
	}
	public void addDepartment(Department department) {
		childDepartments.add(department);
	}
	public void removeDepartment(Department department) {
		childDepartments.remove(department);
	}
}
Vai al menu prin­ci­pa­le