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
  4. 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)

    1. seNumeroElementiListaSupera5 true false
    2. sommaTuttiGliElementiDellaLista
    3. memorizzaSommaInCache
    4. sommaTuttiGliElementiDellaLista
    5. stampaSomma

    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) })

    for
    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:

    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