[JAVA] Dispatcher Pattern (un nouveau modèle de conception qui résout le problème d'expression du modèle de visiteur)

préface

Début mai 2018, j'ai enfin compris le Visitor Pattern, considéré comme le design pattern le plus difficile. Le modèle de visiteur que je comprends est le suivant.

Un modèle utilisé lorsque vous voulez changer de traitement pour une collection contenant différents éléments sans branchement avec une instruction if pour chaque élément.

Problème d'expression

Dans l'article lié ci-dessous, j'ai appris que le modèle de visiteur a un problème d'expression. Réexamen du modèle de visiteur

Je comprends que les principaux points du problème d'expression sont les suivants.

Lorsqu'un élément est ajouté, la classe existante doit être réécrite pour ajouter un traitement pour l'élément ajouté.

Après avoir réfléchi un instant, "Ce serait formidable si nous pouvions concevoir un modèle de visiteur qui nous permette d'ajouter des éléments et des processus arbitrairement sans réécrire la classe existante", j'ai trouvé un design qui semble bon. C'est le modèle Dispatcher présenté dans cet article.

Conception de modèle de répartiteur

Le protagoniste du modèle Dispatcher est la classe Dispatcher. Toutes les classes de traitement implémentent l'interface Expression et sont déléguées à la classe Dispatcher. La classe Dispatcher utilise le «polymorphisme par délégation (décrit plus loin)» pour distribuer le traitement de tous les éléments transmis par le client.

Classes et interfaces

Classe d'article

Groupe de classes d'élément


class ItemA
{
    String getAnimalName() {
        return "cat";
    }
}

class ItemB
{
    String getColorName() {
        return "purple";
    }
}

class ItemC
{

}

class ItemD
{

}

class ItemE
{

}

Classe de traitement (classe d'expression)

Groupe de classes de traitement


interface Expression<T>
{

}

interface Exp<T> extends Expression<T>
{
    void print(T item);
}

class ExpA implements Exp<ItemA>
{
    @Override
    void print(ItemA item) {
        Log.d(
                "DEMO",
                "I am ExpressionA, treat ItemA only. Animal name is " + item.getAnimalName()
        );
    }
}

class ExpB implements Exp<ItemB>
{
    @Override
    void print(ItemB item) {
        Log.d(
                "DEMO",
                "I am ExpressionB, treat ItemB only. Color name is " + item.getColorName()
        );
    }
}

class ExpX<T> implements Exp<T>
{
    @Override
    void print(T item) {
        Log.d(
                "DEMO",
                "I am ExpressionX, treated " + item.getClass().getSimpleName() + "."
        );
    }
}

Classe Dispatcher

Groupe de classe de tri


abstract class AbstractDispatcher<X extends Expression>
{
    private Map<Class<?>, X> expressions = new HashMap<>();

    @SuppressWarnings("unchecked")
    public <T> void set(Class<T> clazz, Expression<T> expression) {
        expressions.put(clazz, (X) expression);
    }

    @Nullable
    protected final X dispatch(@NonNull Object item) {
        return expressions.get(item.getClass());
    }
}

class Dispatcher<I, X extends Exp<I>>
        extends AbstractDispatcher<X>
        implements Exp<I>
{
    @Override
    void print(I item) {
        X exp = dispatch(item);
        if (exp == null) {
            Log.d(
                "DEMO",
                "Unknown item: " + item.getClass().getSimpleName()
            );

            return;
        }

        exp.print(item);
    }
}

Exemple d'implémentation et journal

Exemple d'implémentation


// setup expressions.
Dispatcher<Object, Exp<Object>> dispatcher = new Dispatcher<>();
{
    dispatcher.set(ItemA.class, new ExpA());
    dispatcher.set(ItemB.class, new ExpB());
    dispatcher.set(ItemC.class, new ExpX<>());
    dispatcher.set(ItemD.class, new ExpX<>());

    // dispatcher.set(ItemA.class, new ExpB());     // error
}

// setup elements.
List<Object> list = new ArrayList<>();
{
    list.add(new ItemB());
    list.add(new ItemB());
    list.add(new ItemC());
    list.add(new ItemA());
    list.add(new ItemB());
    list.add(new ItemC());
    list.add(new ItemA());
    list.add(new ItemD());
    list.add(new ItemE());
}

// execute.
for (Object it : list) {
    dispatcher.print(it);
}

Journal


I am ExpressionB, treat ItemB only. Color name is purple
I am ExpressionB, treat ItemB only. Color name is purple
I am ExpressionX, treated ItemC.
I am ExpressionA, treat ItemA only. Animal name is cat
I am ExpressionB, treat ItemB only. Color name is purple
I am ExpressionX, treated ItemC.
I am ExpressionA, treat ItemA only. Animal name is cat
I am ExpressionX, treated ItemD.
Unknown item: ItemE

Exemples supplémentaires d'éléments et de traitement

Vous pouvez éventuellement ajouter un élément et un traitement pour cet élément, comme dans l'exemple ci-dessous. Vous constaterez que vous n'avez pas à réécrire la classe existante.

Classe à ajouter


class ItemF
{

}

class ExpF implements Exp<ItemF>
{
    @Override
    void print(ItemF item) {
        Log.d(
                "DEMO",
                "I am new ExpressionF, treat ItemF only."
        );
    }
}

Différence à ajouter


// setup expressions.
Dispatcher<Object, Exp<Object>> dispatcher = new Dispatcher<>();
{
    ...
+++    dispatcher.set(ItemF.class, new ExpF());
}

// setup elements.
List<Object> list = new ArrayList<>();
{
    ...
+++    list.add(new ItemF());
}

Caractéristiques du modèle Dispatcher

non-instusive (non-invasice)

Étant donné que la méthode accept utilisée dans le modèle Visitor n'est pas requise, même les classes immuables telles que celles utilisées dans la bibliothèque peuvent être traitées comme des classes d'éléments.

Partage du traitement

Il n'y a pas de relation univoque entre la classe d'élément et la classe de traitement, et il est possible pour une classe de traitement de gérer plusieurs éléments. Même dans ce cas, il n'est pas nécessaire de réécrire la classe existante. Voir ExpX dans l'exemple d'implémentation ci-dessus. Si les classes d'éléments dont ExpX est en charge héritent d'une superclasse commune, le traitement peut être agrégé dans ExpX.

Polymorphisme par délégation

Dispatcher Pattern utilise Map pour associer des éléments à des processus. Si la clé est définie comme "classe d'élément" et que la valeur est définie comme "instance de la classe de traitement à laquelle l'élément cible est lié" dans la carte, il est possible de déterminer quel traitement doit être effectué pour n'importe quel élément. J'ai essayé d'exprimer cela par "polymorphisme par délégation".

Si vous l'ajoutez simplement à la carte, la "classe d'élément inconnu qui n'est pas liée à la classe de traitement" sera liée à la classe de traitement. Cela entraînera une erreur d'exécution et ne respectera pas la sécurité de type. Dispatcher # set () était un appareil pour résoudre ce problème. Seule la "classe d'élément liée à la classe de traitement" est restreinte afin qu'elle puisse être enregistrée dans Dispatcher en tant que compagnon de la classe de traitement.

Dispatcher#set()


public <T> void set(Class<T> clazz, Expression<T> expression) {
    expressions.put(clazz, (X) expression);
}

Comparaison avec le modèle de visiteur

article Visitor Pattern Dispatcher Pattern
Méthode d'acceptation de la classe d'élément nécessaire Inutile (non-intusive)
Polymorphisme à utiliser Polymorphisme par surcharge Polymorphisme par délégation
envoi double Célibataire

Valeur de retour multiphase

La valeur renvoyée par Dispatcher ne peut pas être polymorphe. Cependant, si vous considérez la valeur de retour comme un nouvel ensemble d'éléments, vous pouvez appliquer le modèle Dispatcher à la valeur de retour.

Puisqu'il sera long d'introduire le code ici, je n'introduirai que le code dans un autre article. Dispatcher Pattern (valeur de retour multiphase)

Vérification future

Je pense que je l'ai résolu pour taper la sécurité. Cependant, du point de vue des héros du programmeur, cela peut finir par "Non, c'est plein de trous". Même si la sécurité de type n'est pas établie, je pense qu'elle aura une plus large gamme d'applications que Visitor Pattern en ce qu'elle peut gérer des éléments arbitraires tels que les classes incluses dans la bibliothèque.

Si ce nouveau modèle est valable pour la sécurité des types, les futurs livres de modèles de conception incluront le modèle Dispatcher au lieu du modèle de visiteur. À ce moment-là, j'aimerais que vous introduisiez le nom de Stew Eucen (rires).

Recommended Posts

Dispatcher Pattern (un nouveau modèle de conception qui résout le problème d'expression du modèle de visiteur)
Modèle de conception ~ Visiteur ~
J'ai essayé de créer un programme en Java qui résout le problème du voyageur de commerce avec un algorithme génétique