Datentypen eine Methode mit zwei möglichen Rückgabewerten

lafilia

Mitglied
Hallo,

eine vermutlich blöde Frage, aber ich weiß einfach nicht, wie ich Folgendes lösen soll:

Ich habe ein Objekt Spieler mit der Exemplarvariablen spielVerhalten.
Das Spielerobjekt hat entweder spielVerhaltenA oder spielVerhaltenB.
Auf dem Spieler wird nun eine Methode spielZug() aufgerufen, welche der Spieler eigentlich nur an sein spielVerhalten weitergibt.
Nun liefern die beiden spielVerhalten-Objekte aber verschiedene Rückgabetypen. Das eine ein int und das andere ein Array.
Dass ich das Verhalten kapsele, soll ja nun aber eigentlich dazu führen, dass ich sagen kann spieler.spielZug(), ohne dass ich weiß, was der Spieler für ein spielVerhalten hat.
Aber nun muss ich ja im Spieler bei der Implementation der Methode den Rückgabetyp angeben und würde natürlich gerne reinschreiben: int oder Array.

Mir ist bisher nichts Besseres eingefallen, als eigene Klassen zu schreiben, die Integer und Array erweitern und zusätzlich noch ein selbst definiertes Interface implementieren, nur um eine Schnittstelle zu schaffen.
Aber so ein leeres Interface, das wirklich nur als Schnittstelle gilt, ist doch irgendwie unschön, oder?

Gibt es da einen einfacheren und schöneren Weg?

Vielen Dank für die Hilfe!
 
warum haben bei dir objekte die sich sehr ähnlich anhören so grundunterschiedliche rückgabewerte? passe sie doch einfach an

eine methode kann soweit ich weiß immer nur 1 rückgabewert haben.
 
Wenn du an solche Fragen stößt, dann ist das ein Zeichen dafür, dass das Design nicht stimmt. Spielverhaltenobjekte sollten ja einen gemeinsamen Nenner haben (Interface o.ä.), dann erübrigt sich die Frage. Oder du machst ein Interface zu dem Rückgabe wert, wo du dann "machwas()" definierst, wo irgendetwas entweder mit der Zahl oder dem Array (je nach Implementierung) passiert...
Aber egal was du machst, erstmal von Anfang an deine Struktur überdenken.
 
eine methode kann soweit ich weiß immer nur 1 rückgabewert haben.
das ist richtig.

einfach ein objekt rückgabe erstellen, welches für beide Spielverhalten den Rückgabewert enthält, also bei dir einmal inr und ein Array. zudem gibst du dem objekt noch eine boolean / int variable, die angibt, welches verhalten genommen wird. darauf musst du dann im spieler.zug rücksicht nehmen.

mfg
 
Deine Vorgehensweise läßt sich verallgemeinern, so dass du die "zweigeteilte" Rückgabeklasse überall wiederverwenden kannst. In anderen Sprachen (wie Haskell, Scala) heißt diese Klasse "Either" (mit den Untertypen Left und Right).

Mal aus'm Kopf:
Java:
public abstract class Either<L,R> {
     private final L l;    
     private final R r;    

     private Either(L l, R r) {
        this.l = l;
        this.r = r;
     }

     public L getLeft() { 
        if(l == null)  throw new IllegalArgumentException(); 
        return l; 
     }
     public R getRight() { 
        if(r == null)  throw new IllegalArgumentException(); 
        return r; 
     }

    public boolean isLeft() { return l != null; }
    public boolean isRight() { return r != null; }

    public static <A,B> Either<A,B> left(A a)  {
        if (a == null) { throw new IllegalArgumentException(); } 
        return new Either<A,B>(a, null);
    } 

    public static <A,B> Either<A,B> right(B b)  {
        if (a == null) { throw new IllegalArgumentException(); } 
        return new Either<A,B>(null, b);
    } 

}

Leider ist der Umgang in Java damit etwas hakelig, weil überall die Generics dranhängen müssen und das Auspacken nur umständlich über ein if geht.

Java:
public Either<Integer, int[]> method() {
     if (java.util.Math.random() > 0.5) {
         return Either.<Integer,int[]>left(42);
     } else {
         return Either.<Integer,int[]>right(new int[]{1,2,3});
     } 
} 

...

Either<Integer, int[]> either = method();
if(either.isLeft()) {
  int i = either.getLeft();
  ...
} else {
  int[] array = either.getRight();
  ...
}

Der Vorteil ist aber, dass du nicht immer sofort die Werte "auspacken" musst, sondern es auch praktischer sein kann, mit dem Either weiterzuarbeiten (wodurch man sich manchmal methodenüberladungen u.s.w. sparen kann).
 
Erst mal Danke für eure schnellen Antworten.

Sry, ich hab vor lauter Verwirrung eine völlig unsinnige Erklärung für diese Konstellation aufgeschrieben.

Es geht darum, dass ich die Methode spielzug(int spielzuNr) aufrufen möchte und je nach mitgegebenen Parameter ein anderer Typ (int, Array) zurückkommt.
Dort, wo die Methode spielzug() tatsächlich aufgerufen wird, wird nach diesem Rückgabewert entschieden, was weiter passiert. Aber es wäre schön, wenn ich sie als nur eine Methode in meiner Klasse spieler implementieren könnte, weil der nämlich nur wissen soll, dass er einen Spielzug ausführt und nicht mehr.
Ich hoffe, das ist einigermaßen verständlich.
 
Aber es wäre schön, wenn ich sie als nur eine Methode in meiner Klasse spieler implementieren könnte, weil der nämlich nur wissen soll, dass er einen Spielzug ausführt und nicht mehr.
Ich hoffe, das ist einigermaßen verständlich.

Das klingt für mich danach, dass du deinen Rückgabewert als eigenes Objekt implementieren könntest, das entsprechende Methoden
Code:
getInt()
oder
Code:
getArray()
hat.
 
das ist die oben erklärte idee, und entspricht deinen bedürfnissen:

Java:
import java.util.Arrays;


class Test {
    public static void main(String [] args) {
        Antwort ergebniss = spielZug(6);

        if (ergebniss.isInt()) {
            System.out.println("int rückgabe: " + ergebniss.getInt());
        }
        else {
            System.out.println("array rückgabe: " + Arrays.toString(ergebniss.getArray()));
        }
    }

    public static Antwort spielZug (int spielZugNr){
        if (spielZugNr < 5){
            return new Antwort(3);
        }
        else {
            int[] antwortArray = {1,2,3};
            return new Antwort(antwortArray);
        }
    }
}

class Antwort {
    private boolean typ;
    private int[] array;
    private int zahl;

    public Antwort(int zahl) {
        this.typ = true;
        this.zahl = zahl;
    }

    public Antwort(int[] array) {
        this.typ = false;
        this.array = array;
    }

    public boolean isInt() {
        return typ;
    }

    public int getInt() {
        return zahl;
    }

    public int[] getArray() {
        return array;
    }

}
 
Zuletzt bearbeitet von einem Moderator:
Das ist doch im Prinzip das Gleiche, wie ich gepostet habe, nur ohne Generics und deshalb nicht "wiederverwendbar".
 
Im Prinzip schon, nur wurde deine Lösung ja entweder überlesen, gekonnt ignoriert oder nicht verstanden. lafilia könnte sich ja nochmal melden
 
Das ist doch im Prinzip das Gleiche, wie ich gepostet habe, nur ohne Generics und deshalb nicht "wiederverwendbar".
Die "Antwort"-Lösung finde ich verständlicher und durchschaubarer als eine "abstrakte" Either-Klasse mit L- und R-Objekten . Es ist sicherlich auch leichter zu erweitern bzw. anzupassen (falls doch mal eine dritte Spielzugvariante hinzukommt).
 
Man kann auch anstelle eines "int" oder eines Arrays von ints auf eine Menge abstrahieren, das Verhalten gibt dann eine Menge von Zügen zurück und eine Menge kann ja auch einelementig sein. Das wäre einfach durchschaubar und klar. Dennoch werde ich das Gefühl nicht los das mit dem Design vom OT irgendwas nicht stimmt.
 
Ich bin mir nicht sicher ob der TO einen array von int will - dann hat er ja gar kein Problem.

Einfach den einen int in einen einen array der Länge 1 verpacken ....
 
Und wie willst du dann unterscheiden ob a oder b zurückgeben wird? b könnte ja auch nur ein Element enthalten. Ich schließe mich jedenfalls ThreadPools Meinung an - das Design scheint nicht gut gewählt.

Evtl kann der TO ja mal ein bisschen mehr von seiner Architektur preisgeben damit man vernünftige Vorschläge machen kann.
 
der int array war nur ein beispiel, der informationsgehalt der frage ist halt wie üblich etwas mangelhaft

aber ThreadPool hat eh recht, das design scheint arg fraglich
 
aber ThreadPool hat eh recht, das design scheint arg fraglich

Die Grundidee ist nicht schlecht und auch gängig, d.h. man trennt die Client-Schnittstelle von der "Service-Provider-Schnittstelle", was z.B. dem Bridge-Muster sehr ähnlich ist. Auch versucht der OT augenscheinlich "Komposition" vor Vererbung zu bevorzugen. Vll. haut einfach inhaltlich irgendwas nicht so ganz hin. Und vll. ist es auch schon damit getan grundsätzlich ein Array zurückgeben zu lassen etc. pp. Aber um das beurteilen zu können müsste mehr von der Architektur zu sehen sein wie Fassy schon anmerkte.
 
Erst mal vielen Dank für das rege Interesse an meinem Problem 😉

Ich wollte es vermeiden, der Frage einen riesigen Informationshaufen anzufügen.
Aber ihr habt vermutlich Recht damit, dass das Problem die grundsätzliche Architektur ist. Deshalb verlagere ich die Diskussion mal ein wenig.

Die ganze Anwendung soll es Spielern ermöglichen, über RMI gegen einander oder wahlweise einen Computerspieler zu spielen. Das Ganze soll natürlich verteilt laufen, theoretisch aber auch lokal ausführbar sein und vor allem erweiterbar (neue Spielart, GUI (bisherige Anforderung nur Konsole).

Das bisherige Konzept sieht vor, dass der Server für jedes Spiel einen Gamecontroller erstellt, der als eigener Thread läuft und die beiden Spieler mitgegeben bekommt.
Der Gamecontroller soll nun natürlich nicht wissen, mit was für einem Spieler er es zu tun hat, einem menschlichen oder einem computergesteuerten. Zudem soll der Spieler auch spielunabhängig verwendet werden können, darf also keine spielspezifische Logik implementieren.
Deshalb die Idee, sein Spielverhalten zu kapseln und eine Oberklasse Spielverhalten und zwei Unterklassen SpielverhaltenMensch und SpielverhaltenComputer zu erstellen.
Die Oberklasse sollte hier die Gemeinsamkeit implementieren, Aufrufen vom Gamecontroller den entsprechenden Spielzug zuzuordnen und die jeweiligen Unterklassen sollten zusätzlich die Kommunikation zur jeweiligen Intelligenz übernehmen. Das SpielverhaltenComputer ruft dabei Methoden auf seiner Intelligenz auf und das Spielverhalten Mensch kommuniziert mit dem richtigen Spieler, macht also Ausgaben und liest Eingaben.
An dieser Stelle stoße ich mich eigentlich zum ersten Mal. Denn eigentlich versuche ich hier, mit einer Lösung zwei verschiedene Probleme zu lösen. Einerseits versuche ich das Spielverhalten für ein konkretes Spiel zu kapseln und andererseits trenne ich die verschiedenen Kommunikationsarten zur 'Inteligenz'.
Müsste ich nun ein anderes Spiel implementieren, wären natürlich alle drei Klassen hinfällig. Auf der anderen Seite muss ja nun mal die Spiellogik auch irgendwo hin.
Durch diese gewollte Trennung der Spiellogik von der Spielerklasse entstand auch die etwas unglückliche spielzug(int SpielzugNr)-Methode. Der Gamecontroller sollte lediglich diese eine Methode im Spieler aufrufen können und die konkrete Ausführung sollte dem Spielverhalten überlassen werden. Das sollte zunächst die Spielzugnummer einem gewollten Spielzug zuordnen, in meinem konkreten Beispiel Schiffe versenken wäre das beispielsweise setzeUBoot(); und dann diese Methode ausführen. Schließlich sollte das Spielverhaltenobjekt über den Spieler das Ergebnis seines Spielzuges als Rückgabetyp liefern, dass der Gamecontroller mit Hilfe seines Regel-Objektes prüft, den Spieler bei Regelinkonformität den Spielzug erneut durchführen lässt und ansonsten den Spielzug auf den Spielbrettern umsetzt. Die Spieler haben also ihre Spielbretter nicht selber, sondern der Gamecontroller verwaltet diese, da er ja auch ständig zur Prüfung und Entscheidung über den weiteren Spielverlauf auf sie zugreifen muss.

Mir leuchtet aber selber ein, dass dies keine schöne Lösung ist.

Mein Problem ist also eigentlich, dass ich gerne den Spieler von der Spiellogik unabhängig halten möchte (auch eine klare Anforderung der Anwendung).

Ich hoffe, die Beschreibung dieses Anwendungsteils reicht aus, um die Problematik und das bisherige Konstrukt ausreichend zu beschreiben.

Für Anregungen und Kritik bin ich mehr als dankbar 😉
 
kleine Ergänzung:

dem Spieler die Methode macheSpielzug() geben, die immer nur ein int zurückgibt und zusätzlich setzeSchiff(int Schiffgroeße), die ein int[] zurückgibt.
Es wäre bei dieser Lösung natürlich nicht ohne weiteres möglich, den Spieler für ein anderes Spiel genauso zu übernehmen. Allerdings wäre das ja nun vorher auch nur bedingt möglich gewesen. int[] als Rückgabetyp ist ja auch nicht in jedem Spiel möglich.
 

Neue Themen


Zurück
Oben