Forums Neueste Beiträge
 

EnumVisitor

08/12/2014 - 20:02 von Heiner Kücker | Report spam
EnumVisitor



Hallo zusammen,

Entschuldigt, dass ich mich im BigDecimal-Thread nicht gemeldet hatte,
da hàtte ich mich erst mal einarbeiten müssen.

Nun zum eigentlichen Problem:

In meinem aktuellen Projekt gibt es if-else-Kaskaden
auf Enums:

if ( x == MyEnum.A ) {
...
}
else if ( x == MyEnum.B ) {
...
}
else if ( x == MyEnum.C ) {
...
}

Klar, wenn ein neuer Enum-Wert hinzukommt, merkt das keiner.
Es wird vergessen, die Codestelle nachzuarbeiten und Fehler.

Alternativ kann man ein switch verwenden:

switch ( x ) {
case A:
...
break;
case B:
...
break;
case C:
...
break;
// kein default-Zweig
}

Ok, hier gibt es eine Warnung von Eclipse,
wenn nicht alle Enum-Werte aufgeführt wurden.

Ist aber ein schwacher Schutz, geht in vielen Warnungen unter.

Meine alternative Idee ist ein Visitor.

Das Problem beim Enum ist, dass alle Werte des Enum die gleiche
Klasse haben, für einen Visitor benötigt man unterschiedliche
Klassen.

Man kann zwar durch

enum X {
A { ... } ,
}

jeder Enum-Auspràgung eine eigene Klasse geben,
die ist aber anonym, der Visitor bekommt die nicht
in die Hand.

Ich hatte nun folgende Idee:

Interface -

package de.heinerkuecker.enum_visitor;

public interface Visitable<VISITOR>
{
void visit(
final VISITOR visitor);
}


package de.heinerkuecker.enum_visitor;

abstract public class EnumVisitor3<E extends Enum<E>>
{
abstract public void accept0( E visitableEnum );
abstract public void accept1( E visitableEnum );
abstract public void accept2( E visitableEnum );
}

/*
Es gibt also drei verschiedene Methoden für
ein Enum mit drei Werten.
Bei einer anderen Anzahl Werte müsste eine
entsprechende Visitor-Klasse angelegt werden.
*/


package de.heinerkuecker.enum_visitor;

public enum VisitableEnum
implements Visitable<EnumVisitor3<VisitableEnum>>
{
A
{
@Override
public void visit(
final EnumVisitor3<VisitableEnum> visitor)
{
visitor.accept0( this );
}
},
B
{
@Override
public void visit(
final EnumVisitor3<VisitableEnum> visitor)
{
visitor.accept1( this );
}
},
C
{
@Override
public void visit(
final EnumVisitor3<VisitableEnum> visitor)
{
visitor.accept2( this );
}
};

}

/*
Jeder Enum-Wert hat eine eigene anonyme Klasse
und ruft eine andere Visitor-Methode auf.
*/


package de.heinerkuecker.enum_visitor;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;

import junit.framework.Assert;

import org.junit.Test;

/**
* JUnit4-Testcase für die Klasse {@link VisitableEnum}.
*
* @author Heiner K&uuml;cker
*/
public class VisitableEnumTest
{
@Test
public void test()
{
final EnumSet<VisitableEnum> visitableEnumSet EnumSet.allOf( VisitableEnum.class );

System.out.println( visitableEnumSet );

final List<String> result = new ArrayList<String>();

final EnumVisitor3<VisitableEnum> visitor new EnumVisitor3<VisitableEnum>()
{
@Override
public void accept0(
final VisitableEnum visitableEnum )
{
result.add( visitableEnum.toString() );
}

@Override
public void accept1(
final VisitableEnum visitableEnum)
{
result.add( visitableEnum.toString() );
}

@Override
public void accept2(
final VisitableEnum visitableEnum)
{
result.add( visitableEnum.toString() );
}
};

for ( final VisitableEnum visitableEnum : visitableEnumSet )
{
visitableEnum.visit( visitor );
}

System.out.println( result );

Assert.assertEquals(
//expected
Arrays.asList( "A" , "B" , "C" ),
//actual
result );
}

}

-

Was meint Ihr dazu?
Kann man die Idee so verwenden?
Kann man was verbessern/anders machen?

Danke
Heiner
 

Lesen sie die antworten

#1 Patrick Roemer
08/12/2014 - 23:01 | Warnen spam
Responding to Heiner Kücker:
Alternativ kann man ein switch verwenden:

switch ( x ) {
case A:
...
break;
case B:
...
break;
case C:
...
break;
// kein default-Zweig
}

Ok, hier gibt es eine Warnung von Eclipse,
wenn nicht alle Enum-Werte aufgeführt wurden.

Ist aber ein schwacher Schutz, geht in vielen Warnungen unter.



Eigentlich sollte man sich einmalig fuer jede "Compilerbeschwerde"
einigen: "Fast immer Quatsch, ignore", "Hat ja eigentlich recht,
warning" oder "Immer boese, error". Dann IDE und build entsprechend
konfigurieren und zusehen, dass man gar keine Warnings mehr in der code
base hat - idealerweise, in dem man sie tatsaechlich loswird, wenn's gar
nicht anders geht, mittels (bei Bedarf sinnvoll kommentierter) @Ignores.
Dann faellt sowas durchaus auf.

Und man kann Eclipse (und andere IDEs/Buildtools sicher auch) durchaus
so konfigurieren, dass das einen Error statt eines Warnings gibt -
wahlweise sogar bei Verwendung einer default-Klausel. Natuerlich bei
Bedarf auch pro Projekt.

public interface Visitable<VISITOR>
{
void visit(
final VISITOR visitor);
}



Warum drehst Du denn visit/accept um? Ueblicherweise heisst es
Visitor#visit() und Visitable#accept().

Was meint Ihr dazu?



Eigentlich ist das im klassischen Visitor-Pattern schon so angelegt. Der
relevante doppelte Dispatch findet ueber das polymorphe
Visitable#accept() und ueber die polymorphe Visitormethode, die eben
konkret im #accept() aufgerufen wird, statt. Es hat sich zwar
eingebuergert, den "Rueckweg" ueber ein ueberladenes Visitor#visit() zu
machen, aber fuer den Kern des Patterns ist das irrelevant. Zumindest in
meiner Ausgabe des GoF werden im Beispiel #visitAssignment(Assignment),
#visitVariableRef(VariableRef), etc. verwendet. Da gibt es zwar die
Subklassen, aber fuer den korrekten Dispatch wird ihre Kenntnis im
Visitor offenkundig nicht benoetigt.

Kann man die Idee so verwenden?
Kann man was verbessern/anders machen?



Nett, aber fuer mich irgendwie eine etwas barocke Loesung ohne Problem.

Viele Gruesse,
Patrick

Ähnliche fragen