Blog2j
 
Cerca con Google
Ricerca personalizzata
 
Factory complesse con spring
In un precedente post abbiamo visto una prima introduzione a spring in cui si utilizza questo framework per implementare una semplice factory. Vediamo adesso come sia possibile utilizzare spring anche per creare factory più complesse, in particolare factory che ritornano una implementazione di una interfaccia in base ai parametri passati a un metodo.
Utilizzando spring nel modo più semplice e naturale ogni bean è un singleton e la classe da istanziare viene decisa quando parte l'applicazione. Tale scelta può essere statica, in genere in base alla classe di definizione del bean, o può essere determinata in base allo stato di altri bean gestiti con spring.
Vediamo un esempio simile a quello del post precedente, l'interfaccia principale è GestoreClassifica, ha un solo metodo che calcola la classifica di un campionato. Questo metodo ha un parametro di input che specifica il tipo di calcolo da usare per generare la classifica:
public interface GestoreClassifica {
    List<PuntiSquadra> calcolaClassifica(TipoCalcoloClassifica tipo);
}
Il tipo di calcolo è una enum che contiene due valori possibili:
public enum TipoCalcoloClassifica {
    VECCHIO_STILE, NUOVO_STILE;
}
Una prima implementazione di GestoreClassifica potrebbe semplicemente effettuare un if per controllare il tipo ed effettuare il calcolo di conseguenza. In un caso semplice come questo potrebbe essere una implementazione accettabile, ma in casi più complessi in cui i parametri in input possono variare nel tempo ed influenzano pesantemente l'algoritmo di calcolo è meglio prendere un'altra strada.
Per esempio possiamo isolare in una interfaccia la parte di codice influenzata dal tipo di calcolo e scrivere una implementazione per ogni tipo di calcolo:
public interface AggiornatoreClassifica {
    void aggiornaPunti(Partita p, PuntiSquadra puntiSquadraCasa,
        PuntiSquadra puntiSquadraFuori);
}
Per decidere quale implementazione usare creiamo una factory con un metodo che, dato il tipo di calcolo in input, ritorna l'implementazione da usare:
public class AggiornatoreClassificaFactory {

    private AggiornatoreClassifica2 aggiornatoreClassifica2;
    private AggiornatoreClassifica3 aggiornatoreClassifica3;

    public AggiornatoreClassifica getAggiornatoreClassifica(TipoCalcoloClassifica tipo) {
        if (tipo == TipoCalcoloClassifica.VECCHIO_STILE) {
            return aggiornatoreClassifica2;
        } else {
            return aggiornatoreClassifica3;
        }
    }

    public void setAggiornatoreClassifica2(AggiornatoreClassifica2
            aggiornatoreClassifica2) {
        this.aggiornatoreClassifica2 = aggiornatoreClassifica2;
    }

    public void setAggiornatoreClassifica3(AggiornatoreClassifica3
            aggiornatoreClassifica3) {
        this.aggiornatoreClassifica3 = aggiornatoreClassifica3;
    }
}
La factory è solitamente l'unica parte del codice che conosce tutte le implementazioni da usare e la logica di scelta, quindi nel caso in cui si debba aggiungere un nuovo tipo di calcolo questa è l'unica classe da modificare. Utilizzando spring solitamente non si scrivono factory manualmente, questo caso secondo me è una eccezione (smentitemi nei commenti se trovate un modo per non scriverla!).
Vediamo adesso l'implementazione di GestoreClassifica che sfrutta questa factory per eseguire il calcolo della classifica e un dao per ottenere una lista di partite:
public class GestoreClassificaSpringImpl implements GestoreClassifica {

    private final PartitaDAO partitaDAO;
    private AggiornatoreClassificaFactory aggiornatoreClassificaFactory;

    public GestoreClassificaSpringImpl(PartitaDAO partitaDAO,
            AggiornatoreClassificaFactory aggiornatoreClassificaFactory) {
        this.partitaDAO = partitaDAO;
        this.aggiornatoreClassificaFactory = aggiornatoreClassificaFactory;
    }

    @Override
    public List<PuntiSquadra> calcolaClassifica(TipoCalcoloClassifica tipo) {
        List<Partita> partite = partitaDAO.select();
        AggiornatoreClassifica aggiornatoreClassifica =
            aggiornatoreClassificaFactory.getAggiornatoreClassifica(tipo);
        Map<String, PuntiSquadra> mapSquadre =
            new HashMap<String, PuntiSquadra>();

        for (Partita p : partite) {
            PuntiSquadra puntiSquadraCasa =
                getPuntiSquadra(mapSquadre, p.getSquadraCasa());
            PuntiSquadra puntiSquadraFuori =
                getPuntiSquadra(mapSquadre, p.getSquadraFuori());

            aggiornatoreClassifica.aggiornaPunti(p, puntiSquadraCasa, puntiSquadraFuori);
        }

        List<PuntiSquadra> classifica =
            new ArrayList<PuntiSquadra>(mapSquadre.values());

        Collections.sort(classifica);

        return classifica;
    }

