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

Java OOP: Programmazione Data Centric

È possibile rifattorizzzare il codice per concentrare l'attenzione maggiormente sui dati? In questo articolo concentriamo la nostra attenzione su un approccio "Data Centric".
È possibile rifattorizzzare il codice per concentrare l'attenzione maggiormente sui dati? In questo articolo concentriamo la nostra attenzione su un approccio "Data Centric".
Link copiato negli appunti

Nell'Object Orient Programming la classe è uno dei componenti fondamentali nel paradigma, ed è spesso vista dai programmatori come poco più che un contenitore di codice dotato di uno stato (i dati interni, più o meno strutturati) ed un comportamento (i metodi).

La Classe come contenitore di dati e comportamenti

Ragionando in questi termini potremmo individuare inoltre due diverse tipologie di dati che vengono manipolati dal codice:

  • Dati interni alla classe.
  • Dati esterni alla classe.

Il codice interno utilizza i dati per modificarli, ovvero è il comportamento che altera o aggiorna lo stato di una classe.

Classe: stato & comportamento

Classe: stato & comportamento

Programmazione Data Centric: i dati al centro, separati dai comportamenti

Vedremo ora un concetto diverso: cioè i dati che verranno separati dal codice di comportamento. Questo porterà ad un basso livello di coesione per ogni classe, ma ci permetterà di spostare l'attenzione più sui dati che sul codice, introducendo alcuni concetti legati agli aspetti (AOP)

Un progetto di esempio

la struttura in Eclipse

Creiamo un progetto in Eclipse e chiamiamolo DataCentricProgramming, questa la struttura che
andremo a realizzare:

il progetto in eclipse

il progetto in eclipse

Andiamo ora a definire le varie classi...

Un progetto di esempio

La classe DataStorage, per gestire i dati

Iniziamo creando la classe DataStorage nel relativo package:

package com.data;
public class DataStorage {
private String message;
	public DataStorage() {
		super();
	}
	public String getMessage() {
		return message;
	}
	public void setMessage(String message) {
		this.message = message;
	}
}

Questa classe verrà utilizzata come un semplice contenitore di dati.

Le classi con la logica di business

Creiamo ora quest'altra classe:

package com.business;
import com.actions.ActionExecutorInterface;
import com.data.DataStorage;
public class DataStorageWorkflow {
public static DataStorage dataStorage;
	public DataStorageWorkflow() {
		super();
	}
	public DataStorage getDataStorage() {
		return dataStorage;
	}
	public Object execute(ActionExecutorInterface action) {
		return action.execute();
	}
}

Per quanto concerne quest'ultima classe è da notare prima di tutto il riferimento statico DataStorage nel metodo getDataStorage(), ed il metodo execute(), che accetta un parametro di tipo interfaccia che andremo a definire.

Questa l'interfaccia in oggetto:

package com.actions;
public interface ActionExecutorInterface {
	public Object execute();
}

É una interfaccia che permette semplicemente di eseguire del codice, e vedremo tra poco come utilizzarla.

Creiamo ora una classe Action, cioè una Business Class che dovrà eseguire del codice legato al dominio applicativo:

package com.actions;
import com.business.DataStorageWorkflow;
import com.data.DataStorage;
public class PrintMessageAction implements ActionExecutorInterface {
	public PrintMessageAction() {
		super();
	}
	public void foo() {
		System.out.println(DataStorageWorkflow.dataStorage.getMessage());
	}
	public Object execute() {
		foo();
		return null;
	}
}

Qui il discorso si fa più interessante: la classe action esegue una attività che viene richiamata attraverso il
metodo execute implementato dell'interfaccia ActionExecutorInterface.

Ancora il metodo foo() utilizza il riferimento statico al dataContainer per recuperare un messaggio che dovrebbe essere interno alla classe stessa:

public void foo() {
System.out.println(DataStorageWorkflow.dataStorage.getMessage());
}

Questa soluzione costituisce una alternativa all'implementazione:

public class PrintMessageAction implements ActionExecutorInterface {
	private String message;
	public String getMessage() {
		return message;
	}
	public void setMessage(String message) {
		this.message = message;
	}
	public void foo() {
		System.out.println(getMessage());
	}
}

