Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Programmazione Activity Oriented con le Closures in Groovy

Link copiato negli appunti

In questo breve articolo cercheremo di offrire un esempio di approccio alla scrittura di codice activity oriented: cercheremo di migliorare la coesione e la leggibilità delle varie attività tramite le closure di groovy, in attesa di poter utilizzare questi costrutti anche nelle prossime versioni di java.

Attualmente esistono com è noto diversi pattern/metodologie di programmazione che aiutano nello sviluppo di applicazioni più o meno complesse, e che aiutano lo sviluppatore a rendere il codice il più possibile leggibile, semplice e compatto. Non è sempre purtroppo facile definire quanto sia effettivamente leggibile il codice sviluppato.

Per quanto la coesione logica sia un elemento da sempre auspicato dai programmatori, a volte la scarsa leggibilità può essere data anche da una "eccessiva" coesione logica del codice stesso. Vediamo un esempio in Java:

public class Ex1 {
	private ArrayList cache ;
	public Ex1() {
		cache = new ArrayList();
	}
	public boolean storeInCache(int elem) {
		if (!cache.contains(elem)) cache.add(elem);
	}
	private int sommaElementi(Vector v) {
		System.out.println("sommo gli elementi"));
		Int sum = 0;
		for (int i=0; i < v.size(); i++) {
			sum += (Integer) v.elementAt(i).intValue();
		}
	}
	
	Integer x = new Integer(0);
	Integer y = new Integer(0);
	Vector elems = new Vector();
	elems.add(x);
	elems.add(y);
	
	int somma = sommaElementi(elems);
	if (somma > 5 ) {
		storeInCache(somma)
	}
	System.out.println(somma);
}

Questo è un esempio particolarmente semplice, un piccolo programma che data una serie di elementi inserire in un Vector, ne effettua la somma e se maggiore di 5 la memorizza in una variabile cache.

Il problema è che il codice è eccessivamente esteso "in verticale", nel senso che per capire la logica bisogna leggere più righe di codice progressive:

Classico esempio di codice da leggere dall'alto in basso

Esempio di codice da leggere dall'alto in basso

Le righe in nero sono rappresentano i commenti che descrivono le attività che sono tra loro collegate.

Il problema è che sono poco coese tra loro, e in termini di lettura sono eccessivamente "sparse" in verticale.

Si potrebbe risolvere il problema rifattorizzando il codice scrivendo un metodo per ognuna di queste righe, stando attenti a eventuali variabili coinvolte ed al loro scope, ma il miglioramento non sarebbe probabilmente sostanziale.

Implementazione di attività con le closure groovy

A questo punto potrebbe essere utile utilizzare delle closures, e decidiamo di scrivere un piccolo esempio mediante il linguaggio groovy, così da ottenere una classe compilata eventualmente riutilizzabile anche da java stesso.

È bene infatti ricordare che al momento l'utilizzo di closure in java non è ancora supportato (si parla di questa estensione del linguaggio per le prossime versioni, forse già la 8), e potremmo implementare qualcosa di funzionalmente simile alle closure soltanto utilizzando classi anonime.

Sotto questo aspetto quindi le closure di groovy ci aiutano.

Distinguiamo tre sezioni nel codice dell'esempio:

  1. Area Dichiarazione Variabili
  2. Area Dichiarazione Attività
  3. Area Flusso Elaborativo

Vediamo il codice equivalente in groovy:

// ### Area Dichiarazione Variabili ###
def cache = new ArrayList()
def listaElementi = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
def sum = 0;
// ### Area Dichiarazione Attività ###
def execute = {
	Object[] closures ->
	def currentClosure = null;
	closures.each() {
		currentClosure = it.call(currentClosure)
	}
	return currentClosure
}
def seNumeroElementiListaSupera5 = {
	Object[] closures -> if (listaElementi.size() > 5) execute(closures) }
	def sommaTuttiGliElementiDellaLista = {
	println "sommo gli elementi dell'array"
	listaElementi.each { sum += it }
	sum
}
def memorizzaSommaInCache = {
	if (!cache.contains(it)) cache.add(it);
	it
}
def stampaSomma = { println "stampo risultato somma $it" }
// ### Area Flusso Elaborativo ###
seNumeroElementiListaSupera5(sommaTuttiGliElementiDellaLista, memorizzaSommaInCache,
stampaSomma)

L'ultima riga di codice: seNumeroElementiListaSupera5(sommaTuttiGliElementiDellaLista, memorizzaSommaInCache, stampaSomma) è sufficientemente chiara e mostra in maniera compatta l'attività da eseguire, dove:

  1. seNumeroElementiListaSupera5: restituisce true o false in base al numero di elementi
  2. sommaTuttiGliElementiDellaLista: somma gli elementi
  3. memorizzaSommaInCache: riceve in input l'output della closure
  4. sommaTuttiGliElementiDellaLista e se il numero di elementi è maggiore di 5 lo memorizza in una variabile passando poi la somma alla closure successiva
  5. stampaSomma: stampa la somma degli elementi

Le closure interne quindi eseguono una attività in ordine di chiamata comunicando tra loro in catena (ogni closure riceve un input da quella precedente e restituisce un risultato a quella
successiva, se esiste).

generalizzazione per l'utilizzo di loop

Il concetto si può estendere anche ai loop aggiungendo la closure seguente (generalizzabile):

def cicla2volte(Closure c) {
    2.times {
        c.call()
    }
}

Quindi:

cicla2volte( {seNumeroElementiListaSupera5(sommaTuttiGliElementiDellaLista, memorizzaSommaInCache, stampaSomma) })

