Objekte verknüpfen - Java (was kann ich noch verbesser?)

yxy

Lieutenant
Registriert
Juli 2014
Beiträge
556
Hallo zusammen,

beschäftige mich gerade etwas mit dem Verknüpfen von Objekten in Java.
Habe dazu mal paar Beispielklassen geschreiben und versucht diese miteinadner zu verknüpfen.
Was ich euch nun fragen will ist:

1. Gibt es etwas, das ihr elleganter lösen würdet?
2. Was solte man beim Thema Verkmüpfen noch wissen (d.h.was kann ich in meinem Programm noch ausprobieren)?

Code:
package VerknuepfungVonObjekten;

public class MainClass {

    public static void main(String[] args) {
        //3 Kumpels
        Kumpel bernd = new Kumpel("Bernd", "Blond", "Braun", (float) 1.9);
        Kumpel alfon = new Kumpel("Alfon", "Braun", "Grün", (float) 1.8);
        Kumpel lukas = new Kumpel("Lukas", "Grau", "Blau", (float) 2.0);

        //3 Freundinnen
        Freundin bernadett = new Freundin("Bernadett", "Blond", "Braun", (float) 1.6);
        Freundin alfine = new Freundin("Alfine", "Braun", "Grün", (float) 1.7);
        Freundin lukara = new Freundin("Lukara", "Grau", "Blau", (float) 1.5);

        //3 Autos
        Auto autoA = new Auto("VW", "Rot", (float) 2.0);
        Auto autoB = new Auto("Renault", "Grün", (float) 2.0);
        Auto autoC = new Auto("Porsche", "Irischgrün", (float) 2.0);

        //Verkuppeln
        bernd.seineFreundin = bernadett;
        alfon.seineFreundin = alfine;
        lukas.seineFreundin = lukara;

        //Auto andehen
        bernd.seinAuto = autoA;
        alfon.seinAuto = autoB;
        lukas.seinAuto = autoC;

        //Ausgabe Pärchen
        bernd.ausgabePaerchen();
        alfon.ausgabePaerchen();
        lukas.ausgabePaerchen();

        //Bernd ist mit dem Äußeren seiner Feundin nicht zu frieden
        bernd.seineFreundin.schoenheitsOp("Pink", "Weiß", 1);
        bernd.seineFreundin.ausgabeDatenFreundin();
        //Bernd beläd sein Auto
        bernd.seinAuto.beladen((float)0.5);
        bernd.seinAuto.ausgabeDatenAuto();
    }
}

Code:
package VerknuepfungVonObjekten;

public class Kumpel {
    //Objektvariablen
    private String name;
    private String haarFarbe;
    private String augenFarbe;
    private float groesseInM;
    
    public Freundin seineFreundin;   //Darin wird die Referenz auf ein Objekt vom Typ Freundin gespeichert
    public Auto seinAuto;       //Darin wird die Referent auf ein Pbjekt vom Typ Auto gespeichert
    
    //Klassenvariablen
    int anzahlKumpels = 0;

    //Konstruktor
    public Kumpel(String name_, String haarFarbe_, String augenFarbe_, float groesseInM_) {
        name = name_;
        haarFarbe = haarFarbe_;
        augenFarbe = augenFarbe_;
        groesseInM = groesseInM_;
    }
    
    //Methoden
    public void ausgabeDatenKumpel(){
        System.out.println("Er heißt:\t\t" + name);
        System.out.println("Seine Haarfarbe ist:\t" + haarFarbe);
        System.out.println("Seine Augenfarbe ist:\t" + augenFarbe);
        System.out.println("Seine Größe ist:\t" + groesseInM);
    }
    
    public void umTaufen(String neuerName){
        name = neuerName;
    }
    
    public void schoenheitsOp(String neueHaarFarbe, String neueAugenFarbe, float neueGroesse){
        haarFarbe = neueHaarFarbe;
        augenFarbe = neueAugenFarbe;
        groesseInM = neueGroesse;
    }
    
    public void ausgabePaerchen(){
        ausgabeDatenKumpel();
        seineFreundin.ausgabeDatenFreundin();
        seinAuto.ausgabeDatenAuto();
        System.out.println("\n\n\n");
    }
}

Code:
package VerknuepfungVonObjekten;

public class Freundin {

    //Objektvariablen
    private String name;
    private String haarFarbe;
    private String augenFarbe;
    private float groesseInM;

