Eddie: Abstrakte Klassen - wozu?

Hallo allerseits,

abstrakte Klassen (oder auch "Interfaces") sind ja offenbar ganz wichtig - und wie sie funktionieren, habe ich (hoffentlich!) auch soweit verstanden. Aber warum sie so wichtig sind, ist mir ein Rätsel!

Im Grunde lässt sich damit ja eine Schnittstelle definieren, die dann von Objekten ähnlicher Art implementiert wird. Also bspw. eine abstrakte Klasse "Farbinformationen" mit der Eigenschaft "Farbe" und den Methoden "setFarbe" und "getFarbe".
Zwei Klassen "Auto" und "Orange" könnten diese abstrakte Klasse dann verwenden, richtig?

Trotzdem muss ich doch nach wie vor fuer "Orange" und "Auto" den Code komplett entwickeln! Arbeit spare ich mir also schon mal nicht! Was also ist der große Vorteil???

Danke für eure Hilfe,
Eddie

--
Old men and far travelers may lie with authority.
  1. Hi,

    also darüber kann man sich sicherlich sehr ausführlich auslassen, ich mach's mal relativ kurz. Du musst zunächst mal den Unterschied zwischen Interfaces und abstrakten Klassen wahren.
    Ein Interface beschreibt - ohne jegliche Implementierung - wie eine Klasse _mindestens_ auszusehen hat. Sämtliche "Unterklassen" müssen sich daran halten, müssen also alle Methoden in einer für sie angemessenen Methode implementieren.
    Eine abstrakte Klasse enthält idR. bereits einige implementierte Methoden, die nämlich für alle Unterklassen gleich ablaufen werden (siehe Beispiel unten), verhält sich ansonsten wie ein Interface.

    Der Vorteil der ganzen Sache ist der: Wenn du eine Variable deklarierst und sagst "du bist vom Typ meinInterface", dann kannst du auf diese Variable sämtliche Objekte legen, die dieses Interface erfüllen. Ein klassisches Beispiel wäre z.B.:
    abstr. Klasse Person { getName(), getVorname(), getGeburtstag() }
    Klasse Mitarbeiter:Person { getPersonalNummer(), getGehalt() }
    Klasse Kunde:Person { getKundennummer(), getUmsatz() }

    Du schreibst 1x die Methoden von Person, nämlich in der abstrakten Klasse und für Mitarbeiter sowie Kunde schreibst du nun nur noch die speziellen Methoden. Wenn du nun eine Anzeige aller dir bekannten Personen machen willst, so definierst du dir eine Variable vom Typ Person. Du kannst sowohl Kunden als auch Mitarbeiter darauf ablegen, deine Anwendung hat in jedem Fall die Gewissheit, dass sie Namen, Vornamen und Geburtstag ermitteln kann, egal was das Objekt sonst noch kann.

    Auf diese Art kannst du auch Plugin-basierte Systeme aufbauen: Du gibst vor, was diese Klasse nach außen ausmachen soll, jede Klasse die von sich behauptet das zu können kannst du definitiv auch so ansteuern.

    Beispiel aus unserer Anwendung:
    Für sämtliche zentrale Konzepte innerhalb der Anwendung sind Interfaces vorgegeben. Die Teile der Anwendung definieren ihre Variablen immer nur auf Basis dieser Interfaces und nutzen auch nur die Methoden des Interfaces.
    Dann gibt es die Implementierungsebene, in der die ganzen Interfaces mit Leben gefüllt werden.
    Wenn das System startet liest es eine Konfiguration aus und beschließt dann, welche Implementierung aktuell gültig ist und verwendet werden soll. Du kannst also die Implementierung austauschen, ohne an anderen Stellen an den Code ranzumüssen, weil die Klassen die damit arbeiten gar nichts von der Implementierung wissen sondern nur das Interface kennen. Unsere aktuelle Anwendung nutzt das z.B. für Implementierung mit Java1.4 und Implementierung mit neueren Java-Versionen. An anderer Stelle scheren wir verschiedene Event-Logger über einen Kamm, egal ob die für Bildschirm oder Datei geschrieben sind, wir wissen man kann .log(x, y, z) aufrufen.

    MfG
    Rouven

    --
    -------------------
    ss:) zu:) ls:& fo:) de:< va:{ ch:? sh:) n4:( rl:? br:$ js:| ie:) fl:(
    1. 'Nabend.

      abstr. Klasse Person { getName(), getVorname(), getGeburtstag() }
      Klasse Mitarbeiter:Person { getPersonalNummer(), getGehalt() }
      Klasse Kunde:Person { getKundennummer(), getUmsatz() }

      Du schreibst 1x die Methoden von Person, nämlich in der abstrakten Klasse und für Mitarbeiter sowie Kunde schreibst du nun nur noch die speziellen Methoden.

      Das halte ich allerdings für kein besonders gutes Beispiel für abstrakte Klassen. Du demonstrierst damit lediglich, wie eine Kindklasse alle Eigenschaften seiner Elternklasse unverändert erbt und in zusätzlichen Methoden Funktionalität hinzufügt. Dazu muß die Elternklasse nicht notwendigerweise abstrakt sein.

      Deutlicher wird der Nutzen abstrakter Klassen meiner Meinung nach erst in Kombination mit abstrakten Methoden (das paßt auch gut zum von dir erwähnten Plugin-Prinzip). Standardbeispiel geometrischer Gebilde, die z.B. auf dem Bildschirm gezeichnet werden sollen:

      abstrakte Klasse Shape {
        color
        setColor() {
          // hier für alle Shapes implementieren
          ...
        }
        abstrakte Methode redraw() {
          // muß jeder konkrete Shape selber implementieren
        }
      }

      abgeleitete Klasse Circle {
        redraw() {
          // hier Implementierung, um einen Kreis zu zeichen
          ...
        }
      }

      abgeleitete Klasse Rectangle {
        redraw() {
          // hier Implementierung, um ein Rechteck zu zeichen
          ...
        }
      }

      usw.

      Angenommen, alle dazustellenden Gebilde befinden sich in einer Liste, die Shapes speichert. Dann kann beim Neuzeichnen des Bildschirms einfach für jeden Shape die Methode redraw() aufgerufen werden. Jeder konkrete Shape (Circle, Rectangle usw.) zeichnet sich dann mit der richtigen Methode quasi selbst. Das deckt sich ja soweit mit Rouvens Erklärung. :)

  2. Hallo,

    Trotzdem muss ich doch nach wie vor fuer "Orange" und "Auto" den Code komplett entwickeln! Arbeit spare ich mir also schon mal nicht! Was also ist der große Vorteil???

    Ähm warum solltest du das? Ist doch egal ob das Objekt "Auto" oder "Orange" gefärbt werden soll, wird ja beides genau so gemacht. Aber ich glaube das Beispiel ist ein bischen blöd.

    Stell dir zum Beispiel eine abstrakte Klasse vor, die an der Börse Aktien einkaufen kann. Man sagt ihr nur wer die Aktien kaufen will, welche und wie viele. Somit kann jeder, von den hunderten von Leuten (Objekten) die dort arbeiten die Klasse zum einkaufen seiner eigenen Aktien benutzen, ohne sie irgendwie ändern zu müssen.

    Wenn sich dann irgendwann die Art ändert wie man eine Aktie kauft - dass man zum beispiel erst noch irgendwo nen Stempel holen muss - dann erweitert man nur diese eine Klasse und alle Leute (Objekte) können auf einmal von diesem Upgrade profitieren, ohne dass jeder seinen eigenen Quellcode anpassen müsste.

    Grüße
    Jeena Paradies

  3. Moin!

    abstrakte Klassen (oder auch "Interfaces") sind ja offenbar ganz wichtig - und wie sie funktionieren, habe ich (hoffentlich!) auch soweit verstanden. Aber warum sie so wichtig sind, ist mir ein Rätsel!

    So sehe ich das auch, so genannte „virtuelle Klassen“ wie in C++ sind IMHO zweckmäßiger, auch wenn formale Mängel (die wohl eher für Interfaces sprechen) dagegen sprechen.

    Im Grunde lässt sich damit ja eine Schnittstelle definieren, die dann von Objekten ähnlicher Art implementiert wird. Also bspw. eine abstrakte Klasse "Farbinformationen" mit der Eigenschaft "Farbe" und den Methoden "setFarbe" und "getFarbe".
    Zwei Klassen "Auto" und "Orange" könnten diese abstrakte Klasse dann verwenden, richtig?

    So sieht es aus. Mit der Angabe, dass diese Klasse ein bestimmtes Interface implementiert, musst du alle geforderten Methoden in der Klasse definieren.

    Trotzdem muss ich doch nach wie vor fuer "Orange" und "Auto" den Code komplett entwickeln! Arbeit spare ich mir also schon mal nicht! Was also ist der große Vorteil???

    Genau das frage ich mich auch. In C++ mit den genannten virtuellen Klassen definierst du eine Klasse komplett mit Methoden (inklusive Code) und Daten, gibst jedoch vor, dass bestimmte Methoden von Subklassen selbst implementiert werden, d.h. nur diese paar speziellen Methoden musst du jedesmal neu schreiben, aber nicht alles. Das ganze sieht dann in etwa so aus:

      
    class VirtuelleBasis {  
    public:  
        // Konstruktor  
        VirtuelleBasis(const int initWert) : einWert(initWert) {}  
        // virtueller Destruktor, bei Subklassen sinnvoll  
        ~VirtuelleBasis();  
      
        // Diese Methode ist virtuell, die Angabe = 0 signalisiert,  
        // dass sie überschrieben (implementiert) werden muss  
        // („NULL-Pointer in der Tabelle virtueller Methoden“)  
        virtual interfaceMethode() = 0;  
      
    protected:  
        int einWert;  
        double nochEiner;  
    };  
      
    class Abgeleitet : public VirtuelleBasis {  
        // enthält alle Daten und Methoden der Basisklasse  
        // braucht nur folgendes zu implementieren:  
        virtual interfaceMethode() {  
             // …  
        }  
    };  
    
    

    Viele Grüße,
    Robert

  4. Hallo Eddie,

    Interfaces ermöglichen es, eine Schnittstelle zu definieren, die von beliebigen Klassen implementiert werden kann. Der Vorteil ist nicht, dass die Implementierung der Klassen irgendwie einfacher würde, sondern dass man Algorithmen schreiben kann, die nur eine bestimmte Schnittstelle benötigen ohne dass man sich festlegen muss, um welche Klasse es sich handelt.

    Ein einfaches Beispiel ist das Iterface "Comparable" das eine Methode hat, um zwei Objekte zu vergleichen. Dadurch kann man Algorithmen und Datenstrukturen (Sortierte Listen u.ä.) schreiben, die für alle Klassen funktionieren, die eine Schnittstelle zum Vergleichen vorsehen.

    Die abstrakte Klasse hat vom Zweck her eigentlich wenig mit dem Interface gemein. Sie bietet die Möglichkeit, aus mehreren konkreten Klassen gemeinsame Teile herauszuziehen, ohne das dabei etwas entstehen muss, was wirklich instantierbar wäre. Abstrakte Klassen werden oft verwendet um ein Grundgerüst für die Implementierung zu bieten.
    Beispiel davon ist die Klasse AbstractList, die für fast alle Methoden des List-Interfaces implementierungen angibt, außer für ein paar sehr grundlegende zum direkten lesen und schreiben der Liste.

    (Die erwähnten Beispiele gehören zur Java-API, aber auch ohne diese zu kennen sollten sie verständlich sein)

    Ein (möglicherweise) schlechtes Beispiel für eine (abstrakte) Klasse ist das Beispiel von Rouven mit der abstrakten Klasse Person und den davon abgeleiteten Klassen Kunde und Mitarbeiter.
    Bei solchen Entwürfen muss man aufpassen, dass man nicht in die Verlegenheit  kommt, Personen zu haben, die Kunden und Mitarbeiter sind oder ihren Status sogar ändern. Es kann in solchen Fällen sinnvoller sein, Personen eine Liste von Rollen zuzuordnen. Rolle könnte man dann aber wieder als abstrakte Klasse umsetzen (oder auch als Interface, wenn man die Implementierung da erst mal raushalten will).

    Grüße

    Daniel

  5. Hi,

    was ist denn der Unterschied zwischen einer Schnittstelle und einer Klasse? (Wenn Du das gecheckt hats, dann hast Du auch Deine Frage beantwortet.)

    Gaspado

  6. Hallo nochmal,

    danke sehr, ihr habt mir sehr geholfen! Vor allem das Shape-Beispiel von Blaubart, war doch sehr anschaulich, auch die Java-Analogien von Daniel!
    Jetzt werde ich mich mal hinsetzen und mir ueberlegen, an welchen Stellen das bei mir Sinn machen koennte :-)

    Danke euch allen,
    Eddie

    --
    Old men and far travelers may lie with authority.