Come implementare una catena dinamica di responsabilità in Java?


Ho una serie di requisiti per una convalida della logica di business:

  1. Ogni fase indipendente della convalida deve essere separata;
  2. L'ordine di questi passaggi può essere definito da un amministratore;
  3. I passaggi possono essere disabilitati.
  4. Ogni passaggio di convalida non è definito dall'utente, ovvero il codice è compilato.

Così ho pensato di implementare una catena dinamica di responsabilità, che avrebbe caricato l'ordine dei passaggi e i nomi di classe da una tabella, istanziarli con Class.forName(). Ma non mi piace molto memorizzare il nome di classe in una tabella in quanto ciò potrebbe portare a potenziali problemi (il refactoring del nome di un validatore, ad esempio, interromperebbe semplicemente il codice). Questo è quello che faccio:

Inserisci qui la descrizione dell'immagine

Naturalmente, più una soluzione deve essere flessibile, più sarà complessa. Tuttavia, mi piacerebbe sapere se c'è un modo per garantire i requisiti di cui sopra senza memorizzare i nomi delle classi in una tabella?

Author: Peter Mortensen, 2015-05-14

1 answers

Non è necessario reinventare la ruota. Puoi usare Commons Chain da Apache come punto di partenza e mescolare con la tua soluzione personalizzata. Fornisce un modo elegante e semplice per risolvere il tuo problema. Puoi anche salvare la tua configurazione in un file XML (Catalogo) e caricarlo dal tuo DB se lo desideri.

Ecco un esempio:

Per vedere come funziona Commons Chain, iniziamo con un esempio un po ' artificioso: il processo aziendale impiegato dai fornitori di veicoli usati (alias, usato venditori di auto). Ecco i passaggi che compongono il processo di vendita:

Get customer information
Test-drive vehicle
Negotiate sale
Arrange financing
Close sale

Supponiamo ora di voler modellare questo flusso usando il modello del metodo Template. È possibile creare una classe astratta-definendo l'algoritmo-che assomiglia a questo:

    public abstract class SellVehicleTemplate {
        public void sellVehicle() {
            getCustomerInfo();
            testDriveVehicle();
            negotiateSale();
            arrangeFinancing();
            closeSale();
        }

    public abstract void getCustomerInfo();
    public abstract void testDriveVehicle();
    public abstract void negotiateSale();
    public abstract void arrangeFinancing();
    public abstract void closeSale();   
}

Ora vediamo come è possibile implementare questo processo utilizzando Commons Chain. Innanzitutto, scarica Commons Chain. È possibile afferrare l'ultimo download notturno come .zip o .file tar, oppure è possibile acquisire il codice più up-to - date da controllo del modulo Commons Chain dai repository di origine CVS o SubVersion. Estrai l'archivio, posizionando la commons-chain.file jar sul tuo classpath.

Per implementare il processo aziendale utilizzando Commons Chain, implementare ogni fase del processo come una classe che ha un singolo metodo pubblico "do it all" chiamato execute(). Questo è un uso tradizionale del modello di comando. Ecco una semplice implementazione del passo "Ottieni informazioni sui clienti".

package com.jadecove.chain.sample;

import org.apache.commons.chain.Command;
import org.apache.commons.chain.Context;

public class GetCustomerInfo implements Command {
    public boolean execute(Context ctx) throws Exception {
        System.out.println("Get customer info");
        ctx.put("customerName","George Burdell");
        return false;
    }
}

A titolo illustrativo scopi, questa classe non fa molto. Tuttavia, memorizza il nome del cliente nel Contesto. L'oggetto Contesto fornisce la colla tra i comandi. Per il momento, pensa al Contesto come nient'altro che una tabella hash in cui puoi inserire valori e estrarre valori da, per chiave. Tutti i comandi successivi possono ora accedere a questi dati. Le classi di comando TestDriveVehicle, NegotiateSale e ArrangeFinancing sono semplici implementazioni che semplicemente stampano ciò che il comando fare.

package com.jadecove.chain.sample;

import org.apache.commons.chain.Command;
import org.apache.commons.chain.Context;

public class TestDriveVehicle implements Command {
    public boolean execute(Context ctx) throws Exception {
        System.out.println("Test drive the vehicle");
        return false;
    }
}

public class NegotiateSale implements Command {
    public boolean execute(Context ctx) throws Exception {
        System.out.println("Negotiate sale");
        return false;
    }
}

public class ArrangeFinancing implements Command {
    public boolean execute(Context ctx) throws Exception {
        System.out.println("Arrange financing");
        return false;
    }
}

L'implementazione CloseSale utilizza il contesto per estrarre il nome del cliente, impostato nel comando GetCustomerInfo.