    //Klassenvariablen
    int anzahlVergebenerFrauen = 0;

    //Konstruktor
    public Freundin(String name_, String haarFarbe_, String augenFarbe_, float groesseInM_) {
        name = name_;
        haarFarbe = haarFarbe_;
        augenFarbe = augenFarbe_;
        groesseInM = groesseInM_;
    }
    
    //Methoden
    public void ausgabeDatenFreundin(){
        System.out.println("Seine Freundin ist:\t" + name);
        System.out.println("Ihre Haarfarbe ist:\t" + haarFarbe);
        System.out.println("Ihre Augenfarbe ist:\t" + augenFarbe);
        System.out.println("Ihre Größe ist:\t\t" + groesseInM);
    }
    
    public void umTaufen(String neuerName){
        name = neuerName;
    }
    
    public void schoenheitsOp(String neueHaarFarbe, String neueAugenFarbe, float neueGroesse){
        haarFarbe = neueHaarFarbe;
        augenFarbe = neueAugenFarbe;
        groesseInM = neueGroesse;
    }
}

Code:
package VerknuepfungVonObjekten;

public class Auto {
    //Objektvariablen

    private String marke;
    private String farbe;
    private float gewichtInKg;

    //Klassenvariablen
    int anzahlProduzierterAutos = 0;

    //Konstruktor
    public Auto(String marke_, String farbe_, float gewicht_) {
        marke = marke_;
        farbe = farbe_;
        gewichtInKg = gewicht_;
    }

    //Methoden
    public void ausgabeDatenAuto() {
        System.out.println("Seine Auto ist ein:\t" + marke);
        System.out.println("Autofarbe ist:\t\t" + farbe);
        System.out.println("Autogewicht ist:\t" + gewichtInKg);
    }

    public void umlackieren(String neueFarbe) {
        farbe = neueFarbe;
    }

    public void beladen(float gewichtÄnderung) {
        gewichtInKg += gewichtÄnderung;
    }
}
 
Bin jetzt kein Java Profi
aber wenn du einen Kumpel anlegst
hat der dann nicht immer auch gleich ein Auto und eine Freundin?

glaub mal nicht das das so sein soll, oder?
weiters solltest du bei der Methode Ausgabeparchen irgendwie überprüfen ob er eine Freundin hat oder nicht..

z.B. Freundin ist per Default Gummipuppe und später kann er eine richtige kriegen :)
 
Danke für den Tipp :)
Code:
    public void ausgabePaerchen() {
        ausgabeDatenKumpel();
        if (seineFreundin != null) {
            seineFreundin.ausgabeDatenFreundin();
        } else {
            System.out.println(name + " hat keine Freundin!");
        }
        if (seinAuto != null) {
            seinAuto.ausgabeDatenAuto();
        } else {
            System.out.println(name + " hat kein Auto!");
        }
        System.out.println("\n\n\n");
    }

EDIT: In der Klasse Kumpel lege ich nur eine Variable an, in der später die Referenz der Freundin gespeichert ist. Er hat also am Anfang zwar eine Referenzvariable für Freundin, der ist aber noch keine Freundin zugeordnet. (So habe ich es zumindes verstanden :))
 
Was mir so auf den ersten Blick auffällt:

1) Verkapselung der Felder (Getter/Setter)
2) Gemeinsame Oberklasse Mensch für Kumpel und Freundin (ist dir nicht aufgefallen, dass sie teils gleiche Felder haben?)
3) Builder-Pattern lohnt sich hier schon bei 4 Konstruktorparametern + optionale Felder
4) Was du "verknüpfen" nennst, mal als Fachbegriff: https://de.wikipedia.org/wiki/Dependency_Injection
5) Dir sollte klar sein, dass Bernd weder mit dem Beladen seines Autos noch mit der Schönheits-OP seiner Freundin wirklich was zu tun hat, denn die Instanzmethoden werden auf dritten Objekten aufgerufen und modifizieren diese, ohne dass Bernd davon behelligt wird (durch die fehlende Verkapselung bekommt er nicht einmal mit, dass an seinem Auto oder seiner Freundin rumgefummelt wird).
6) anzahlVergebenerFrauen ist keine Klassenvariable, wenn du sie nicht als static deklarierst.
7) Für Haar- und Augenfarbe enums verwenden.
 
