Du verwendest einen veralteten Browser. Es ist möglich, dass diese oder andere Websites nicht korrekt angezeigt werden. Du solltest ein Upgrade durchführen oder ein alternativer Browser verwenden.
angenommen, ich möchte eine Klasse schreiben, die verschiedene Dinge in einem rechtwinkligen Dreieck berechnet.
Dann könnte ein Konstruktor z.b. Seitenlänge A und Seitenlänge B als Parameter erwarten und über unseren Freund Pythagoras die Seitenlänge C errechnen.
Ein weiterer Konstruktor könnte aber auch Seite und Winkel erwarten und über Sinus-/Cosinusfunktion rechnen wollen.
Somit hätte ich 2 Konstruktoren, die beide 2 Parameter als double erwarten, nicht wahr?
Zwei Konstruktoren mit gleicher Signatur ist schlecht.
Ein Lösungsansatz wäre, eigene Winkel- oder Seitentypen zu verwenden.
Zu bedenken ist auch, wenn du nicht NUR die Seite C im Dreieck als Member speichern willst, dass zwischen den Membern keine Abhängiugkeiten bestehen sollten. Also wenn du A und B hast, macht in der Regel eine Variable für C keinen Sinn mehr und sollte vermieden werden, denn diese kann per Methode errechnet und ausgegeben werden. Dadurch bleibt die Datenintegrität gewährleistet. (Denn wenn du eine Variable änderst musst du sonst immer auch alle anderen ändern, da sie sich bedingen). Vielleicht ist es daher besser, für bestimmte Berechnungen auch spezielle Klassen zu entwerfen, die eine bestimmte Aufgabe haben und nicht eine Universelle.
Auf der logischen Ebene ist für dich klar, dass 2 Seitenlängen etwas anderes sind als eine Seitenlänge und ein Winkel.
Wenn du nun auf die Programm/Java-Ebene kommst, dann bildest du aber alles auf den gleichen Datentyp ab. Du verlierst also Information.
Was also auf der logischen Ebene funktioniert, kannst du in Java nicht mehr 1:1 realisieren.
Eine schöne Lösung kann ich dir auch nicht anbieten.
Ad hoc würde ich das über eine Factory-Klasse bzw. eine statische create-Methode lösen.
Denkbar, aber etwas unsauberer wäre bei einem der beiden Konstruktoren einen zusätzlichen Dummy-Parameter hinzufügen.
Generell sind Konstruktoren nicht dazu da um Sachen zu berechnen.. Sondern vielmehr um Objekte (z.B. ein Dreieck) zu erstellen.. Die Berechnungen innerhalb eines Dreiecks (oder auch mit einem zweiten) sollte dann eher in Methoden stattfinden! Dazu sind Methoden auch schließlich da.
Mit der "Überladung" kriegst Du hier auch kein Problem, da Du einfach die beiden Methoden unterschiedlich benennst. So entsteht auch kein Konflikt, wenn sie die gleiche Signatur (Anzahl und Typ der Parameter) haben!
Generell sind Konstruktoren nicht dazu da um Sachen zu berechnen.. Sondern vielmehr um Objekte (z.B. ein Dreieck) zu erstellen.. Die Berechnungen innerhalb eines Dreiecks (oder auch mit einem zweiten) sollte dann eher in Methoden stattfinden! Dazu sind Methoden auch schließlich da.
Kann man so pauschal nicht sagen. Wenn ich etwa einen Immutable Typen habe, nur verhältnismäßig selten eine Instanz erstelle, aber sehr oft auf abgeleitete Attribute zugreife, kann es durchaus Sinn machen die "abgeleiteten" Attribute vorzuhalten und beim Erstellen der Instanz nur einmalig zu berechnen.
Oder auch, um ein weiteres Beispiel zu nennen: Beim Aufruf von .size() auf einer Liste, würde ich zunächst immer von konstanter Laufzeit ausgehen. Bei einer LinkedList müsste ich sonst ja z.B. immer die gesamte Liste durchlaufen, wenn das Attribut nicht extra vorgehalten werden würde, auch wenn es redundant ist.
Generell sind Konstruktoren nicht dazu da um Sachen zu berechnen.. Sondern vielmehr um Objekte (z.B. ein Dreieck) zu erstellen.. Die Berechnungen innerhalb eines Dreiecks (oder auch mit einem zweiten) sollte dann eher in Methoden stattfinden! Dazu sind Methoden auch schließlich da.
Aber genau das war ja auch die Frage:
Kann man ein Dreieck erzeugen, indem man verschiedene Informationen zum Dreieck im Konstruktor angibt ?
Ich finde es legitim, ein rechtwinkeliges Dreieck durch 2 Seiten oder 1 Seite und einen Winkel zu erzeugen. Die Konstruktoren wandelt diese Daten dann in eine interne Darstellung.
Die anderen Berechnungen (Fläche,...) gehören natürlich in entsprechende Methoden.
So richtig "schön" finde ich die Löungen alle nicht. Ich würde es folgendermaßen angehen:
Zunächst finde ich einen Typ "Winkel" sinnvoll. Das vermeidet gleiche Signaturen und ermöglicht dir auch die Anwendung in anderen geometrischen Figuren. Außerdem kann sehr leicht Vergleichbarkeit mittels equals hergestellt werden, ohne bei doubles jedesmal manuell die Ungenauigkeiten beachten zu müssen. (Beispiel in der Mainmethode)
Also könnte die Winkelklasse z.B. so aussehen:
Java:
package dreieck;
public class Winkel {
private double grad;
public Winkel(double grad){
setGrad(grad);
}
public double getGrad() {
return grad;
}
public void setGrad(double grad) {
this.grad = grad;
}
@Override
public String toString() {
return "Winkel [grad=" + grad + "]";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
long temp;
temp = Double.doubleToLongBits(grad);
result = prime * result + (int) (temp ^ (temp >>> 32));
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Winkel other = (Winkel) obj;
if (Double.doubleToLongBits(grad) != Double
.doubleToLongBits(other.grad))
return false;
return true;
}
}
Somit ist sicher gestellt, dass gleiche Winkel den gleiche Hash haben und auch per equals immer das korrekte Ergebnis liefern. Dem Winkel ist es dabei egeal, ob er in einem Dreieck oder Viereck benutzt wird, was vielleicht später Arbeit spart.
----
Für das Dreieck würde ich nur die Dinge als Variablen definieren, die zur exakten Bestimmung eines rechtwinkligen Dreieckes mindestens nötig sind. Ich wähle zwei Seiten. Der Rest lässt sich daraus ableiten. So werden Abhängigkeiten in den Membern vermieden und Intergrität ist leichter gewährleistet.
Eine statische Methode "berechneSeite" übernimmt bei Angabe eines Winkels im Konstruktor das Auflösen in eine Seite.
Andersherum gibt "getAplha" nach Berechnung den Winkel aus.
Java:
package dreieck;
public class Dreieck {
private double a;
private double b;
public Dreieck(double a, double b){
setA(a);
setA(b);
}
public Dreieck(double a, Winkel alpha){
this(a, seiteBerechnen(a, alpha));
}
private static double seiteBerechnen(double a, Winkel alpha) {
double ergebnis;
//berechne die Seite
ergebnis = 2.3;
return ergebnis;
}
public Winkel getAlpha(){
//berechne alpha
return new Winkel(30);
}
public static void main(String[] args) {
Dreieck dreiEck1 = new Dreieck(100, 100);
Dreieck dreiEck2 = new Dreieck(100, new Winkel(30));
System.out.println(dreiEck1.getAlpha().equals(dreiEck2.getAlpha()) ? "gleich" : "ungleich");
}
public double getA() {
return a;
}
public void setA(double a) {
this.a = a;
}
public double getB() {
return b;
}
public void setB(double b) {
this.b = b;
}
}
Die Werte sind hier aus Gründen der Faulheit fest codiert.
So würde ich mich zunächst am wohlsten fühlen. Da hier (und übrigends auch in den anderen Beispielen) niemals klar ist, welcher Winkel und welche Seite (Kathete, Ankathete oder Hypothenuse) nun genau gemeint ist und die Berechnungen daher irgendwie immer schwammig bleiben (und ausserdem gehen dir vermutlich die primitiven Datentypen irgendwann aus oder der Kostruktor ist überfüllt mit Dummies und 100 Zeichen lang), würde ich auch für die Seiten eigene Typen anlegen und den Konstruktor entsprechend überladen, sodass er eben mit jeder Kombination aus Kathete, Ankathete, Hypothenuse, Alpha und Beta usw klar kommt. Dass intern tatsächlich immer nur zwei Seiten deiner Wahl gespeichert werden ist bei der Benutzung der Klasse ja vollkommen transparent.
Die statischen Methoden zur Berechnung des fehlenden Parts sind so ja übrigends auch unabhängig von einem konkreten Dreiecksobjekt zur Dreiecksberechnung benutzbar, was mMn ein weiterer Vorteil ist. Pytagoras kann ja auch sehr gut für Abstandsberechnungen zweier Koordinaten benutzt werden zB. Und dafür ist ein konkretes Dreiecksobjekt überflüssig. Du hast also zusätzlich eine kleine Utilityklasse.
klingt für mich sehr logisch, danke für die Anregungen.
Das heißt also, wir können vorab "Datentypen" erzeugen, die dann später im Konstruktor wiederum als Eingangsparameter genutzt werden können? Dann liest sich das für mich tatsächlich nach der "saubersten" Lösung. Denn so können wir tatsächlich ganz genau sagen, ob und welche Seite bzw. welcher Winkel als Parameter für unsere Konstruktoren "reinkommt".
Genau das ist die Idee dahinter. Für mich klingt das nach einer soliden Lösung im Sinne der OOP. Ich bin jetzt aber auch kein Java-Geek und selbst interessiert daran, was die Anderen dazu sagen.
Ach ja, bei den Seiten könnte man es sich recht einfach machen. Da ja eine Seite immer nur eine Länge hat, könnte man ein mal eine abstrakte Elternklasse mit diesem Attribut erstellen und dann die konkreten Klassen "Kathete" usw davon ableiten.
Allerdings hätten die abgeleiteten Klassen dann keinerlei eigene Eigenschaften sondern nur die geerbten. Es ginge als ausschliesslich um den Typ. Und da bin ich mir nicht sicher ob das sauber ist, oder ob es da nicht eventuell eine bessere Lösung gibt.