package com.jadecove.chain.sample;

import org.apache.commons.chain.Command;
import org.apache.commons.chain.Context;

public class CloseSale implements Command {
    public boolean execute(Context ctx) throws Exception {
        System.out.println("Congratulations "
                  +ctx.get("customerName")
            +", you bought a new car!");
        return false;
    }
}

Ora è possibile definire il processo come una sequenza o "catena di comandi."

package com.jadecove.chain.sample;

import org.apache.commons.chain.impl.ChainBase;
import org.apache.commons.chain.Command;
import org.apache.commons.chain.Context;
import org.apache.commons.chain.impl.ContextBase;

public class SellVehicleChain extends ChainBase {
    public SellVehicleChain() {
        super();
        addCommand(new GetCustomerInfo());
        addCommand(new TestDriveVehicle());
        addCommand(new NegotiateSale());
        addCommand(new ArrangeFinancing());
        addCommand(new CloseSale());
    }
    public static void main(String[] args) throws Exception {
        Command process = new SellVehicleChain();
        Context ctx = new ContextBase();
        process.execute(ctx);
    }
}

Questo esempio mostra come è possibile utilizzare l'API Commons Chain per creare ed eseguire una sequenza di comandi. Naturalmente, come quasi ogni nuovo pezzo di software scritto in Java in questi giorni, Commons catena può essere configurato tramite un file XML. Applicazione di questa funzionalità al processo "vendi veicolo", è ora possibile definire la sequenza di comandi in un file XML. Il nome canonico per questo file è chain-config.XML.

<catalog>
  <chain name="sell-vehicle">
    <command   id="GetCustomerInfo"
        className="com.jadecove.chain.sample.GetCustomerInfo"/>
    <command   id="TestDriveVehicle"
        className="com.jadecove.chain.sample.TestDriveVehicle"/>
    <command   id="NegotiateSale"
        className="com.jadecove.chain.sample.NegotiateSale"/>
    <command   id="ArrangeFinancing"
        className="com.jadecove.chain.sample.ArrangeFinancing"/>
    <command   id="CloseSale"
        className="com.jadecove.chain.sample.CloseSale"/>
  </chain>
</catalog>

Il file di configurazione della catena può contenere più definizioni di catena raggruppate in cataloghi. In questo esempio, la definizione della catena viene definita all'interno del catalogo predefinito. È possibile, infatti, avere più cataloghi con nome all'interno di questo file, ognuno con il proprio set di catene.

Ora, invece di definire la sequenza di comandi come è stato fatto in SellVehicleChain, si carica il catalogo e si recupera la catena denominata utilizzando le classi fornite da Commons Chain.

package com.jadecove.chain.sample;

import org.apache.commons.chain.Catalog;
import org.apache.commons.chain.Command;
import org.apache.commons.chain.Context;
import org.apache.commons.chain.config.ConfigParser;
import org.apache.commons.chain.impl.CatalogFactoryBase;

public class CatalogLoader {
    private static final String CONFIG_FILE = 
        "/com/jadecove/chain/sample/chain-config.xml";
    private ConfigParser parser;
    private Catalog catalog;

    public CatalogLoader() {
        parser = new ConfigParser();
    }
    public Catalog getCatalog() throws Exception {
        if (catalog == null) {

    parser.parse(this.getClass().getResource(CONFIG_FILE));     

        }
        catalog = CatalogFactoryBase.getInstance().getCatalog();
        return catalog;
    }
    public static void main(String[] args) throws Exception {
        CatalogLoader loader = new CatalogLoader();
        Catalog sampleCatalog = loader.getCatalog();
        Command command = sampleCatalog.getCommand("sell-vehicle");
        Context ctx = new SellVehicleContext();
        command.execute(ctx);
    }
}

Chain utilizza il Digestore Commons per leggere e analizzare il file di configurazione. Per utilizzare questa funzionalità, è necessario aggiungere il Digestore Commons .file jar al tuo classpath. Ho usato la versione 1.6 e non ho avuto problemi. Digestore dipende dalle collezioni Commons (ho usato la versione 3.1), Commons Logging (versione 1.0.4) e Commons BeanUtils 1.7.0. Si sarà necessario aggiungere questi .vasi al tuo classpath, pure. Dopo aver aggiunto questi .file jar sul mio classpath, il CatalogLoader è stato compilato ed eseguito correttamente. L'output è esattamente come quello generato dagli altri due test.

Fonte: http://www.onjava.com/pub/a/onjava/2005/03/02/commonchains.html

 5
Author: Icaro Camelo, 2015-05-14 14:18:00