Zuletzt bearbeitet:
Deine "Klassenvariablen" sind einfach nur Instanz/Objektvariablen mit anderer Sichtbarkeit. Außerdem scheinen die nirgends benutzt zu werden.
Und ich meine es ist immer noch Best-Practice Getter und Setter anstatt öffentliche Variablen zu nutzen.

Edit Thumbleweed war schneller...
 
Danke für die Rückmeldungen, werde meinen Code abändern und mich nochmals melden :)
Ergänzung ()

zu: 7) Für Haar- und Augenfarbe enums verwenden.

Wie mache ich das mit der enum Klasse?
Definieren kann ich das ja so:
Code:
package VerknuepfungVonObjekten;

public enum AugenFarbe {
    Grün, Blau, Braun, Rot
}

Aber was mache ich jetzt damit?
EDIT: Habs rausgefunden
 
Zuletzt bearbeitet:
Die Signatur deines Konstruktors so ändern, dass kein String erwartet wird, sondern der Typ AugenFarbe. Dann beim Erzeugen des Objekts z.B. AugenFarbe.Braun reinstecken. Den Typ des Felds der Klasse musst du natürlich auch anpassen.
 
2) Gemeinsame Oberklasse Mensch für Kumpel und Freundin (ist dir nicht aufgefallen, dass sie teils gleiche Felder haben?)

Stimmt, vieles ist gleich. Aber dennoch sind die Klassen doch nicht komplett identisch.
Wie bekomme ich dann die Felder weg bzw. hinzu?
 
Du erzeugst eine "abstract" Klasse mit den Gemeinsamkeiten und lässt Kumpel und Freundin davon erben. Neben gemeinsamen Feldern wandern da auch gemeinsame Methoden rein. Ob die Implementierung da auch landet, musst du im Einzelfall selbst entscheiden. Auch Methoden können abstract sein.
 
So ganz funktioniert es noch nicht :/

Code:
package VerknuepfungVonObjekten;

public abstract class Mensch {
    
    //Objektvariablen
    private String name;
    private String haarFarbe;
    private AugenFarbe augenFarbe;
    //private String augenFarbe;
    private float groesseInM;
    
    //Methoden
    public void umTaufen(String neuerName){
        name = neuerName;
    }
    
    public void schoenheitsOp(String neueHaarFarbe, AugenFarbe neueAugenFarbe, float neueGroesse){
        haarFarbe = neueHaarFarbe;
        augenFarbe = neueAugenFarbe;
        groesseInM = neueGroesse;
    }
}

Code:
package VerknuepfungVonObjekten;

public class Freundin extends Mensch {
    //Vererbung


    //Klassenvariablen
    static int anzahlVergebenerFrauen = 0;

    //Konstruktor
    public Freundin(String name_, String haarFarbe_, AugenFarbe augenFarbe_, float groesseInM_) {
        name = name_;
        haarFarbe = haarFarbe_;
        augenFarbe = augenFarbe_;
        groesseInM = groesseInM_;
    }
    
    //Methoden
    public void ausgabeDatenFreundin(){
        System.out.println("Seine Freundin ist:\t" + name);
        System.out.println("Ihre Haarfarbe ist:\t" + haarFarbe);
        System.out.println("Ihre Augenfarbe ist:\t" + augenFarbe);
        System.out.println("Ihre Größe ist:\t\t" + groesseInM);
    }
    

}
 
In der klasse Mensch fehlt der Konstruktor. Dieser muss dann in Freundin per super(...) als erster Befehl in deren Klasse aufgerufen werden.
Für ausgabeDatenFreundin brauchst du dann auch die schon angesprochenen Getter.
 
Also so in Mensch:

Code:
package VerknuepfungVonObjekten;

public abstract class Mensch {
    
    //Objektvariablen
    private String name;
    private String haarFarbe;
    private AugenFarbe augenFarbe;
    //private String augenFarbe;
    private float groesseInM;
    
    //Konstruktor
    public Mensch(String name_, String haarFarbe_, AugenFarbe augenFarbe_, float groesseInM_) {
        name = name_;
        haarFarbe = haarFarbe_;
        augenFarbe = augenFarbe_;
        groesseInM = groesseInM_;
    }
    
    //Methoden
    public void umTaufen(String neuerName){
        name = neuerName;
    }
    
    public void schoenheitsOp(String neueHaarFarbe, AugenFarbe neueAugenFarbe, float neueGroesse){
        haarFarbe = neueHaarFarbe;
        augenFarbe = neueAugenFarbe;
        groesseInM = neueGroesse;
    }
}
Ergänzung ()

