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.
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.
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.
Elementklassengruppe
class ItemA
{
String getAnimalName() {
return "cat";
}
}
class ItemB
{
String getColorName() {
return "purple";
}
}
class ItemC
{
}
class ItemD
{
}
class ItemE
{
}
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() + "."
);
}
}
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
// 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
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());
}
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.
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.
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);
}
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 |
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)
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).