Switches ohne String

  • Themenstarter Themenstarter Mart
  • Beginndatum Beginndatum
M

Mart

Gast
Ich stehe shcon länger auf dem Schlauch wenn man zb navigiert hat man die Methode

Java:
void gehen(String richtung)
{
    if(richtung.equals("norden"))
        else if(richtung.equals("süden"))
}
das muss doch anders auch gehen ohne jedes mal einen String zu benutzen, bzw. etwas anderes zu benutzen ,ohne jedesmal die switch bzw die If else zu haben

sodass man basierend auf dem Übergabe parameter eine Methode ausführt
 
Ja und Nein. Geht es anders? Ja. Wird es viel simpler? Nein, nicht ganz.

Nehmen wir mal du hast einen Spieler und bewegst diesen in eine der vier Himmelsrichtungen:

Java:
public void gehen(String richtung) {
    if (richtung.equals("norden")) {
        splieler.position.y = splieler.position.y + 1;
    } else if (richtung.equals("sueden")) {
        splieler.position.y = splieler.position.y - 1;
    } else if (richtung.equals("westen")) {
        splieler.position.x = splieler.position.x - 1;
    } else if (richtung.equals("osten")) {
        splieler.position.x = splieler.position.x + 1;
    }
}

Das koennen wir jetzt natuerlich auf ein "switch" umbauen:

Java:
public void gehen(String richtung) {
    switch (richtung) {
        case "norden":
            splieler.position.y = splieler.position.y + 1;
            break;
      
        case "sueden":
            splieler.position.y = splieler.position.y - 1;
            break;
      
        case "westen":
            splieler.position.x = splieler.position.x - 1;
            break;
      
        case "osten":
            splieler.position.x = splieler.position.x + 1;
            break;
    }
}

So...macht die Sache jetzt nicht wirklich viel besser, um ehrlich zu sein. Naechster Schritte muesste eigentlich sein einen Enum zu verwenden.

Java:
public enum Himmelsrichtung {
    NORDEN,
    SUEDEN,
    WESTEN,
    OSTEN;
}

public void gehen(Himmelsrichtung richtung) {
    switch (richtung) {
        case NORDEN:
            splieler.position.y = splieler.position.y + 1;
            break;
      
        case SUEDEN:
            splieler.position.y = splieler.position.y - 1;
            break;
      
        case WESTEN:
            splieler.position.x = splieler.position.x - 1;
            break;
      
        case OSTEN:
            splieler.position.x = splieler.position.x + 1;
            break;
    }
}

Damit haben wir zumindest mal Typen-Sicherheit gewonnen und koennen nicht mehr so einfach Tippfehler mehr machen. Von hier aus wird es aber schwierig, weil die Logik muss irgendwie mit diesen Werten verbunden sein. Wir koennten zum Beispiel dem Enum beibringen den Spieler zu bewegen.

Java:
public enum Himmelsrichtung {
    NORDEN(0, +1),
    SUEDEN(0, -1),
    WESTEN(-1, 0),
    OSTEN(+1, 0);
  
    private int bewegung_x = 0;
    private int bewegung_y = 0;
  
    private Himmelsrichtung(int bewegung_x, int bewegung_y) {
        super();
      
        this.bewegung_x = bewegung_x;
        this.bewegung_y = bewegung_y;
    }
  
    public final void spielerBewegen(Spieler spieler) {
        splieler.position.x = splieler.position.x + bewegung_x;
        splieler.position.y = splieler.position.y + bewegung_y;
    }
}

public void gehen(Himmelsrichtung richtung) {
    richtung.spielerBewegen(spieler)
}

Gewinnen wir dadurch etwas? Nein, eigentlich nicht. Der Enum ist jetzt um einiges komplexer und ist noch dazu verbunden mit dem Spieler.

Wir koennten jetzt noch versuchen das ganze mit mehr Klassen zu loesen, aber ab hier wird es einfach nur komplexer ohne dass wir etwas gewinnen. Das "switch" (mit Enum) ist, meiner Meinung nach, das lesbarste das du in diesem Fall finden wirst. Irgendwo muss die Logik sein welche den Spieler um jeweils ein Feld bewegt, und mir persoenlich ist es lieber wenn dies in einer Funktion passiert, als ueber 2 verschiedene Klassen mit 4 Implementationen aufgeteilt ist.
 
Java:
TextFileOperationExecutor textFileOperationExecutor
 = new TextFileOperationExecutor();
TextFile textFile = new TextFile("file1.txt");
textFileOperationExecutor.executeOperation(textFile::open);
textFileOperationExecutor.executeOperation(textFile::save);
basierend auf dem was im command pattern drin gestanden ist wäre es möglich sowas zu schreiben