In Freundin kann ich dann den Konstruktor löschen?
 
Nein, der Konstruktor in Freundin muss bleiben. Du musst die Variablen dann an die abgeleitete Klasse per super() durchreichen.
 
Code:
package VerknuepfungVonObjekten;

public class Freundin extends Mensch {
    
    //Klassenvariablen
    static int anzahlVergebenerFrauen = 0;

    //Konstruktor
   public Freundin(String name_, String haarFarbe_, AugenFarbe augenFarbe_, float groesseInM_) {
       super(name_, haarFarbe_, augenFarbe_, groesseInM_); //
    }
    
    //Methoden
    public void ausgabeDatenFreundin(){
        System.out.println("Seine Freundin ist:\t" + name);
        System.out.println("Ihre Haarfarbe ist:\t" + haarFarbe);
        System.out.println("Ihre Augenfarbe ist:\t" + augenFarbe);
        System.out.println("Ihre Größe ist:\t\t" + groesseInM);
    }
    

}

? Bin gerade etwas verwirrt xD
 
Weil du in ausgabeDatenFreundin nicht auf "name" zugreifen kannst? Guck dir bitte das Prinzip der Getter und Setter an.
 
Ich glaube jetzt habe ich es:

Code:
package VerknuepfungVonObjekten;

public class Freundin extends Mensch {
    
    //Klassenvariablen
    static int anzahlVergebenerFrauen = 0;

    //Konstruktor
   public Freundin(String name_, String haarFarbe_, AugenFarbe augenFarbe_, float groesseInM_) {
       super(name_, haarFarbe_, augenFarbe_, groesseInM_); 
    }
    
    //Methoden
    public void ausgabeDatenFreundin(){
        System.out.println("Seine Freundin ist:\t" + Freundin.super.returnName());
        System.out.println("Ihre Haarfarbe ist:\t" + Freundin.super.returnHaarFarbe());
        System.out.println("Ihre Augenfarbe ist:\t" + Freundin.super.returnAugenFarbe());
        System.out.println("Ihre Größe ist:\t\t" + Freundin.super.returnGroesse());
    }
}
Ergänzung ()

Gut, habe dann alles bis auf Punkt: 3) Builder-Pattern lohnt sich hier schon bei 4 Konstruktorparametern + optionale Felder
umgesetzt.

Was das bringen soll, verstehe ich momentan aber noch nicht.
Habe folgendes Beispiel gefunden:

Code:
public static final class Builder {
        private int size;
        private int type;
        private int colour;
 
        private Builder() {}
 
        public Builder withSize(int size) {
            this.size = size;
            return this;
        }
 
        public Builder withType(int type) {
            this.type = type;
            return this;
        }
 
        public Builder withColour(int colour) {
            this.colour = colour;
            return this;
        }
 
        public Wheel build() {
            return new Wheel(this);
        }
    }

Code:
public class Wheel {
 
    private int size;
    private int type;
    private int colour;
 
    private Wheel(Builder builder) {
        setSize(builder.size);
        setType(builder.type);
        setColour(builder.colour);
    }
 
    public static Builder newBuilder() {
        return new Builder();
    }
 
    public static Builder newBuilder(Wheel copy) {
        Builder builder = new Builder();
        builder.size = copy.size;
        builder.type = copy.type;
        builder.colour = copy.colour;
        return builder;
    }
...}
 
Meiner Meinung nach sollte die Methode ausgabeDaten heißen und als abstract in der Oberklasse deklariert sein. Sie wird dann in der Klasse Freundin und Kumpel eben spezifisch implementiert. Warum das sinnvoll ist, verstehst du erst später, wenn du dir angewöhnst, bei der Übergabe von Referenzen mit dem minimal ausreichenden Interface zu arbeiten, statt mit konkreten Implementierungen.

Um auf Felder/Methoden (also auch getter/setter) der Oberklasse zugreifen zu können, müssen diese mindestens protected oder eben public deklariert sein. public aber nur, wenn es für irgendwas wirklich notwendig ist, denn du kannst in der implementierenden Klasse die Sichtbarkeit nicht mehr reduzieren. Daher immer mit dem Minimum anfangen.

Für den Zugriff musst du übrigens nicht so Umständlich über Klassenname.super gehen. Der Zugriff ist direkt möglich.

