Typecast entfernen

  • Themenstarter Themenstarter JohannisderKaeufer
  • Beginndatum Beginndatum
J

JohannisderKaeufer

Gast
Ich habe folgendes Programm

2 Klassen
Java:
/** Abstrakte Klasse */
class AbstractClass {
  void m1(){}
}
/** Erweiterung der Abstrakten Klasse */
class ConcreteClass extends AbstractClass {
  void m2(){}
}

2 weitere Klassen, die Methoden der ersten beiden Klassen aufrufen
Java:
/** Ruft m1 auf */
class Caller1 implements Caller{
  void call(AbstractClass c){
    c.m1();
  }
}
/** Ruft m2 auf */
class Caller2 implements Caller{
  void call(AbstractClass c){
    ConcreteClass cc = (ConcreteClass) c;
    cc.m2();
  }
}

und eine Klasse die das ganze Zusammenhalten soll und in der Methode foo, die entsprechenden Methoden aufruft.
Java:
class Main{
  List<Caller> callers;
  AbstractClass c;
  public Main(List<Caller> caller, AbstractClass c){
    this.caller = caller;
    this.c = c;
  }

  void foo(){
    for(Caller caller : callers){
      caller.call(c);
    }
  }
}

Soweit so gut. Das funktioniert auch, wenn ich in der Main Methode so zusammenbaue.

Java:
public static void main(String[] args){
  List<Caller> callers = new ArrayList<Caller>();
  callers.add(new Caller1();
  callers.add(new Caller2();
  AbstractClass ac = new ConcreteClass();
  new Main(callers, ac);
}


Wenn ich allerdings in der Main-Methode eine AbstractClass initialisiere, dann läuft das ganze solange bis ich in der Klasse Caller2 den Typecast auf ConcreteClass durchführe. Und deshalb würde ich gerne den Typecast umgehen, sodaß ich möglichst schon zur Compilezeit diesen Fehler erhalte.

Und genau hier happert es momentan. Ich hab mir schon ein paar Sachen mit Generics überlegt, die aber irgendwie alle nicht hinhauen. Vielleicht hat noch jemand eine Idee oder einen Beweis das das nicht geht.

Edit: Ok, ich hab noch was vergessen.
Das Programm soll zuerst mit den Klassen Main, Caller1 und AbstractClass zusammenarbeiten und dann dynamisch erweitert werden können durch die Klassen Caller2 und entsprechend ConcreteClass ohne wieder an Main, Caller1 oder AbstractClass handanzulegen.

Idealierweise stelle ich mir eine Signatur für den Konstruktor von Main so oder zumindest soähnlich vor
Java:
callers = new ArrayList<Caller<ConcreteClass>>();
new Main<ConcreteClass>(callers, new ConcreteClass);
 
Zuletzt bearbeitet von einem Moderator:
Wie schaut denn das Interface Caller aus? Schreibt das die Methode call vor?
Das Problem an deinem Konstrukt ist, dass du in der Methode eine AbstractClass erwartest, intern dann aber mit ner ConcreteClass arbeiten willst/musst.

Wäre folgendes nicht machbar für dich?

Java:
public interface Caller<T> {
  public void call (T t);
}
 
Java:
public interface Caller<T> {
  public void call (T t);
}

Sowas hatte ich probiert, aber irgendwie hing es dann. Jetzt hab ich es aber raus denke ich.

Das Interface:
Java:
public interface Caller<T extends AbstractClass>{
  void call(T t);
}

Probleme hatte ich bisher bei deren Implementierung. Nun habe ich es so gemacht
Java:
class Caller1<T extends AbstractClass> implements Caller<T>{
  void call(T t){ 
    t.m1();
  }
}

class Caller2<T extends ConcreteClass> implements Caller<T>{
  void call(T t){ 
    t.m2();
  }
}

Und die Main Klasse
Java:
class Main<T extends AbstractClass>{
  List<Caller<T>> callers;
  T c;
  public Main(List<Caller<T>> caller, T c){
    this.caller = caller;
    this.c = c;
  }
 
  void foo(){
    for(Caller<T> caller : callers){
      caller.call(c);
    }
  }
}

Jetzt ist es perfekt, wenn ich versuche was zu "brechen", dann hagelt es entweder Exceptions oder zumindest Warnings.
 
Irgendwie schaut das ganze Konstrukt recht merkwürdig aus? Wird die m1() an der konkreten Klasse auch irgendwann man aufgerufen? Warum der Caller?
 
Irgendwie schaut das ganze Konstrukt recht merkwürdig aus?

Gut ich habe hier das ganze auf das notwendigste reduziert. Dort wo ich das ganze einbauen möchte macht es allerdings schon Sinn. Es ist hier ein wenig aus dem Context gerissen.

Wird die m1() an der konkreten Klasse auch irgendwann man aufgerufen?

Ja, die Implementierung wird aber von der AbstractClass geerbt, von daher paßt das schon.

Warum der Caller?

Tatsächlich ist da noch etwas mehr dahinter. In der Methode foo() werden nachher nicht alle Caller nacheinander aufgerufen sondern ein passender gesucht der dann die entsprechende Methode aufrufen soll.

Ein Teil des Programms (Main) kümmert sich nachher, welche Methoden aufgerufen werden können und sollen der andere Teil stellt die Methoden (AbstractClass und ConcreteClass) zur Verfügung.

Die Caller repräsentieren eine Methode die aufgerufen werden kann. Und mit den einzelnen Callern, kann ich Zentral (in der main-Methode) steuern welche Methoden zur Verfügung stehen. (z.B. haben Admins gegenüber normalen Usern mehr Möglichkeiten zur Verfügung)

Die Geschichte mit AbstractClass und ConcreteClass, kann man sich so vorstellen, daß ein Teil eine Grundfunktionalität zur Verfügung stellt und ein weiterer diese erweitert. (z.B. Basisdienst und Premiumdienst)

Oder einfach DependencyInjection ohne Framework.
 
In meinen Augen macht das alles immer noch keinen Sinn.

Du hast ein Interface "Caller" und in diesem die Methode "void call(AbstractClass)" definiert.
Dieses Interface implementieren die Klasse "Caller1" und "Caller2".
Zusätzlich die abstrakte Klasse "AbstractClass" die die Methode "void m1()" definiert.
Der einzige Unterschied zwischen dem Interface "Caller" und der abstrakten Klasse "AbstractClass" ist das das Interface die implementierende Klasse dazu zwingt die vorgegebene Methode zu implementieren. Deine "AbstractClass" macht dies nicht da die Methode selbst nicht als "abstract" deklariert ist.
Nun willst du einen List-Iterator bauen der über alle registrierten "Caller" geht und dabei ein Objekt von "AbstractClass" übergibt. Wenn allerdings das Objekt eine Instanz von "ConcreteClass" ist soll im entsprechenden "Caller" in "void call(AbstractClass" anstatt "void m1()" von "AbstractClass" eher "void m2()" von "ConcreteClass" gecallt werden.
Und damit das geht brauchst du natürlich einen Cast den du jetzt mit Generics weghaben willst.

Ganz erlich : WAS SOLL DER MIST ?
Das hat weder Sinn noch passt es irgendwie zu den von dir verwendeten Konzepten. Generell finde ich es schon krass Interfaces, abstrakte Klassen und dann noch Generics so krass zu mischen.

Das einfachste wäre gewesen wenn du in "AbstractClass" die Methode "void m1()" ebenfalls als "abstract" deklariert und dann in "ConcreteClass" mit
Java:
void m1() { m2(); }
implementiert hättest.
Schnell, einfach, sauber und vor allem verständlich.

Mag sein das das ganze so ohne den Kontext wenig Sinn ergibt, aber wenn deine Frage nun mal auf einen bestimmten Kontext bezogen ist solltest du diesen mal mitposten oder ein KSKB basteln an dem man das Problem auch versteht und erkennt was du am Ende damit anstellen willst. Stattdessen postest du hier etwas an sich sinnloses weil es aus dem Zusammenhang gerissen wurde.

Fass den Post nicht "aggressive" auf *wobei eher schon ein negativer Konstruktivbeitrag sein soll* aber so kann man dir nun mal bei diesem speziellen Problem schlecht helfen. Und es ist nicht verwunderlich warum einige sowas posten wie : "sieht schräg aus". Vielleicht gibt es ja eine viel bessere Lösung. Aber die können wir schlecht erraten ohne wichtige Infos. Denn erlich : das "Teil" da ist so wie es da steht echt ziemlich schräg und würde so sicher niemand verwenden.
 
Wieso denn immer alles so abstract? Ist eine Adapterklasse evtl. auch ein Designpattern?
Java:
interface ToAdapt {
	void m1();
	void m2();
}

class AdapterClass implements ToAdapt {
	void m1() {
	}
	void m2() {
	}
}

class ConcreteClass extends AdapterClass {
	void m2() {
		
	}
}
ConcreteClass überschreibt nun wahlweise m1 und/oder m2, dann den Typecast auf ToAdapt und gut is'. Für weitere Inspirationen schaust du dir evtl. mal die Listenerinterfaces des AWT an.
 
Zuletzt bearbeitet von einem Moderator:
Abstract war jetzt etwas ungünstig gewählt.

Ich möchte am Ende mehrere Anwendungen erstellen.
Eine sieht so aus
Java:
interface BasisI{
  void macheBasisZeug();
}
interface ErweiterungAI{
  void macheAZeug();
}
interface AnwendungA extends BasisI, ErweiterungAI{}

und eine beispielsweise so

Java:
interface BasisI{
  void macheBasisZeug();
}
interface ErweiterungB{
  void macheBZeug();
}
interface ErweiterungC{
  void macheCZeug();
}
interface AnwendungBC extends BasisI, ErweiterungB, ErweiterungC{}

Also bei der zweiten Komplett ohne dem A-Zeug.
Letztendlich habe ich einmal eine Anwendung
Code:
BasisI basis = new AnwendungA(){};
Und einmal
Code:
BasisI basis = new AnwendungBC(){};

Hier liegt allerdings nicht das Problem! Dies bläht nur die Beschreibung auf und läßt sich im prinzip auf
eine AbstractClass und ConcreteClass extends AbstractClass runterkochen.
Ich wiederhole es nochmals "ich möchte am Schluß mehrere Anwendungen haben".
Diesen Bereich könnte man auch Backend betitteln. Das stellt die Funktionalität bereit.

Jetzt kommt die Verknüpfung mit dem UserInterface.
Das Userinterface soll immer ähnlich aussehen und jeweils die Funktionen bereitstellen, die auch im Backend zur Verfügung stehen.
Jetzt kommen diese ominösen Caller ins Spiel.
Ein Caller soll eine bestimmte Funktion im Backend aufrufen können.
Das hat zur Folge das das Backend auch diese Funktion bereitstellen muß.
Der User bekommt schließlich die Auswahl einen Caller auszuwählen um eine Funktion im Backend aufzurufen.
Das führt dann z.B. zu
Code:
caller.callOn(backend);

Also zurück zum Anfang des UI.
Ich möchte nur einmal ein UI schreiben!
Das UI bekommt ein Backend übergeben und hält darauf eine Referenz.
Das UI bekommt eine Liste von möglichen Funktionsaufrufen, hier caller genannt übergeben.
Das UI weiß wie es die einzelnen Caller darstellen und Aufrufen kann.

Jetzt muß ich einen Typ für das Backend angeben.
Zur Auswahl stehen Object und BasisI. Object fällt weg, da UI noch ein paar anderer Dinge aus BasisI direkt abfragt.

Halte im UI für das Backend als Typ BasisI.
Dann kann ich den Callern nur ein BasisI übergeben und die caller müßen dann eigenständig einen Typcast durchführen. Folgen wären ClasscastExceptions zur Laufzeit. Dies sollte aber vermeidbar sein!

Also Generics! damit kann ich im UI bestimmen das das Backend vom Typ <T extends BasisI> ist. Für jeden Caller kann ich einzeln bestimmen was im Backend zur Verfügung stehen muß
Code:
Caller<T extends ErweiterungA>
und somit
Code:
void call<T>()

Wenn ich nun das ganze Initialisiere Bekommt das UI den Typ AnwendungA. Somit akzeptiert das UI nur ein Backend vom Typ AnwendungA und die Liste von Callern kann ich ebenfalls auf AnwendungA typen.

Für die AnwendungBC das ganze mit AnwendungBC typen.

Versuche ich nun für AnwendungBC einen Caller der ErweiterungA benötigt hinzuzufügen, dann bekomme ich wenn ich nicht typisiere eine Warning und wenn ich typisiere einen Error.

Wenn ich nun im weiteren weitere Erweiterungen schreibe, dann brauche ich einen Caller, den ich zentral dem UI hinzufügen kann und eine entsprechende Erweiterung für das Backend.

Es muß auch gesagt werden, daß ein Caller nicht nur die Funktion für das Backend aufruft, sondern auch Daten einliest, Convertierungen vornimmt und parameter für den Funktionsaufruf im Backend setzt.
Eine Erweiterung im Backend besteht auch aus mehreren Funktionen, wobei ein Caller sich nur um jeweils eine dieser kümmern wird.

TL;DR
Stell dir ein JFrame vor dem du nur ein Model und beliebige ActionListener übergeben mußt die Methoden auf dem Model aufrufen. Stell dir auch noch vor, daß dieses JFrame für jeden ActionListener einen Button mit entsprechender Beschriftung erstellt und dem UI hinzufügt. Und jetzt stell dir vor, daß soll noch lose gekoppelt, dynamisch kombinierbar und Typesafe sein.

Funktioniert jetzt aber schon, daher erledigt
 

Zurück
Oben