Java:
void gehen(Richtung richtung){
    spieler.geh(bewegen::richtung)
}
also dass man die methode die man aufruft übergeben kann und basierend auf den das ausgeführt wird
 
Und jetzt noch ein ueberzuechtetes Beispiel, der Vollstaendigkeit halber:

Java:
public enum Himmelsrichtung {
    NORDEN,
    SUEDEN,
    WESTEN,
    OSTEN;
}

public interface SpielerAenderer {
    public abstract void anwenden(Spieler spieler);
}

public class GeheNachNordenSpielerAenderer implements SpielerAenderer {
    @Override
    public void anwenden(Spieler spieler) {
        spieler.position.y = spieler.position.y + 1
    }
}

public class GeheNachSuedenSpielerAenderer implements SpielerAenderer {
    @Override
    public void anwenden(Spieler spieler) {
        spieler.position.y = spieler.position.y - 1
    }
}

public class GeheNachWestenSpielerAenderer implements SpielerAenderer {
    @Override
    public void anwenden(Spieler spieler) {
        spieler.position.x = spieler.position.x - 1
    }
}

public class GeheNachOstenSpielerAenderer implements SpielerAenderer {
    @Override
    public void anwenden(Spieler spieler) {
        spieler.position.x = spieler.position.x + 1
    }
}

private Map<Himmelsrichtung, SpielerAenderer> bewegungen = new HashMap<>();
bewegungen.put(Himmelsrichtung.NORDEN, new GeheNachNordenSpielerAenderer());
bewegungen.put(Himmelsrichtung.SUEDEN, new GeheNachSuedenSpielerAenderer());
bewegungen.put(Himmelsrichtung.WESTEN, new GeheNachWestenSpielerAenderer());
bewegungen.put(Himmelsrichtung.OSTEN, new GeheNachOstenSpielerAenderer());

public void gehen(Himmelsrichtung richtung) {
    bewegungen.get(richtung).anwenden(spieler);
}

Und wir sehen...wir gewinnen nichts in diesem Fall...ganz im Gegenteil...
 
Irgendwo muss die Logik sein welche den Spieler um jeweils ein Feld bewegt, und mir persoenlich ist es lieber wenn dies in einer Funktion passiert, als ueber 2 verschiedene Klassen mit 4 Implementationen aufgeteilt ist.
Die Logik besteht aktuell ja auch zwei Teilen: nächste Position bestimmen und Spieler dorthin bewegen (und auf Dauer sicherlich noch mehr, zB prüfen ob Bewegung möglich ist).

Das kann man grad mit dem Enum ziemlich sauber trennen: anstatt den Spieler übergibt man die Position und bekommt dann die neue Position zurück. Oder andersrum: die Position bekommt den enum und gibt die neue Position zurück.


Und wir sehen...wir gewinnen nichts in diesme Fall...ganz im Gegenteil...
Sowas würde deshalb auch jeder Refactoren, 4 identische Klassen nutzt dafür doch niemand 😉
 
Sowas würde deshalb auch jeder Refactoren, 4 identische Klassen nutzt dafür doch niemand 😉
Wie meinst du das?

es geht im Moment eher um das Prinzip, wie man so eine Problematik anpacken sollte und nicht speziell um die Bewegung ,da diese als Beispiel gedacht war


in c# kann man das zb tun in dem Man in einem hash array eine Richtung und eine Function abspeichert und je nachdem welcher parameter gekommen ist die methode ausführt

nur ich weis nicht ob das der richtige weg ist methoden so abzurufen bzw wie man das in java machen sollte
 
Das kann man grad mit dem Enum ziemlich sauber trennen: anstatt den Spieler übergibt man die Position und bekommt dann die neue Position zurück. Oder andersrum: die Position bekommt den enum und gibt die neue Position zurück.
Du meinst sowas?

Java:
public enum Himmelsrichtung {
    NORDEN(0, +1),
    SUEDEN(0, -1),
    WESTEN(-1, 0),
    OSTEN(+1, 0);
  
    private int richtung_x = 0;
    private int richtung_y = 0;
  
    private Himmelsrichtung(int richtung_x, int richtung_y) {
        super();
      
        this.richtung_x = richtung_x;
        this.richtung_y = richtung_y;
    }
  
    public final void anwenden(Position position) {
        position.x = position.x + richtung_x;
        position.y = position.y + richtung_y;
    }
}

public void gehen(Himmelsrichtung richtung) {
    richtung.anwenden(spieler.position)
}

Mh, da koennte man vielleicht darueber reden, wirkt aber trotzdem immer noch nicht nach der wirklich guten Loesung.

es geht im Moment eher um das Prinzip, wie man so eine Problematik anpacken sollte und nicht speziell um die Bewegung ,da diese als Beispiel gedacht war
Allgemein kann man die Anforderung aber nicht beantworten, weil je nach dem auf was das "if" wirkt und welche Operationen dann passieren sollen, wird man etwas anderes brauchen. Das kann jetzt von meinem "switch" bis zu einer Plugin-Struktur alles sein.
 
