Home > java, programmazione > Tell, don’t Ask

Tell, don’t Ask

3 ottobre 2013

Translate in English with Google Translate

Il titolo di questo articolo prende spunto da un principio di programmazione che ho letto recentemente in un articolo “The Art of Enbugging” scritto da Andrew Hunt e David Thomas, gli autori di “The Pragmatic Programmers”.
In questo articolo gli autori fanno delle interessanti considerazioni in merito ai principi di programmazione, osservando che la programmazione è sempre più orientata ad un approccio procedurare piuttosto che ad un approccio funzionale, partendo da una considerazione di Alec Sharp, autore di “Smalltalk by Example”:

Procedural code gets information then makes decisions. Object-oriented code tells objects to do things. (Alec Sharp)


Secondo un approccio procedurale, il codice esegue una serie di operazioni in sequenza, ricava delle informazioni (ASK), le elabora ed infine prende delle decisioni sulla base di alcune condizioni.

Logica + Dati
Mentre secondo un approccio funzionale, il codice richiede l’esecuzione di una determinata funzionalità (TELL) che al termine, come frutto dell’elaborazione, ritorna il risultato.

Oggetto

Tendenza alla separazione tra logica e dati
Si osserva sempre più spesso una separazione del comportamento e dello stato in oggetti distinti. In fondo questa separazione parte dall’hardware fino ad arrivare alle architetture software:

  • Le unità fondamentali di un computer sono la CPU e la RAM, la CPU svolge il ruolo di elaborazione mentre la RAM detiene i dati.
  • In una moderna architettura 3-tier abbiamo, oltre al front-end, l’application server che esegue la logica mentre il database funge da datasource.
  • In un modello MVC, oltre alla View, il Controller gestisce le invocazioni ed il Model esegue le logiche di business su JavaBean che incapsulano i dati.
  • In una archiettura enterprise la logica viene incapsulata in EJB Stateless o Statefull mentre i dati vengono detenuti da EJB Entity oppure strati ORM tipo oggetti JPA o framework Hibernate.
  • In una architettura basata su servizi, spesso si assiste alla presenza di un BPM che incapsula la logica ed un ESB che media il recupero dei dati.
  • Nei pattern GoF troviamo questa separazione di competenza, basta guardare il Pattern Visitor che prevede una netta separazione di competenza: i dati sono mantenuti dagli Element mentre la logica è detenuta dai Visitor.

Nella programmazione ad oggetti tali funzionalità sono definite in sede di creazione delle classi le quali detengono le proprietà ed i  metodi, pertanto gli oggetti possono detenere sia lo stato che il comportamento.

Quindi partendo da un orientamento ad oggetti il dato ed il comportamento sono fortemente legati, anzi il dato è incapsulato ed accessibile tramite dei metodi “intelligenti”, ossia dei metodi che non dovrebbero restituire il dato grezzo, come fanno invece i JavaBean, ma dovrebbero restituire il dato elaborato, ossia restituire l’informazione!
Secondo gli autori l’approccio comune è quello di recuperare il dato e poi prendere delle decisioni: questo è ciò che gli autori chiamano “Ask”, ossia chiedere il dato.
L’approccio dovrebbe essere diverso, ossia invocare una funzionalità che ritorni il frutto della elaborazione: quello che gli autori chiamano “Tell”, ossia chiedere l’esecuzione di una funzionalità che ritorni l’informazione desiderata.
Questo comporta che la logica non dovrebbe essere separata dai dati, ma dovrebbe esserne parte integrante.
La programmazione ad oggetti parte proprio da questa base: secondo il principio dell’incapsulamento il dato viene mascherato e viene esposta una funzionalità che dovrebbe ritornare l’informazione ossia il dato grezzo frutto di una elaborazione.

I casi di elaborazione dei dati solitamente si riscontrano nel caso di trascodifiche utenti, per esempio a fronte di un valore booleano si decide di tornare Si/No oppure Maschio/Femmina ed altri casi più complessi.

Implementazione
Immaginiamo di dover implementare una funzionalità: il lancio di un dado. Stabiliamo una condizione di vincita molto semplice: se il numero è superiore a 4, allora ho vinto!

Approccio Procedurale
Creo prima una implementazione creando 2 classi separate, una classe di logica ed una classe di dati:
Procedurale

La classe Client è l’invoker che richiama la classe che gestisce la logica dell’operazione.

package patterns.imperativo;

public class Client {

    public static void main(String[] args) {
        new LogicaEstrattore().lancioDado();
    }
}

La classe LogicaEstrattore detiene la logica dell’estrazione, recupera lo stato e prende la decisione!

package patterns.imperativo;

public class LogicaEstrattore {

    public void lancioDado() {
        //recupero dato: "ASK"
        double datoEstratto = new DatoEstrattore().getDatoEstratto();

        //Logica di funzionamento della vincita
        if (datoEstratto > 4) {
            System.out.println("Estratto: " + datoEstratto + " Perfetto, hai vinto!");
        } else {
            System.out.println("Estratto: " + datoEstratto + " Peccato, hai perso!");
        }
    }
}

La classe DatoEstrattore detiene lo stato dell’estrazione.

package patterns.imperativo;

import java.util.Random;

public class DatoEstrattore {

    private int datoEstratto;

    public DatoEstrattore(){
        datoEstratto = (new Random()).nextInt(6)+1;
    }

    public int getDatoEstratto() {
        return datoEstratto;
    }

    public void setDatoEstratto(int datoEstratto) {
        this.datoEstratto = datoEstratto;
    }

}

Tell don’t Ask
Adesso implemento la stessa funzionalità ma utilizzando un unico oggetto che incapsuli il dato e la logica:

TellDontAsk

La classe Client funge da invoker e richiede direttamente la funzionalità alla classe Estrattore che ritorna l’esito.

package patterns.tellDontAsk;

public class Client {

    public static void main(String[] args) {
        new Estrattore().lancioDado();
    }
}

La classe Estrattore detiene lo stato ed il comportamento, tramite il metodo lancioDado() sarà possibile sapere l’esito.

package patterns.tellDontAsk;

import java.util.Random;

public class Estrattore {

    private int datoEstratto;

    //"TELL"
    public void lancioDado() {
        //Lancio del dado
        datoEstratto = (new Random()).nextInt(6)+1;

        //Logica di funzionamento della vincita
        if (datoEstratto > 4) {
            System.out.println("Estratto: " + datoEstratto + " Perfetto, hai vinto!");
        } else {
            System.out.println("Estratto: " + datoEstratto + " Peccato, hai perso!");
        }
    }
}

In un articolo Martin Fowler mostra i suoi codivisibili dubbi su questo approccio “co-locate”, sicuramente da approfondire.

Categorie:java, programmazione
%d blogger cliccano Mi Piace per questo: