Verschiedene Versionen eines Interfaces

Status
Nicht offen für weitere Antworten.

Lim_Dul

Top Contributor
Mir spukt gerade folgende Frage im Kopf rum, ist momentan eher theorethische Natur, weil ich das Problem vermeiden will. Aber interessieren würde mich es dennoch mal.

Gegeben seien von einem Interface A zwei Versionen. (Die eine älter, die eine neue) sowie zwei davon abgeleitete Klassen.

Code:
// Version 1
public interface A {
  public void doSomething();
}

Code:
// Version 2, neuer

public interface A {
  public void doSomething();
  public void doSomeOtherThing();
}

Klasse B1 wurde mit der Version 1 kompiliert, Klasse B2 mit der Version 2.

Wann kracht es, wenn ich sowohl ein Objekt von B1 und B2 anlege? Sofort beim laden der Klasse B1? Oder erst wenn ich auf einem Objekt der Klasse B1 die Methode doSomeOtherThing() versuche aufzurufen?


Hintergrund der ganzen Geschichte ist der, dass ich für mein Programm eine Art Plugin Struktur haben will für Computergegner. Dabei kann es sein, dass sich das zu implementierende Interface erweitert, wenn neue Optionen hinzukommen. Ich kann allerdings garantieren, dass die neuen Methoden niemals bei alten Versionen aufgerufen werden.

Allerdings denke ich momentan nach, wie ich das sauberer lösen kann, dennoch würde mich eine Antwort interessieren.
 
Also wenn Du wirklich beide Varianten des Interfaces gleichzeitig benutzen willst, dann kommt es doch darauf an, in welchem Package sie sich befinden. Sind die qualifizierten Namen der Interfaces also unterschiedlich, dann wirds keine Probleme geben (hängt dann halt vom Import Statement ab). Der andere Fall, also zweimal der gleiche qualifizierte Name, das würde ja nur gehen, wenn die Dateien aus unterschiedlichen Jars stammen. Dann wirds wohl zu nem Compiler Fehler kommen. Ausprobiert habe ich sowas aber noch nie.
 
Hi diese Java Schwachstelle ist mir auch schon aufgefallen. Es kracht sobald du versuchst b1.doSomeOtherThing() aufzurufen.

Gruß,
nocxsville
 
Es kracht mit einer "NoSuchMethodException", die kann man wenigstens abfangen.
 
die richtige vorgehensweise wäre das neue interface von a abzuleiten sprich:

public interface Aneu extends A {
public void doSomeOtherThing();
}

dann weiß man auch zu jedem Zeitpunkt welches Interface gerade verwendet wird
 
Gast hat gesagt.:
die richtige vorgehensweise wäre das neue interface von a abzuleiten sprich:

public interface Aneu extends A {
public void doSomeOtherThing();
}

dann weiß man auch zu jedem Zeitpunkt welches Interface gerade verwendet wird

Ideal wäre es, sowas überhaupt zu vermeiden 😉

Dieser Vorschlag hätte wieder den Nachteil, dass ich irgendwann eine riesige Kaskade von Interfaces habe A, A1, A2, ... An und das Testen, welche Version das zu ladende Plugin implementiert, in eine if instanceof Kaskade ausartet. Auch nicht schön 🙂

Wenn ich "einfach" das Interface ändere und gleichzeitig relativ sicher garantierten kann, dass bei alten Plugins die neuen Methoden nie aufgerufen werden, habe ich weniger Arbeit. Und zur Sicherheit kann ich jede Methode immer noch in ein try - catch packen und entsprechend im Fehlerfall reagieren.

Wirklich sauber ist diese Lösung auch nicht, aber eine Lösung, die auf beide obere Varianten verzichtet und gleichzeitig den Verwaltungsoverhead, den jemand hat, der das Plugin schreibt, minimiert habe ich (noch) nicht.
 
Wie wäre es denn, wenn Plugins installiert werden müssen undzwar in der Form, dass der Quelltext eines Plugins zum Installieren des selbigen mit den gegenwärtigen Binaries der Interfaces compiliert wird?

Auf diese Weise würde man keine fremd-compilierten Binaries laden müssen und hätte somit nicht nur die richtige Version der Interfaces, sondern es sind sogar alle Plugins mit dem gleichen compiler compiliert worden 😀
 
Lim_Dul hat gesagt.:
Gast hat gesagt.:
die richtige vorgehensweise wäre das neue interface von a abzuleiten sprich:

public interface Aneu extends A {
public void doSomeOtherThing();
}

dann weiß man auch zu jedem Zeitpunkt welches Interface gerade verwendet wird

Ideal wäre es, sowas überhaupt zu vermeiden 😉

Dieser Vorschlag hätte wieder den Nachteil, dass ich irgendwann eine riesige Kaskade von Interfaces habe A, A1, A2, ... An und das Testen, welche Version das zu ladende Plugin implementiert, in eine if instanceof Kaskade ausartet. Auch nicht schön 🙂


Naja, aber dafür gibts ja wiederum Muster, um sowas zu verhinern. Mir erscheint die oben angesprochene Lösung schon am sinnvollsten. Die Versionsbehandlung könnte man imo recht komfortabel mit ner Zuständigkeitskette realisieren. Die kannst Du nämlich Schritt für Schritt erweitern, ohne dabei die bereits implementierte Behandlung älterer Versionen verändern zu müssen. Anfragen werden dann automatisch vom richtigen Objekt behandelt, ohne dass Du unübersichtliche if instanceof Blöcke hast.
 
Ich hol jetzt mal etwas weiter aus und erkläre, worum es geht.

Das Ziel ist es, Computergegner für ein von mir programmiertes Spiel zu implementieren.
Dabei soll es möglich sein, Computergegner extern zu programmieren und einfach als JAR Datei hinzuzufügen und im Programm zu nutzen.

Konkret geht es um das Brettspiel Carcassonne-

Nun hat das Spiel aber extrem viele Spieloptionen und damit auch sogenannte Spielmodi. Sei es Karte legen, Figur setzen, Drachen ziehen etc.

Idee, die mir momentan für die Bots im Kopf rumspukt ist die, dass es für jeden Modus eine Funktion im Interface gibt:

Code:
public ResultObject placeCard(GameInfoObjekt gameInfo);

Der Bot bekommt die Infos übergeben und liefert ein entsprechendes Ergebnis - wo er die Karten hinsetzen will - zurück.
Das ganze für jeden Modus. Vorteil der Sache ist, dass innerhalb des Bots keinerlei Verwaltungsoverhead der Form anfällt: "In welchem Modus bin ich, was soll ich tun".

Nachteil ist, dass es passieren kann, dass neue Spieloptionen und damit auch Spielmodi auftauchen, weil ich beispielsweise eine neue Erweiterung eingebaut habe. Das können dann die alten Bots nicht.

Aber jeder Bot muss zwingend eine Abfrage haben, wo man ihn Fragen kann, welche Optionen er überhaupt unterstützt. Das heißt in Spielen mit der neuen Option würde ein alter Bot nicht auswählbar sein und somit wäre garantiert, dass nur Methoden aufgerufen werden, die tatsächlich implementiert sind. (Was nicht heiß, das auf try/catch Abfragen verzichtet werden kann.)



Wie wäre es denn, wenn Plugins installiert werden müssen undzwar in der Form, dass der Quelltext eines Plugins zum Installieren des selbigen mit den gegenwärtigen Binaries der Interfaces compiliert wird?

Auf diese Weise würde man keine fremd-compilierten Binaries laden müssen und hätte somit nicht nur die richtige Version der Interfaces, sondern es sind sogar alle Plugins mit dem gleichen compiler compiliert worden
Alte Versionen liesen sich aber nur kompilieren, wenn zumindest leere Versionen der neuen Methoden implementiert würden.

Ich würde auch ungerne drauf verzichten alle alten Bots nutzlos zu machen, nur weil ich eine weitere Spieloption implementiere.

Ich könnte das natürlich auch so im Interface definieren:

Code:
public ResultObject doSomething(GameInfoObjekt gameInfo);

Und anhand von gameInfo.getModus() verzweigt der Bot dann in den jeweiligen Modus. Nachteil dieser Lösung wäre aber der Verwaltungsaufwan innerhalb der Bots, da jeder dann ein switch/case auf gameInfo.getModus() implementieren müsste.
Weiterhin würde ich damit die Fehlerbehandlung, wenn ein nicht implementierter Modus übergeben wird, in die Hände des jeweiligen Botprogrammieres legen. Das würde ich ebenfalls gerne vermeiden.


Von daher tendiere ich momentan noch zu der Variante, die NoSuchMethodExceptions werfen kann, auch wenn ich das Gefühl habe, dass dieses Design nicht ganz sauber ist.
 
Alte Versionen liesen sich aber nur kompilieren, wenn zumindest leere Versionen der neuen Methoden implementiert würden.

Ich würde auch ungerne drauf verzichten alle alten Bots nutzlos zu machen, nur weil ich eine weitere Spieloption implementiere.

Oder du nimmst statt eines Interfaces eine Klasse, von der alle Bots erben? Dann meckert der compiler nicht mehr über nicht implementierte Methoden und es sind auch alle Methoden erstmal existent (auch wenn die nicht überschrieben nix tun)

Nun könnte eine solche Methode in der Basisklasse immer null zurückliefern, wenn sie nicht überschrieben wurde, woran man erkennen kann, dass der bot das Feature nicht unterstützt.

Noch eine andere Variante wäre es, für jedes Feature ein neues Interface zu nehmen (bestehende Features dürfen sich dann natürlich nicht mehr ändern, aber da kann man vllt. zur Not auch noch Versionsnummern oder einfach schlüssige neue Bezeichnungen für ein weiteres Interface nehmen 😀). Aber so könnte man jederzeit durch ein instanceof sehen, welcher Bot welches Feature implementiert.
 
Du solltest deine Bots versionieren. Du kannst dann auch zur Laufzeit die Bots fragen, welche Version sie haben und entsprechend nicht implementierte Spielmodi deaktivieren. Der Anwendung muß es egal sein, wie die Bots programmiert sind. Sie fragt nach der Version und weiß dann, welche Features der Bot unterstützt.

Ich würde für die Bots eine Hierachie aus abstrakten Klassen verwenden. Da kannst du die Schnittstellen festlegen und bestimmte Basisfuktionalität (auch Variablen, die Auskunft über die Version geben etc.) hinterlegen, die du für alle Bots verwenden kannst.
 
Die Abfrage, was die Bots können ist obligatorisch. Daher kann ich auch garantieren, dass nur die Sachen aufgerufen werden, die tatsächlich unterstützt werden.

Ich hab auf jeden Fall jetzt erstmal ein paar weitere Möglichkeiten, über die ich nochmal in Ruhe nachdenken kann.
 
Status
Nicht offen für weitere Antworten.

Zurück
Oben