Wie vererbe ich vernünftig bei stark überladenen Konstruktoren?

Status
Nicht offen für weitere Antworten.

-SPM-Mad

Mitglied
Hallo zusammen,

ich nehme mal das klassiche Beispiel von Fahrzeugen und Autos. Auto erbt von Fahrzeug.

Fahrzeug kann allerdings diverse Eigenschaften haben und da Java keine optionalen Parameter erlaubt, kann man den Konstruktor überladen mit verschiedenen Kombinationen von Eigenschaften.

Allerdings werden Konstruktoren nicht mit vererbt und so muss ich die Konstruktoren mit super() durchreichen in meiner Kind-Klasse Auto.

Was ist aber nun wenn ich in Auto selber beim Konstruktor was setzen will? Ich habe mal um das zu veranschaulichen ein fiktives Beispiel erstellt:

[HIGHLIGHT="Java"]public class Car extends Vehicle {
// example attribute
private int value;

public Car(String color, String name, double weight) {
super(position, orientation, weight);

// this here is stupid...:
this.value = calcValue();
}

public Car(String color, String name) {
super(position, orientation);

// ... because here I need to write it again:
this.value = calcValue();
}

private int calcValue() {
// .. fancy code
//return fancyValue;
}
}

public abstract class Vehicle {

private double weight = 0; //in kg
private String color = "";
private String name = "";

public Vehicle(String color, String name) {
this(color, name);
}

public Vehicle(String color, String name, double weight) {
this.setName(name);
this.setColor(color);
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getColor() {
return color;
}

public void setColor(String color) {
this.color = color;
}

public void setWeight(double weight) {
this.weight = weight;
}

public double getWeight() {
return weight;
}

}
[/HIGHLIGHT]

Beim erstellen von Car will ich auch etwas Car-Spezifisches machen (hier beispielhaft einen 'value'(wertigkeit) zuweisen.
Würde Car nicht erben hätte ich ja einen 'haupt'-Konstruktor und alle anderen parameter-Kombinationen würden mit this() diesen aufrufen und nicht übergebene Parameter mit Standardwerten füllen. Dies macht aber die Oberklasse Vehicle hier, also muss ich doch super() aufrufen!?

Ich könnte auch bei jedem Konstruktor in Car eine Methode aufrufen in der ich dann wiederrum die Car-Spezifischen Sachen mache, aber das ist irgendwie blöd. Gibt es da keine elegantere Methode? In meinem richtigem Projekt sind es nämlich wesentlich mehr Konstruktoren und Parameter die das alles umständlicher machen.

Wie ist der 'saubere' Weg?

Vielen Dank schonmal,
Gruß
-SPM-Mad
 

hdi

Top Contributor
Umgang mit mehreren Konstruktoren:
Du hast einen "Haupt"-Konstruktor, der die "Arbeit" macht, also die Attribute setzt.
Nun kannst du hundert verschiedene Konstruktoren schreiben, die alle verschiedene
Werte übergeben bekommen.
Die setzen aber niemals per "=" einen Wert (keinen einzigen!), sondern rufen
immer den Haupt-Konstruktor auf, mit den Werten, die sie bekommen haben,
und für den Rest Default-Werte.

...und mit Vererbung? Hat das alles nix zu tun, einziger Unterschied: Der Haupt-Konstruktor (!!!)
hat nicht nur "=" Zuweisungen, sondern ruft am Anfang n super auf, um die Attribute
der Vaterklasse zu setzen. Das ist ja auch nix anderes als "=", nur eben auf
Vater-Attribute, um die sich der Vater-Konstruktor selber kümmert.
Danach kommen per "=" alle Unterklassen-spezifischen Dinge.

Kuck dir das mal an:

[HIGHLIGHT="Java"]public class Car extends Vehicle {

private int carValue1;
private int carValue2;

// Das hier ist der Haupt-Konstruktor, er erwartet ALLE Attribute sowohl von
// der Vaterklasse als auch dieser Klasse selbst.

public Car(String color, String name, double weight, int carValue1, int carValue2) {
// Vater-Konstruktor mit Vater-Attributen
super(color, name, weight);

// Alle Attribute DIESER Klasse normal zuweisen:
this.carValue1 = carValue1;
this.carValue2 = carValue2;
}

// Irgendwelche "Teil"-Konstruktoren, delegieren (evtl über mehrere
// Konstruktoren hinweg)im Endeffekt immer auf den Haupt-Konstruktor.

public Car(String color, String name) {
this(color, name, 200, carValue1, 230);
}

public Car(String name, int carValue1){
this.("white", name, 200, carValue1);
}
// usw.

}[/HIGHLIGHT]

...das klappt wie gesagt, im Endeffekt muss nur immer dein Haupt-Konstrukor aufgerufen werden.
Also nochmal zum an den Kühlschrank kleben, paar einfache Regeln zum Auswendig lernen:

1) Ein Hauptkonstruktor, erwartet ALLE Attribute (Vater- &Kindklasse)
2) Beliebige Nebenkonstruktoren, haben IMMER nur eine Zeile: this(....)
3) Die Nebenkonstruktoren können sich beliebig verschachtelt weiterdelegieren
4) Am Ende muss man aber immer beim Haupt-Konstrukor landen.
 
Zuletzt bearbeitet:

0x7F800000

Top Contributor
@hdi:
Nein, so geht's nicht. Sein Problem ist schon echt. Das kriegt man auch nicht durch irgendwelche merkwürdige parameter-permutationen gelöst. es gibt nämlich zwei überladene super-konstruktoren, und diese muss man irgendwie von zwei verschiedenen überladenen this-konstruktoren aufrufen, sonst bleiben die default-werte des überladenen super-konstruktors nicht erhalten.

@OP:
Aber ist denn die Initialisierung derart abgefahren, dass du hier unbedingt auf "DRY bis in die letzte Zeile" achten musst? Wäre es dann vielleicht möglich, eine zusätzliche "init"-methode dranzubauen?

Tja, in der Tat, ziemlich hässliche geschichte... :bahnhof:
 

hdi

Top Contributor
Ahh... Shit das hab ich grad echt verplant... Hm.. aber das muss doch irgendwie gehen.
Naja es gibt sicher Möglichkeiten, aber spontan denke ich: Das mit der Methode ist die
beste Idee.

edit: Meine einzige Idee wäre jetz sonst nur final static protected Variablen
für Default-Werte in der Oberklasse zu definieren.
Sowohl die Oberklasse als auch Unterklasse initialisieren bestimmte Werte
mit diesem Wert dann...

Dann bekommste wenigstens keine Probleme dass sich da was widersprechen könnte.
Sieht halt auch etwas strange aus, aber mein Gott, ist denke ich recht "sicher" so.

Und wenn du komplett paranoid bist, packst du die Klasse+Kinderklassen halt noch
in ein eigenes Package, dann ist das dicht.

Dann könnte man zwar noch immer beliebige Werte in den Unterklassen setzen,
aber man kanns auch übertreiben. Man muss sich fragen, wie dumm jmd sein muss,
so ein doch ziemlich ins Auge stechendes Konstrukt mal einfach abzuändern, und
final-konstanten durch magic numbers zu ersetzen.
 
Zuletzt bearbeitet:

-SPM-Mad

Mitglied
Naja, irgendwie drum herum basteln kann ich sicherlich. Ich wollte mir nur gleich die 'elegante' Herangehensweise aneigenen. Weil mein aktuelles Projekt vererbt noch weiter und hat mehr Konstruktoren... besser früher als zu spät merken wie es richtig geht :p
Vielleicht ist Vererbung hier nicht flexibel genug und ein anderes DesignMuster ist hier passender :p

Ich hab im Moment die Version mit einer weitere init() methode gewählt wie Andrey es vorschlägt:

[HIGHLIGHT="Java"]public class Car extends Vehicle {
// example attribute
private int value;

public Car(String color, String name, double weight) {
super(position, orientation, weight);
init();
}

public Car(String color, String name) {
super(position, orientation);
init();
}

private init() {
// doing fancy init stuff
}
}[/HIGHLIGHT]
 

hdi

Top Contributor
Nur nochmal n Bsp was ich meinte. Damit behebst du das Problem von
mehreren Super-Aufrufen in den Kinderklassen, und brauchst keine intit() Methode mehr o.ä. Packs in ein Package wenn die Default-Werte nach aussen komplett geheim sein sollen.

Zugegeben: Ungewühnlich, aber ich denke doch ne ganz gute Lösung wenn du
wirklich so viele Konstruktoren hast. Sonst müsstest du 10x ein init() Aufrufen,
bzw. in 10 Konsturktoren was umschreiben wenn du auch noch ein init2() o.ä. möchtest.

[HIGHLIGHT="Java"]public class Car extends Vehicle {
// example attribute
private int value;

public Car(String color, String name, double weight) {
super(color, name, weight);

// this here is stupid...:
this.value = calcValue();
}

public Car(String color, String name) {
this(color, name, DEFAULT_WEIGHT);
}

private int calcValue() {
return 0;
}
}

