Ognuno di noi (informatici) usa il pattern composite quasi tutti i giorni della sua vita, probabilmente senza accorgersene.

Facciamo una prova: mettetevi davanti al computer (dietro non si vede niente! :-) e aprite una finestra associata al disco fisso (assumo per ipotesi che abbiate Windoze o qualche sistema simile).

Vedrete una serie di icone che rappresentano file o directory. Se selezionate una di quelle icone e la trascinate da qualche parte state iniziando uno spostamento/copia di un file/directory...

Se invece chiedete le proprietà di un certo oggetto vi verrà di sicuro fornita anche l'informazione riguardante lo spazio occupato da quell'oggetto... tale spazio è la dimensione del file (se di file si tratta) mentre è la somma degli spazi occupati dagli "oggetti" contenuti se avete selezionato una directory.

Detto in termini OO quello che avete fatto è stato semplicemente chiedere un "servizio" (esecuzione di un metodo, in questo caso una specie di metodo getSize() ) ad un oggetto composite ... il servizio è "dimmi quanto occupi" e il polimorfismo vi ha mascherato l'effettiva implementazione (nel senso che non vi interessa se avete chiesto l'informazione ad un FILE o ad una DIRECTORY).

Questo è il senso dell'esistneza del composite.

Guardate lo schema del composite coi nomi cambiati opportunamente... ;-)

Figura 1 - Struttura UML del pattern Composite applicata a file e directory
Figura 1 - Struttura UML del pattern Composite applicata a file e directory

(i post-it sono note in pseudocodice) Vi ricorda qualcosa? Ora però vi parlerò di un altro esempio di applicazione...

Scopo applicativo

In elettronica si usano spesso quelli che vengono chiamati FILTRI. Esistono i cosiddetti "passa-basso", "passa-alto", "passa-banda" e altri creati ad hoc.

Essi sono rappresentabili (descrivibili) tramite funzioni matematiche banalissime tipo y=F(x). Nel caso dei filtri i due domini (ingresso e uscita) sono diversi, nel senso che non appartengono allo stesso campo, normalmente l'ingresso appartiene al campo delle frequenze mentre l'uscita rappresenta l'ampiezza da dare al segnale (data la sua frequenza).

Per quel che ci interessa possiamo "fregarcene" di cosa rappresentano i due dominii, a noi basta sapere che possiamo implementare delle funzioni definite per intervalli e in ogni intervallo possiamo usare una classe filtro già disponibile.

In questa realizzazione abbiamo due tipi di classi principali: i FILTRI SEMPLICI e i FILTRI COMPOSTI .

  • Un filtro semplice è quello che calcola la funzione out() DIRETTAMENTE.
  • Un filtro composto è quello che calcola la funzione out() BASANDOSI SUI FILTRI CHE CONTIENE (vedere lo schema poco più avanti). Questo filtro composto, per calcolare la propria funzione out() DELEGA il cacolo ad uno dei filtri contenuti.
  • un filtro composto CONTIENE alcuni filtri SEMPLICI (o COMPOSTI a loro volta ;-)
  • ogni filtro "copre" un certo range di valori in ingresso
  • l'insieme dei filtri contenuti NON deve avere "buchi" (da controllare in fase di inizializzazione di un filtro composto)
  • l'insieme dei filtri contenuti NON deve avere "sovrapposizioni" (da controllare in fase di inizializzazione di un filtro composto)

Uno schema formale

Ed ecco lo schema formale del composite applicato all'esempio (è in OMT perchè quando l'ho realizzato non usavo ancora UML)

Figura 2 - Schema OMT del pattern Composite applicato ai filtri
Figura 2 - Schema OMT del pattern Composite applicato ai filtri

Nota importante: per realizzare nuovi filtri

Se volete aggiungere un nuovo filtro non dovete far altro che:

per un filtro SEMPLICE: estendere Filter (o una sua sottoclasse) e implementare out() in modo da calcolare una funzione che vi serve (più i metodi di inizializzazione) per un filtro COMPOSTO estendere FilterArray (o una sua sottoclasse) e implementare i SOLI metodi di inizializzazione! Il metodo out() è già corretto quello di FilterArray !

Descrizione schematica semi-formale

Definizione dei concetti di base di un filtro

Un filtro astratto nella sua minima descrizione è costituito da questi attributi/funzionalità:

MIN l'inizio del dominio di ingresso MAX la fine del dominio di ingresso out (in) la funzione che ogni filtro calcola, chi usa il filtro sa che può chiamare la funzione out() all'interno del range di validità del filtro stesso . Al di fuori del range di validità il metodo out() lancia un'eccezione.

Figura 3 - Rappresentazione schematica dei concetti di base di un filtro
Figura 3 - Rappresentazione schematica dei concetti di base di un filtro

Esempi di filtri realizzati

Un filtro PIATTO (NON è composto)

Figura 4 - Filtro piatto
Figura 4 - Filtro piatto

Un filtro LINEARE (NON è composto)

Figura 5 - Filtro lineare
Figura 5 - Filtro lineare

Un filtro CUSPIDE (composto da due Linear)

Figura 6 - Filtro a cuspide
Figura 6 - Filtro a cuspide

Un filtro TRAPEZOIDALE, detto anche "passa-banda" (composto da due Linear e un Flat)

Figura 7 - Filtro trapezoidale
Figura 7 - Filtro trapezoidale

Esempio

Qui vedete l'esempio che trovate nel sorgente allegato

E' una rappresentazione "applettizzata" di alcuni filtri di prova. Sono tre cuspidi, un trapezio, un lineare e un flat. Vi basta lanciare appletviewer _applet.html

Figura 8 - Applet dimostrativa con i filtri di prova
Figura 8 - Applet dimostrativa con i filtri di prova

Note

Per informazioni ulteriori guardate anche la JavaDoc allegata al sorgente o mandatemi mail... ;-)