In sostanza abbiamo spostato nel DataContainer una variabile di classe di printMessageAction().

Vediamo ora come implementare le attività.

Implementazione delle attività

Creiamo ora una classe Workflow executor che esegua cioè le attività che implementano l'interfaccia ActionexEcutorInterface:

package com.business;
import com.actions.ActionExecutorInterface;
import com.data.DataStorage;
public class DataStorageWorkflow {
	public static DataStorage dataStorage;
		public DataStorageWorkflow() {
		super();
	}
	public DataStorage getDataStorage() {
		return dataStorage;
	}
	public Object execute(ActionExecutorInterface action) {
		return action.execute();
	}
}

e diamo l'idea di una possibile classe di prova:

package com.test;
import com.actions.PrintMessageAction;
import com.business.DataStorageWorkflow;
import com.data.DataStorage;
public class Test {
	public Test() {}
	public static void main(String[] args) {
		PrintMessageAction printAction = new PrintMessageAction(); // 1.
		DataStorageWorkflow.dataStorage = new DataStorage(); // 2.
		DataStorageWorkflow.dataStorage.setMessage("messaggio!"); // 3.
		DataStorageWorkflow dsw = new DataStorageWorkflow(); // 4.
		dsw.execute(printAction); // 5.
	}
}

la console di eclipse

la console di eclipse

Facciamo pertanto un riepilogo, partendo dal main:

  1. viene creata la classe action che esegue l’attività di stampa
  2. viene istanziato attraverso il riferimento statico di classe il dataStorage nella classe
    DataStorageWorkflow
  3. viene inserito un messaggio del dataStorage
  4. viene creata l’istanza del dataStorage
  5. il workflow chiede alla action di modificare i dati

Se vogliamo farci una idea complessiva della struttura delle classi, prima di procedere oltre, questo è il diagramma UML generato facendo un po' di reverse engineering:

uml: schema classi con reverse engineering

uml: schema classi con reverse engineering

Completiamo quindi il nostro piccolo progetto con qualche rifattorizzazione finale.

Ora facciamo una cosa interessante, modifichiamo prima di tutto dataStorage in questo modo:

package com.data;
public class DataStorage {
	private String message;
	public DataStorage() {
		super();
	}
	public String getMessage(String owner) {
		System.out.println("DataStorage: get [" + message +"] by " + owner);
		return message;
	}
	public void setMessage(String owner,String message) {
		System.out.println("DataStorage: set [" + message +"] by " + owner);
		this.message = message;
	}
}

E quindi PrintMessageAction:

il codice di PrintMessageAction

il codice di PrintMessageAction

e la relativa classe di test:

test per PrintMessageAction

test per PrintMessageAction

Qualsiasi action che utilizzerà la variabile message implicitamente utilizzerà il log. Oltretutto si può anche pensare di applicare il tracing in modo da valutare il funzionamento del codice attraverso il cambiamento di stato delle variabili in maniera molto più marcata rispetto all'approccio tradizionale.

Possiamo inoltre immaginare una serie di soluzioni potenzialmente applicabili alla nostra classe
dataContainer: avendo cioè centralizzato l'utilizzo dei dati possiamo applicare su questi una serie di aspetti, che saranno condivisi da tutte le classi che impiegheranno quella variabile.

Si può anche pensare, per gestire diversi aspetti dal Log alle transazioni, di applicare il Chain Of Responsibility Pattern, prevedendo anche un filtro da parte del chiamante che specifica quali aspetti in particolare siano da applicare.

Ancora è da considerare l'utilizzo della sincronizzazione per il Thread-Safe, o evitare l'uso della parola
static in ambiente MultiThreaded, ma questo non costituisce un problema quanto una buona regola di scrittura del codice.

Si può infine anche generalizzare il prelievo e la memorizzazione di una variabile nel Datacontainer, ma questo lo lascio ai lettori come esercizio. Allo stesso modo l'impiego del pattern Chain Of Responsibility Pattern per spostare la logica che riguarda il log in una classe "aspect" separata.

Ti consigliamo anche