abstract class Vehicle {

/* This class AND ALL SUBCLASSES should use these initial default values: */
protected static final int DEFAULT_WEIGHT = 200;

private double weight = 0; // in kg
private String color = "";
private String name = "";

public Vehicle(String color, String name) {
this(color, name, DEFAULT_WEIGHT);
}

public Vehicle(String color, String name, double weight) {
this.setName(name);
this.setColor(color);
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getColor() {
return color;
}

public void setColor(String color) {
this.color = color;
}

public void setWeight(double weight) {
this.weight = weight;
}

public double getWeight() {
return weight;
}

}
[/HIGHLIGHT]
 

0x7F800000

Top Contributor
Eigentlich kommt mir überhaupt ein wenig suspekt vor, dass ein konstruktor für ein Objekt so viele verschiedene Argumente hat...
Bei so einer einfachen funktionslosen struct-ähnlichen Anhäufung von variablen wie "GridBagConstraints" ist das noch okay, aber wieso hat denn deine "komplexe" Klasse eigentlich derart komplizierte Konstruktoren? Kannst du da vielleicht etwas sinnvoll zusammenfassen?
 

hdi

Top Contributor
Ja, ich denke auch, du solltest mal mehr Setter verwenden, und nicht versuchen
alle möglichen Zustände direkt über einen Konstruktor zu realisieren.

edit: Bei sowas kannst du dir nämlich von aussen, wo du das OBjekt konfigurierst,
auch ne Methode schreiben die ne Map von zu setzenden Parametern nimmt (keys),
und dementsprechend setter aufruft mit den Values der Map.

Dann haste quasi eine einzige "Konfigurations"-Methode für dein Objekt,
die auch voll dynamisch ist und sich mit 0 bis 123 Werten aufrufen lässt, und nur die
Setter aufruft auf's Objekt.
Dann musste das nich mehr über tausend Konstruktoren regeln.

[HIGHLIGHT="Java"]Car myCar = new Car(..) // Ober- und Unterklasse haben nur einen Konstruktor für die wichtigsten Dinge.
//..
map.put("weight", 200);
map.put("anotherthing", "whatever);
configure(myCar, map); // ruft zur Laufzeit setter auf dem Objekt auf[/HIGHLIGHT]

.. man kann ja über irgendwelche Klassen sich den Aufruf von Methoden zur Laufzeit
zusammenbauen... Weiss jetz nich genau welche, aber es geht. Is auch nich so kompliziert.

...Ist natürlich auch eine recht komische Vorgehensweise.
Das beste is noch immer es nich mit Design zu übertreiben: Du wirst ja wohl nicht
421 Attribute haben die du per Setter setzen musst oder... Das geht recht flott einfach
hinzuschreiben...
 
Zuletzt bearbeitet:
V

Vayu

Gast
@optionale parameter

[HIGHLIGHT="Java"]public MyClass(String... arg) {

}[/HIGHLIGHT]

leg dir halt ne reihenfolge fest, in der die parameter kommen müssen und gib null an, wenn ein parameter nicht gesetzt werden soll. dann kommst mit einem Konstruktor aus :)
 
S

Spacerat

Gast
Wie veerbt man richtig... Hmmm... alles mir vermachen...
Oh der Titel ging ja noch weiter. Naja... normalerweise sollte das was hdi anfangs sagte schon klappen. Man muss ja nicht jeden bzw. man muss gar keinen Konstruktor der Vater-Klasse überlagern. Es genügt völlig, wenn man in seiner Klasse den Hauptkonstruktor der Vaterklasse aufruft. Dieser ist an der Anzahl seiner Parameter zu erkennen (er hat halt die meisten). Wenn nun alledings für den Hauptkonstruktor noch irgendwelche Parameter initialisiert werden müssen, geht das pro Parameter mit einer "private static"-Methode.
@Andrey: Und wenn es jeder auf diese (einzig richtige) Art machen würde, gäbe es auch keine Probleme. Wenn allerding die Vaterklasse schon mehrere Konstruktoren hat, die es nicht so macht, muss man sich vorher den Konstruktor herauspicken, der einem die Vaterklasse seinen Bedürfnissen entsprechend initialisiert.
 
Zuletzt bearbeitet von einem Moderator:

-SPM-Mad

Mitglied
Ich bin nun auch auf die 'vernünftige' Variante gestoßen. Builder-Pattern bzw. Factory-Patter ist das was ich suche. (danke @maki)

Ich werde mich mit diesen mal auseinandersetzen und gucken welches das Entwurfsmuster ist das mir am besten Hilft.

Danke schonmal an alle.

Gruß
-SPM-Mad
 
Zuletzt bearbeitet:

Wildcard

Top Contributor
Das ein Builder-Pattern nicht immer aufwendig mit abstrakt, konkret, usw. sein muss, sondern auch ganz einfach sein kann und kinderleicht implementiert werden kann, sieht man zB an der von mit heiß geliebten GridDataFactory von SWT.
Damit baut man GridData, ähnlich GridBagConstraints und wer die kennt, weiß, das es keinen Spaß macht damit zu arbeiten, so auch in SWT.
GridDataFactory Builder Pattern hingegen:
[HIGHLIGHT="Java"]GridDataFactory.fillDefaults().align(SWT.FILL, SWT.CENTER).span(1,2).applyTo(button)[/HIGHLIGHT]
Jede Methode liefert also wieder eine GridDataFactory zurück und ganz am Ende kommt dann entweder applyTo(Control), oder create() um ein GridData Objekt zu erhalten.
Kommt eben immer auf den Use-Case an
 
Status
Nicht offen für weitere Antworten.

Ähnliche Java Themen

Neue Themen


Oben