Getter bennent man normalerweise "getIrgendwas" und "setIrgendwas".

Edit: zum Builder-Pattern:
Beispiel:
Code:
new Kumpel("Alfon", "Grün", "Grün", (float) 1.8);
Stell dir mal vor, die Klasse wäre nicht von dir, sondern von einem Team-Kollegen geschrieben. Alternativ - du hast den Code ein halbes Jahr nicht angefasst.
Das Problem: Java bietet keine "named parameters" an. In anderen Sprachen kann man schön schreiben
Code:
new Kumpel(name="Alfon", hair="Braun", eyes="Grün", height=(float) 1.8);
Na, erkennst du den Vorteil? am perversesten wird es, wenn Konstruktoren entstehen, die "auch mal null erwarten".
Code:
new Kumpel("Alfon", null, null, true, false, null, (float) 1.8);
Viel Spaß beim Parameter zählen und der Fehlersuche später. :)

Der Builder erlaubt dir, jeden einzelnen Parameter schön lesbar zu setzen. Wenn die Parameter alle gesetzt wurden, kannst du build() aufrufen. Heraus bekommst du eine entsprechend konfigurierte Instanz. Vorteil - in build() kannst du Validierungs-Logik einbauen, d.h. sicherstellen, dass 1) alle Parameter gesetzt wurden und 2) kein Blödsinn gesetzt wurde. Zudem können einige Parameter einfach optional sein (d.h. die Methode zum Setzen wird nicht aufgerufen) und vom Builder mit Default-Werten initialisiert werden.

Der Nachteil - der Aufwand Builder zu schreiben und zu pflegen ist recht hoch. Daher ist es ein abwägen, aber da du hier offensichtlich übst, würd ich dir empfehlen, das mal zu machen. Es gibt auch noch andere Varianten, um die oben genannten Probleme zu adressieren, wie das Bean-Pattern (hiervon rate ich ab) und static factory Methoden, die nicht ganz so mächtig wie ein Builder, aber ein netter Mittelweg für schlichtere Klassen sind.

P.S.: was mir auch noch auffiel - versuche Gleitkommazahlen zu vermeiden. Einfach int benutzen und in cm speichern. Wenn du mal in den Genuss kommst mit floats zu rechnen, wird dir auffallen, warum du das lieber nicht willst.
 
Zuletzt bearbeitet:
"Für den Zugriff musst du übrigens nicht so Umständlich über Klassenname.super gehen. Der Zugriff ist direkt möglich."

Kann ich mir das also so vorstellen, dass die Variablen (durch die Vererbung) in der Klasse Freundin vorhanden sind?
Ergänzung ()

Meiner Meinung nach sollte die Methode ausgabeDaten heißen und als abstract in der Oberklasse deklariert sein.

Wie ändere ich dann aber den auszugebenden String? z.B. "Er heißt..."
 
Zuletzt bearbeitet:
yxy schrieb:
Kann ich mir das also so vorstellen, dass die Variablen (durch die Vererbung) in der Klasse Freundin vorhanden sind?
Ganz genau.
yxy schrieb:
Wie ändere ich dann aber den auszugebenden String? z.B. "Er heißt..."
Deswegen sagte ich, die Oberklasse soll diese Methode nur als "abstract" enthalten. D.h., da steht nur die Signatur, also die Kopfzeile der Methode. Sie hat keinen Rumpf, keine Implementierung, jedenfalls nicht in der Oberklasse.

Benutzt du zufällig Eclipse o.ä.? Falls ja - kommentier mal deine Methode ausgabeDaten in Kumpel aus oder lösche sie. Dann fügst du in die abstract Oberklasse ein
Code:
public abstract void ausgabeDaten();
und dann wird dich der Compiler darauf hinweisen, dass diese Methode doch bitte in Kumpel und Freundin implementiert werden muss. Eclipse hilft dir da und generiert den ganzen Methoden-Körper samt @Override-Annotation.

Das ist dann die Stelle, wo dein Code hinkommt. Es ändert sich also auf den ersten Blick nicht viel, aber das schrieb ich ja zuvor
Warum das sinnvoll ist, verstehst du erst später, wenn du dir angewöhnst, bei der Übergabe von Referenzen mit dem minimal ausreichenden Interface zu arbeiten, statt mit konkreten Implementierungen.
 
"Freundin is not abstract and does not override abstract method ausgabeDaten() in Mensch"
 
Zurück
Oben