c'è solo un aspetto da chiarire: per generalizzare le closure occore che non agiscano su variabili fisse laddove è necessario applicare l'attività su diverse entità; il nostro ciclo for esegue l'attività due volte sugli stessi dati poiché la closure:

def sommaTuttiGliElementiDellaLista = {
    println "sommo gli elementi dell'array"
    listaElementi.each { sum += it }
    sum
}

agisce sempre su listaElementi.

Per generalizzare basta far riferimento ad una struttura che contenga tutte le listaElementi.

Questo il codice (dove usiamo le closure per accorpare il codice sequenziale):

// ### DECLARATION ELEMENTS ###
def cache = new ArrayList()
def struttura = [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, 2, 3, 4]]
def listaElementi = struttura[0]
def sum = 0;
// ### DECLARATION GENERIC CLOSURES ###
def execute = {
	Object[] closures ->
	def currentClosure = null;
	closures.each() {
		currentClosure = it.call(currentClosure)
	}
	return currentClosure
}
def forDo = {}
indexFor, Object[] closures ->indexFor.times {
	execute(closures)
}
// #### ACTIVITY : SOMMA ELEMENTI ####
def seNumeroElementiListaSupera5 = {
	Object[] closures ->
	if (listaElementi.size() > 5) {
		println 'rilevato NumeroElementiListaSupera5'
		execute(closures)
	}
}
def seNumeroElementiListaNonSupera5 = {
	Object[] closures ->if (listaElementi.size()

In genere quindi l'attività di sviluppo si distingue nella realizzazione di micro attività che vanno assemblate per costruire attività più grandi.

tutte le Activity accedono ad una stessa variabile con scope globale, in questo modo potrebbe non esserci traccia precisa di quale tra le Activities usa una determinata variabile globale:

accesso ad una variabile di scope globale dalle varie activity

Oltretutto la variabile potrebbe essere esposta a modifiche non volute e non evidenti occorre quindi un meccanismo che centralizza l’accesso alla variabile come nel codice seguente:

// ### DECLARATION ELEMENTS ###
def cache = new ArrayList()
def struttura = [[1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, 2, 3, 4]]
def listaElementi = struttura[0]
def sum = 0;
def dataContainerStruct = ['struttura': struttura,
'listaElementi': listaElementi]
// ### DECLARATION GENERIC CLOSURES ###
def execute = {
	Object[] closures ->
	def currentClosure = null;
	closures.each() {
		currentClosure = it.call(currentClosure)
	}
	return currentClosure
}
def forDo = {
	indexFor, Object[] closures ->
	indexFor.times {
		execute(closures)
	}
}
def getProperty = {
	key, requester -> println "requested property [$key] from [$requester]"
	dataContainerStruct[key]
}
def putProperty = {
	key, value, requester -> println "update property [$key] from [$requester]"
	dataContainerStruct[key] = value
}
// #### ACTIVITY : SOMMA ELEMENTI ####
def seNumeroElementiListaSupera5 = {
	Object[] closures -> if (getProperty('listaElementi','seNumeroElementiListaSupera5').size() > 5) { execute(closures) }
}
def seNumeroElementiListaNonSupera5 = {
	Object[] closures -> if (getProperty('listaElementi','seNumeroElementiListaNonSupera5').size()

Che eseguito da sulla console di output:

requested property [listaElementi] from [seNumeroElementiListaSupera5]
rilevato NumeroElementiListaSupera5
Closure [ sommaTuttiGliElementiDellaLista ]
requested property [listaElementi] from [sommaTuttiGliElementiDellaLista]
Closure [ memorizzaSommaInCache ]
Closure [ stampaSomma ]
stampo risultato somma 55
requested property [listaElementi] from [seNumeroElementiListaNonSupera5]
requested property [listaElementi] from [seNumeroElementiListaSupera5]
rilevato NumeroElementiListaSupera5
Closure [ sommaTuttiGliElementiDellaLista ]
requested property [listaElementi] from [sommaTuttiGliElementiDellaLista]
Closure [ memorizzaSommaInCache ]
Closure [ stampaSomma ]
stampo risultato somma 110
requested property [listaElementi] from [seNumeroElementiListaNonSupera5]

In questo modo abbiamo traccia di quale Closure viene eseguita e cosa richiede.

Se vogliamo ulteriormente complicare le cose possiamo prevedere delle lista di accesso per proteggere le variabili cioè definire per ciascuna di esse quale activities può utilizzarla.

Riepilogando quanto descritto costituisce solo una ricerca su quanto un linguaggio di programmazione può essere sintatticamente utilizzato per costruire un modello semantico diverso rispetto agli standard attuali, in particolare per questo modello.

I vantaggi sono:

  1. Il codice risulta molto più compatto nella lettura: per capirne il funzionamento si può
    leggere la parte ‘Business Code Flow’ per avere subito una idea di cosa fa e poi agire
    analizzando le micro activity
  2. ciasuna micro activity viene realmente vista come un blocco di codice

Note:

  1. Volendo si può limitare il dominio di utilizzo delle micro activitiyes facendo in modo che solo la prima micro activity acceda ai dati di input e che la stessa li passi alla successiva (con l'ultima che che restituisce tutto) questa limita l'accesso ai dati globali solo alla
    prima tra esse.
  2. si può associare una struttura dati di input dedicata ad ogni activity in modo da tener traccia di esse

Il campo di applicazione è molto vasto (si pensi ad esempio a query annidate).


Ti consigliamo anche