Comment implémenter une chaîne de responsabilité dynamique en Java?


J'ai un ensemble d'exigences sur une validation de logique métier:

  1. Chaque étape indépendante de la validation doit être séparée;
  2. L'ordre de ces étapes peut être défini par un administrateur;
  3. Les étapes peuvent être désactivées.
  4. Chaque étape de validation n'est pas définie par l'utilisateur-c'est - à-dire que le code est compilé.

J'ai donc pensé à implémenter une chaîne de responsabilité dynamique, qui chargerait l'ordre des étapes et les noms de classe à partir d'une table, les instancierait avec Class.forName(). Mais je n'aime pas beaucoup stocker le className dans une table car cela pourrait entraîner des problèmes potentiels (refactoriser le nom d'un validateur, par exemple, casserait simplement le code). Voici ce que je fais:

Entrez la description de l'image ici

Bien sûr, plus une solution doit être flexible, plus elle sera complexe. Pourtant, j'aimerais savoir s'il existe un moyen de garantir les exigences ci-dessus sans stocker les noms de classe dans une table?

Author: Peter Mortensen, 2015-05-14

1 answers

Vous n'avez pas besoin de réinventer la roue. Vous pouvez utiliser Commons Chain d'Apache comme point de départ et mélanger avec votre solution personnalisée. Il fournit un moyen élégant et facile de résoudre votre problème. Vous pouvez également enregistrer votre configuration dans un fichier XML (Catalogue) et la charger à partir de votre base de données si vous le souhaitez.

Voici un exemple:

Pour voir comment fonctionne Commons Chain, commençons par un exemple quelque peu artificiel: le processus métier employé par les fournisseurs de véhicules d'occasion (alias, utilisé voiture vendeurs). Voici les étapes qui composent le processus de vente:

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

Supposons maintenant que vous vouliez modéliser ce flux en utilisant le modèle de méthode de modèle. Vous pouvez créer une classe abstraite defining définissant l'algorithme that qui ressemble à ceci:

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

Voyons maintenant comment vous pouvez implémenter ce processus en utilisant Commons Chain. Tout d'abord, téléchargez Commons Chain. Vous pouvez saisir le dernier téléchargement nocturne en tant que .zip ou .fichier tar, ou vous pouvez acquérir le code le plus à jour en vérification du module Commons Chain à partir des référentiels source CVS ou SubVersion. Extrayez l'archive, en plaçant la chaîne commons.fichier jar sur votre chemin de classe.

Pour implémenter le processus métier à l'aide de Commons Chain, implémentez chaque étape du processus en tant que classe qui a une seule méthode publique "tout faire" nommée execute(). C'est une utilisation traditionnelle du modèle de Commande. Voici une implémentation simple de l'étape" Obtenir des informations client".

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

Pour illustration cette classe ne fait pas beaucoup. Cependant, il stocke le nom du client dans le contexte. L'objet Context fournit la colle entre les commandes. Pour le moment, considérez le contexte comme rien de plus qu'une table de hachage dans laquelle vous pouvez insérer des valeurs et en extraire des valeurs par clé. Toutes les commandes suivantes peuvent désormais accéder à ces données. Les classes de commande TestDriveVehicle, NegotiateSale et ArrangeFinancing sont des implémentations simples qui affichent simplement ce que la commande ferait faire.

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'implémentation CloseSale utilise le contexte pour extraire le nom du client, défini dans la commande 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;
    }
}

Maintenant, vous pouvez définir le processus comme une séquence ou "chaîne de commandes."

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

Cet exemple montre comment vous pouvez utiliser l'API Commons Chain pour créer et exécuter une séquence de commandes. Bien sûr, comme presque tous les nouveaux logiciels écrits en Java de nos jours, Commons Chain peut être configuré via un fichier XML. Application de cette capacité pour vendre le véhicule", vous pouvez maintenant définir la séquence de commandes dans un fichier XML. Le nom canonique de ce fichier est 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>

Le fichier de configuration de chaîne peut contenir plusieurs définitions de chaîne regroupées dans des catalogues. Pour cet exemple, la définition de chaîne est définie dans le catalogue par défaut. Vous pouvez, en fait, avoir plusieurs catalogues nommés dans ce fichier, chacun avec son propre ensemble de chaînes.

Maintenant, au lieu de définir la séquence de commandes comme cela a été fait dans SellVehicleChain, vous chargez le catalogue et récupérez la chaîne nommée en utilisant les classes fournies par 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 utilise le digesteur Commons pour lire et analyser le fichier de configuration. Pour utiliser cette fonctionnalité, vous devrez ajouter le digesteur Commons.fichier jar dans votre chemin de classe. J'ai utilisé la version 1.6 et n'ai eu aucun problème. Digesteur dépend des collections Commons (j'ai utilisé la version 3.1), Commons Logging (version 1.0.4), et Commons BeanUtils 1.7.0. Vous devez ajouter ces .jars à votre classpath, aussi. Après avoir ajouté ces .fichiers jar dans mon classpath, le CatalogLoader a été compilé et exécuté avec succès. La sortie est exactement comme celle générée par les deux autres tests.

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

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