[JAVA] Dispatcher-Muster (ein neues Entwurfsmuster, das das Ausdrucksproblem des Besuchermusters löst)

Vorwort

Anfang Mai 2018 habe ich endlich das Besuchermuster verstanden, das als das schwierigste Entwurfsmuster gilt. Das Besuchermuster, das ich verstehe, ist wie folgt.

Ein Muster, das verwendet wird, wenn Sie die Verarbeitung für eine Sammlung mit verschiedenen Elementen wechseln möchten, ohne mit einer if-Anweisung für jedes Element zu verzweigen.

Ausdrucksproblem

In dem unten verlinkten Artikel habe ich erfahren, dass das Besuchermuster ein Ausdrucksproblem hat. Überprüfung des Besuchermusters

Ich verstehe, dass die Hauptpunkte des Ausdrucksproblems wie folgt sind.

Wenn ein Element hinzugefügt wird, muss die vorhandene Klasse neu geschrieben werden, um die Verarbeitung für das hinzugefügte Element hinzuzufügen.

Nachdem ich einen Moment nachgedacht hatte: "Es wäre großartig, wenn wir ein Besuchermuster entwerfen könnten, mit dem wir Elemente und Prozesse willkürlich hinzufügen können, ohne die vorhandene Klasse neu zu schreiben." Ich fand ein Design, das gut zu sein scheint. Dies ist das in diesem Artikel vorgestellte Dispatcher-Muster.

Dispatcher Pattern Design

Der Protagonist des Dispatcher-Musters ist die Dispatcher-Klasse. Alle Verarbeitungsklassen implementieren die Ausdrucksschnittstelle und werden an die Dispatcher-Klasse delegiert. Die Dispatcher-Klasse verwendet "Polymorphismus durch Delegierung (später beschrieben)", um die Verarbeitung für alle vom Client übergebenen Elemente zu verteilen.

Klassen und Schnittstellen

Artikelklasse

Elementklassengruppe


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

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

class ItemC
{

}

class ItemD
{

}

class ItemE
{

}

Verarbeitungsklasse (Ausdrucksklasse)

Klassengruppe verarbeiten


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

Dispatcher-Klasse

Klassengruppe sortieren


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

Implementierungsbeispiel und Protokoll

Implementierungsbeispiel


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

Log


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

Zusätzliche Beispiele für Elemente und Verarbeitung

Sie können optional ein Element hinzufügen und für dieses Element verarbeiten, wie im folgenden Beispiel. Sie werden feststellen, dass Sie die vorhandene Klasse nicht neu schreiben müssen.

Klasse zum Hinzufügen


class ItemF
{

}

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

Unterschied hinzuzufügen


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

Merkmale des Dispatcher-Musters

non-instusive (non-invasice)

Da die im Besuchermuster verwendete Akzeptanzmethode nicht erforderlich ist, können auch unveränderliche Klassen, wie sie in der Bibliothek verwendet werden, als Elementklassen behandelt werden.

Weitergabe der Verarbeitung

Es gibt keine Eins-zu-Eins-Beziehung zwischen Elementklassen und Verarbeitungsklassen, und es ist möglich, dass eine Verarbeitungsklasse mehrere Elemente verarbeitet. Selbst in diesem Fall muss die vorhandene Klasse nicht neu geschrieben werden. Siehe ExpX in der obigen Beispielimplementierung. Wenn die Elementklassen, für die ExpX zuständig ist, von einer gemeinsamen Oberklasse erben, kann die Verarbeitung in ExpX aggregiert werden.

Polymorphismus durch Delegation

Das Dispatcher-Muster verwendet Map, um Elemente mit Prozessen zu verknüpfen. Wenn der Schlüssel als "Elementklasse" und der Wert als "Instanz der Verarbeitungsklasse, an die das Zielelement gebunden ist" in der Map festgelegt ist, kann festgelegt werden, welche Verarbeitung für ein Element ausgeführt werden soll. Ich habe versucht, dies als "Polymorphismus durch Delegation" auszudrücken.

Wenn Sie es einfach zur Map hinzufügen, wird die "unbekannte Elementklasse, die nicht an die Verarbeitungsklasse gebunden ist" mit der Verarbeitungsklasse verknüpft. Dies führt zu einem Laufzeitfehler und erfüllt nicht die Typensicherheit. Dispatcher # set () war ein Gerät zur Lösung dieses Problems. Nur "an Verarbeitungsklasse gebundene Elementklasse" ist eingeschränkt, damit sie in Dispatcher als Begleiter der Verarbeitungsklasse registriert werden kann.

Dispatcher#set()


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

Vergleich mit Besuchermuster

Artikel Visitor Pattern Dispatcher Pattern
Methode zum Akzeptieren von Elementklassen notwendig Unnötig (nicht-intusive)
Polymorphismus zu verwenden Polymorphismus durch Überlastung Polymorphismus durch Delegation
Versand doppelt Single

Mehrphasen-Rückgabewert

Der von Dispatcher erhaltene Rückgabewert kann nicht polymorph sein. Wenn Sie sich den Rückgabewert jedoch als neuen Satz von Elementen vorstellen, können Sie das Dispatcher-Muster auf den Rückgabewert anwenden.

Da es lange dauern wird, den Code hier einzuführen, werde ich nur den Code in einem anderen Artikel einführen. Dispatcher-Muster (mehrphasiger Rückgabewert)

Zukünftige Überprüfung

Ich glaube, ich habe es gelöst, um Sicherheit zu tippen. Aus der Sicht der Helden des Programmierers kann das Ergebnis jedoch "Nein, es ist voller Löcher" sein. Selbst wenn die Typensicherheit nicht festgelegt ist, wird sie meiner Meinung nach einen breiteren Anwendungsbereich als das Besuchermuster haben, da sie beliebige Elemente wie in der Bibliothek enthaltene Klassen verarbeiten kann.

Wenn dieses neue Muster für die Typensicherheit gilt, enthalten zukünftige Entwurfsmusterbücher das Dispatcher-Muster anstelle des Besuchermusters. Zu diesem Zeitpunkt möchte ich, dass Sie den Namen Stew Eucen vorstellen (lacht).

Recommended Posts

Dispatcher-Muster (ein neues Entwurfsmuster, das das Ausdrucksproblem des Besuchermusters löst)
Entwurfsmuster ~ Besucher ~
Ich habe versucht, ein Programm in Java zu erstellen, das das Problem des Handlungsreisenden mit einem genetischen Algorithmus löst