Thema: Vererbung Ober-/Unterklassen

  • Themenstarter Themenstarter David Nienhaus
  • Beginndatum Beginndatum
D

David Nienhaus

Gast
Hallo liebe Leute,

ich bin gerade dabei eine Klasse "Raum" in zwei Unterklassen "Geschaeft" und "StandartRaum" zu unterteilen. Was ich noch nicht ganz verstehe ist folgendes:

Ich möchte in meiner Hauptklasse überprüfen, ob ein Raum ein Geschäft bzw ein StandartRaum ist. Gibts dafür eine elegante Lösung?

Liebe Grüße
David
 
Ich glaube ich habe schon selbst die Antwort gefunden :

if(aktuellerRaum.getClass() == Geschaeft.class){

🙂
 
Java:
public abstract class Room
{
}
public class BedRoom extends Room
{ 
}
public class WorkRoom extends Room
{  
}

public class Rooms
{
  
    public static void main(String[] args)
    {
        Room bedroom = new BedRoom();
        Room workroom = new WorkRoom();
      
        whoAreYou(bedroom);
        whoAreYou(workroom);
      
    }
  
    static void whoAreYou(Room room)
    {
        if(room instanceof BedRoom)
        {
            System.out.println("Here you can sleep");
        } else if(room instanceof WorkRoom)
        {
            System.out.println("Here you can work");
        }
    }
  
}
/** Output */
Here you can sleep
Here you can work
instanceof
 
Die Antwort, wie man das technisch löst, hast Du ja bereits bekommen.

Einmal unabhängig davon stellen sich zwei Fragen:
  1. Aus welchem Grund möchtest Du das in der Raumklasse machen?
  2. Inwiefern unterscheiden sich Geschäfts- und Standardraum voneinander?
 
Also mit Hauptklasse war nicht die Raumklasse gemeint. Also vielleicht einmal zum Kontext des Programms:
Es ist ein sehr einfaches textbasiertes Spiel mit verschiedenen RÄumen zwischen denen man sich bewegen kann.
In meiner Hauptklasse "Spiel" möchte ich nun abfragen, ob der aktuelle Raum ein Geschäft oder ein normaler Raum ist. Der Unterschied soll erstmal nur darin bestehen, dass ein Geschäft einen Kassierer hat und dass ein Geschäft "verschlossen" sein kann, sobald man einen Gegenstand aufnimmt und ihn noch nicht bezahlt hat.
 
Das Spiel an sich läuft schon, jetzt möchte ich aber versuchen Vererbungen zu implementieren und alles auf mehere Unterklassen aufzuteilen, da wir dies momentan in der Vorlesung haben.
 
Schön, sowas in der Richtung habe ich mir schon gedacht 🙂

Das Problem ist, dass Du in der Spielklasse jetzt bei jeder Gelegenheit überprüfen müsstest, vom welchem Typ der Raum ist. Das ist nicht Sinn und Zweck der Geschichte.

Überlege Dir, wie Du das ohne "instanceof" hinbekommen könntest.

Mal ein Beispiel:
Java:
class Form { }
class Rechteck extends Form { ... }
class Kreis extends Form { ... }
class Spielfeld {
    void zeichne(Form f) {
        ...
        if (f instanceof Rechteck) { zeichneRechteck((Rechteck) f); }
        else if (f instanceof Kreis) { zeichneKreis((Kreis) f); }
    }
    ....
}

Das ist nicht gut: wenn Du jetzt eine Klasse Dreieck einführst, musst Du die Klasse Spielfeld anpassen und wieder auf f instanceof Dreieck prüfen.

Alternativ könnte die Form sich selbst zeichnen, dann sähe die Methode in Spielfeld nur noch so aus:
Java:
    void zeichne(Form f) {
        ...
        f.zeichne();
    }
 
mihe7 hat's schon ganz gut beschrieben.
Die Oberklasse sollte nur gemeinsame Eigenschaften besitzen, alles andere muss sollte in den Unterklassen passieren, sonst bräuchte man jene ja gar nicht. Um genau zu sein, sollte die Oberklasse gar nicht wissen, dass es die Unterklassen überhaupt gibt.
Wenn man so vorgehen will, wär's besser, der Oberklasse eine Eigenschaft (EnumVariable als Beispiel) zuzuweisen, welche beschreibt, worum es sich handelt.

Überlege auch, Logik von Daten zu trennen. Ein externer Handler wäre gerade bei einem Spiel wahrscheinlich sinnvoller.
 
Ein anschauliches Beispiel aus dem Real Life: Eine Musikdatei muss sich nicht selbst abspielen können, es ist sinnvoll, dass dies eine spezialisierte Verarbeitungsstation erledigt, damit nicht jede Musikdatei selbst den Code des Betriebssystems und alle Soundkartenschnittstellen kennen muss.

Ein anderes Beispiel:
(Graphics)g.draw(new Rectangle(...))
Das Rectangle selbst beinhaltet keine Zeichenlogik und das ist auch gut so.
Es erfüllt aber einen Interface-Standard (Shape), der von g gezeichnet werden kann.

Wie wäre es mit Zinsberechnung? Soll ein Bankkonto oder gar der aktuelle Kontostand in der Lage sein, Zinsen selbst auszurechnen? Auf die Idee würde keiner kommen und das zu Recht.

Das "sich selber zeichnen" habe ich jahrelang so praktiziert - bis das Ausgabeziel ein anderes wurde, dann ging der Schuss nach hinten los. Auch sollten die Elemente sich selbst anhand von Parametern verarbeiten, selbst dafür sorgen, dass die Zieldaten erzeugt wurden - das hat sich im Nachhinein ebenfalls als Fehler erwiesen.
Man verliert enorm viel Flexibilität, wenn komplexe Vorgänge innerhalb der Datenklassen passieren und das Ganze wird sehr schwer zu warten, die Schnittstellen werden zu unübersichtlich. Einfache Vorgänge, welche wirklich nur die Daten selbst betreffen, können natürlich rein. Aber die Verarbeitung der Daten - vor allem mit verschiedenen Zielen - sollte außerhalb passieren.
 
Ein anderes Beispiel:
(Graphics)g.draw(new Rectangle(...))
Das Rectangle selbst beinhaltet keine Zeichenlogik und das ist auch gut so.
Es erfüllt aber einen Interface-Standard (Shape), der von g gezeichnet werden kann.
Das amüsante daran, dass du @mihe7 zustimmst, dann aber genau das Gegenteil ergänzt und ihr auch noch quasi das gleiche Beispiel für gegenteilige Argumente bringt, seh grad wohl nur ich?



Ich würde bei allen drei Punkten nur bedingt zustimmen, dass da die zu dem Objekt gehörende Login von den Daten getrennt wird.

Die ersten zwei Punkte sind je nach Sichtweise Trennung UI - Model.
Beim zweiten Punkt ist es außerdem einfach sinnvolles Refactoring. Es gibt im Zeichnen keine Unterscheid zwischen allen "normalen" Objekten, von daher ist das völlig logisch, das auszulagern.

Zinsen berechnen ist für mich auch nur in Teilen Aufgabe eines Kontos, und da es vermutlich nicht "die eine" Zinsberechnung geben wird, würde man das auch auslagern und einfach das Strategypattern nutzen.

Das "sich selber zeichnen" habe ich jahrelang so praktiziert - bis das Ausgabeziel ein anderes wurde, dann ging der Schuss nach hinten los. Auch sollten die Elemente sich selbst anhand von Parametern verarbeiten, selbst dafür sorgen, dass die Zieldaten erzeugt wurden - das hat sich im Nachhinein ebenfalls als Fehler erwiesen.
Man verliert enorm viel Flexibilität, wenn komplexe Vorgänge innerhalb der Datenklassen passieren und das Ganze wird sehr schwer zu warten, die Schnittstellen werden zu unübersichtlich. Einfache Vorgänge, welche wirklich nur die Daten selbst betreffen, können natürlich rein. Aber die Verarbeitung der Daten - vor allem mit verschiedenen Zielen - sollte außerhalb passieren.
Das Thema passt besser hierzu: OO ist gescheitert
Das was du beschreibst, hat nicht mehr viel mit OO zu tun - was nicht ohne Grund ziemlich verbreitet ist. Die Probleme die du beschreibst, klingen eher nach Architekturproblemen, die man mit besserem, und trotzdem Objekt-Orientiertem, Design besser hätte lösen können.
 
Zuletzt bearbeitet:
Das amüsante daran, dass du @mihe7 zustimmst, dann aber genau das Gegenteil ergänzt und ihr auch noch quasi das gleiche Beispiel für gegenteilige Argumente bringt, seh grad wohl nur ich?
Ja, ich denke schon. Nach wie vor gilt: Die Oberklasse sollte nach Möglichkeit nichts von den Unterklassen wissen.
Und ja, das sind Beispiele dafür, wo ein sich selbst verwaltendes Objekt nicht gut funktioniert, danach hast du ja gefragt.
Wenn du Beispiele willst, wo es funktioniert, darfst du gerne danach fragen.
 
Ja, ich denke schon. Nach wie vor gilt: Die Oberklasse sollte nach Möglichkeit nichts von den Unterklassen wissen.
Und ja, das sind Beispiele dafür, wo ein sich selbst verwaltendes Objekt nicht gut funktioniert, danach hast du ja gefragt.
Die Oberklasse muss nie was über die Unterklassen wissen - in keinem würde aber das Vorhandensein bze nicht-Vorhandensein einer Oberklasse irgendwas ändern 😉
 
Das amüsante daran, dass du @mihe7 zustimmst, dann aber genau das Gegenteil ergänzt und ihr auch noch quasi das gleiche Beispiel für gegenteilige Argumente bringt, seh grad wohl nur ich?
Darin sehe ich keinen Widerspruch: vgl. Graphics#draw(Shape) und Component#paint(Graphics).

Die Formulierung "Trennung von Daten und Logik" ist auf Objektebene allerdings ungünstig, denn das hört sich so an, als dürfte ein Objekt, das eine Funktionalität anbietet, keine Felder enthalten.
 
Darin sehe ich keinen Widerspruch: vgl. Graphics#draw(Shape) und Component#paint(Graphics).
Hm, in "Form [kann] sich selbst zeichnen" und "[Form] beinhaltet keine Zeichenlogik" ist kein Widerspruch?

Und in dem Fall: Shape ist eine geometrische Form, zu deren Logik gehört es halt auch nicht, gezeichnet zu werden - im Gegensatz zu Component, zu dessen Logik das Zeichen durchaus gehört, und welchem es deshalb überlassen wird 😉
 
Mir ging es lediglich darum, mit einem Beispiel zu zeigen, wie der Fragesteller ohne instanceof auskommt. Ich hätte die Klassen auch einfach A, B und C nennen können und die Methoden doSomethingWithB, doSomethingWithC bzw. domeSometing.

@Neumi5694 hat ergänzt, dass er (=Fragesteller) auch zwischen Daten und Logik trennen soll. Das sind völlig verschiedene Dinge.

Hätte ich statt Form "Component" verwendet, dann stünden jetzt die beiden Aussagen "Component kann sich selbst zeichnen" und "Shape beinhaltet keine Zeichenlogik" im Raum.

Shape ist eine geometrische Form, zu deren Logik gehört es halt auch nicht, gezeichnet zu werden
Ich denke, das sollte "Trennung von Daten und Logik" auch aussagen.
 

Zurück
Oben