    private PuntiSquadra getPuntiSquadra(Map<String, PuntiSquadra> mapSquadre,
            String squadra) {
        PuntiSquadra puntiSquadra = mapSquadre.get(squadra);
        if (puntiSquadra == null) {
            puntiSquadra = new PuntiSquadra();
            puntiSquadra.setSquadra(squadra);
            mapSquadre.put(squadra, puntiSquadra);
        }
        return puntiSquadra;
    }
}
Sia la factory che il dao vengono "iniettate" da spring usando il costruttore della classe, il file di configurazione di spring è il seguente:
<bean id="partitaDAO" class="it.blog2j.dao.PartitaDAOImpl" />

<bean id="aggiornatoreClassifica2"
    class="it.blog2j.classifica.AggiornatoreClassifica2" />
<bean id="aggiornatoreClassifica3"
    class="it.blog2j.classifica.AggiornatoreClassifica3" />

<bean id="aggiornatoreClassificaFactory"
    class="it.blog2j.classifica.AggiornatoreClassificaFactory"
    autowire="byName" />

<bean id="gestoreClassifica"
        class="it.blog2j.springFactory.GestoreClassificaSpringImpl">
    <constructor-arg ref="partitaDAO" />
    <constructor-arg ref="aggiornatoreClassificaFactory" />
</bean>
Da notare l'utilizzo dell'attributo autowire all'interno del bean aggiornatoreClassificaFactory, usando il valore byName spring scandisce in automatico i campi della classe che hanno un metodo set e popola tali campi con i bean che hanno id uguale al nome del campo.
Uno degli aspetti migliori di spring è che il disaccoppiamento fra le classi prodotte è molto alto, ciò porta a classi facilmente testabili in isolamento. Nel nostro esempio c'è un legame fra i bean di spring che potrebbe essere evitato, quello fra aggiornatoreClassificaFactory e gestoreClassifica, vediamo come si può fare per eliminarlo. Per adesso scriviamo una nuova implementazione di GestoreClassifica che istanzia un AggiornatoreClassifica in un metodo separato che ritorna null:
public class GestoreClassificaSpringInjectionImpl implements GestoreClassifica {

    private final PartitaDAO partitaDAO;

    public GestoreClassificaSpringInjectionImpl(PartitaDAO partitaDAO) {
        this.partitaDAO = partitaDAO;
    }

    @Override
    public List<PuntiSquadra> calcolaClassifica(TipoCalcoloClassifica tipo) {
        List<Partita> partite = partitaDAO.select();
        AggiornatoreClassifica aggiornatoreClassifica = getAggiornatoreClassifica(tipo);
        ....  
        return classifica;
    }

    protected AggiornatoreClassifica getAggiornatoreClassifica(
            TipoCalcoloClassifica tipo) {
        return null;
    }
...
}
Questa nuova implementazione non ha un legame con la factory, il metodo getAggiornatoreClassifica può essere sostituito runtime da spring usando un oggetto MethodReplacer:
public class AggiornatoreClassificaMethodReplacer implements MethodReplacer {

    private AggiornatoreClassificaFactory aggiornatoreClassificaFactory;

    public AggiornatoreClassificaMethodReplacer(AggiornatoreClassificaFactory
            aggiornatoreClassificaFactory) {
        this.aggiornatoreClassificaFactory = aggiornatoreClassificaFactory;
    }

    public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
        TipoCalcoloClassifica tipo = (TipoCalcoloClassifica) args[0];
        return aggiornatoreClassificaFactory.getAggiornatoreClassifica(tipo);
    }
}
Ovviamente questo nuovo bean introdotto ha un riferimento alla factory ma può essere visto come un bean di utilità e non come un bean che contiene la logica di business. Infine la configurazione dei due bean di spring è la seguente:
<bean id="aggiornatoreClassificaMethodReplacer"
        class="it.blog2j.springInjection.AggiornatoreClassificaMethodReplacer">
    <constructor-arg ref="aggiornatoreClassificaFactory" />
</bean>

<bean id="gestoreClassifica"
        class="it.blog2j.springInjection.GestoreClassificaSpringInjectionImpl">
    <constructor-arg ref="partitaDAO" />
    <replaced-method name="getAggiornatoreClassifica"
            replacer="aggiornatoreClassificaMethodReplacer">
        <arg-type>it.blog2j.TipoCalcoloClassifica</arg-type>
    </replaced-method>
</bean>
La sostituzione del metodo runtime avviene usando un proxy, per fare ciò vengono utilizzate le librerie cglib e asm che devono essere presenti nel classpath dell'applicazione.
 
News