Du könntest auch zu je Aktion ein Klasse, welche von Runable abgeleitet ist, in eine Map packen. Du kannst dann je nach Aktion das entsprechende Runable aus der Map holen und die Methode run aufrufen. Dabei sollte auch eine Default-Implemetierung erstellt werden, falls mal eine unbekannte Aktion aufgerufen wird. Das erspart die Verzweigung über if. Ob sich das jetzt für 4 Himmelsrichtungen lohnt sei dahingestellt.
 
in c# kann man das zb tun in dem Man in einem hash array eine Richtung und eine Function abspeichert
Das geht in Java doch ebenso ... wenn Du es also so machen willst, dann mach es 🙂

An Wie die Methode aussehen soll kannst Du über ein functional Interface festlegen oder du nutzt eines, das es schon gibt.
 
es geht im Moment eher um das Prinzip, wie man so eine Problematik anpacken sollte und nicht speziell um die Bewegung ,da diese als Beispiel gedacht war
Grundsätzlich hast du zwei verschiedene Möglichkeitem: den direkten Weg (mit if/switch/Map/...) oder Command Pattern. Letzteres ist oft „schöner“ und uU später einfacher zu nutzen, wenn auch initial etwas mehr Aufwand.

In beiden Umsetzungen kannst du dir dann überlegen, wie du das konkret geforderte umsetzt: trennt man Aktion und das Drumherum (hierbei Bewegen und nächste Position bestimmen) und wenn ja, wer hat die Verantwortlichkeiten für die einzelnen Dinge. Vor- und Nachteile kann man in fast jedem finden, die muss man dann für den konkreten Fall immer abwägen, und dafür kennt man am besten möglichst viele konkrete Anforderungen 🙂


Meine Präferenz wäre in diesem Fall Command Pattern, und Positionsberechnung rausgezogen in die Position und einen Enum für die Richtung - Möglichkeiten gibt es aber viele.
 
Ok gut danke an alle

das muss ich mal alles in Ruhe durch den Kopf gehen lassen bis ich die ganzen sachen angesehen hab
 
Unter dem Strich kommt alles auf das Gleiche hinaus:

Du hast Methoden für jede Aktion. Diese sind - je nach Umfang/Komplexität sinnvoller Weise Klassen, aber das ist egal.

Und nun musst Du diese aufrufen. Dies kannst Du viele unterschiedliche Wege machen.
- Du machst ein switch / case und rufst es dann auf.
- Du holst es aus einer Map und rufst es auf.
- Du hast bereits ein Enum und rufst da einfach eine Methode auf (NORDEN.execute(whatever))
- Oder das Enum hat (ähnlich der map) eine entsprechende Action hinterlegt (NORDEN.getAction().execute(whatever))
- oder ...

Aber bei allem sparst Du nicht wirklich was. Ich bin ein Enum Fetischist - Ich mag es, enums auch mal einiges mitzugeben an Funktionalität.
 
Es geht ja auch um die Grundprinzipen wie man das lösen kann zu sehen und nicht speziell über die Richtungen das war nur als Beispiel gedacht wozu jeder was anfangen könnte
Dazu noch ein kleines Beispiel

Java:
public final class Main {

    private enum Himmelsrichtung {
        NORDEN, SUEDEN, WESTEN, OSTEN, TO_HELL;
    }

    private static final Map<Himmelsrichtung, Runnable> actions;
    static {
        final EnumMap<Himmelsrichtung, Runnable> a = new EnumMap<>(Himmelsrichtung.class);
        a.put(Himmelsrichtung.NORDEN, (Runnable) () -> System.out.println("geh nach Norden"));
        a.put(Himmelsrichtung.SUEDEN, (Runnable) () -> System.out.println("geh nach Süden"));
        a.put(Himmelsrichtung.WESTEN, (Runnable) () -> System.out.println("geh nach Westen"));
        a.put(Himmelsrichtung.OSTEN, (Runnable) () -> System.out.println("geh nach Osten"));
        actions = Collections.unmodifiableMap(a);
    }

    public static void main(final String[] args) {

        gehen(Himmelsrichtung.NORDEN);
        gehen(Himmelsrichtung.WESTEN);
        gehen(Himmelsrichtung.TO_HELL);

    }

    private static void gehen(final Himmelsrichtung himmelsrichtung) {
        actions.getOrDefault(himmelsrichtung, (Runnable) () -> { throw new IllegalArgumentException(); }).run();
    }

}
Edit: Die cast's sind nicht nötig, die hat nur die IDE gemacht und sind jetzt übbrig - weil ich fast alles auf die Schnelle generiert habe.
 

Zurück
Oben