steckl: C++: wie mache ich richtiges OO-Design?

Hi,

ich habe zwar schonmal in der Schule etwas ueber OOP gelernt, aber dort wurde uns immer alles vorgekaut und wir haben es dann nur noch in Quellcode umgesetzt, was ja nicht besonders viel eigenleistung erfordert.
Darum habe ich so gut wie keine Ahnung, was objektorientiertes Desing betrifft, da ich ansonsten bis jetzt nur prozedural programmiert habe.

Ich habe mal mit meinem aktuellen Kenntnisstand versucht, einen Taschenrechner, den ich mal in C geschrieben habe, in C++ objektorientiert zu gestalten. Das Programm funktioniert zwar, aber ich denke ich habe noch einige Unschoenheiten drin.

Da der Quellcode wohl etwas zu lang fuer das Forum ist habe ich ihn hier abgelegt.

Wuerde mich ueber Verbesserungsvorschlaege freuen.

mfG,
steckl

  1. Hi!

    Darum habe ich so gut wie keine Ahnung, was objektorientiertes Desing betrifft, da ich ansonsten bis jetzt nur prozedural programmiert habe.

    Kostenloses Buch:

    Praxisbuch Objektorientierung - Professionelle Entwurfsverfahren
    von Bernhard Lahres, Gregor Raýman

    Zum online lesen:
    http://www.galileocomputing.de/openbook/oo/
    Zum Download (knappe 9MB):
    http://download.galileo-press.de/openbook/oo/galileocomputing_oo.zip

    Schöner Gruß,
    rob

    1. Hi,

      Kostenloses Buch:

      Praxisbuch Objektorientierung - Professionelle Entwurfsverfahren
      von Bernhard Lahres, Gregor Raýman

      Zum online lesen:
      http://www.galileocomputing.de/openbook/oo/
      Zum Download (knappe 9MB):
      http://download.galileo-press.de/openbook/oo/galileocomputing_oo.zip

      Danke fuer die Links.
      Hab da mal die ersten zwei Kapitel durchgelesen. Viel davon war bis jetzt Wiederholung fuer mich, aber weiter hinten scheinen doch noch einige interessante Dinge zu kommen, die noch neu fuer mich sind. Ich werd mir das auf alle Faelle die naechsten Tage mal fertig reinziehen.

      mfG,
      steckl

  2. Hi Steckl!
    Also ich denke, wie man "richtig" Objektorientiert Programmiert ist eine Stil und eine Glaubensfrage.

    Hier: http://www.hyperkommunikation.ch/lexikon/objektorientierte_programmierung.htm
    findest du (fast ganz am Ende der Seite, unter "Objekt-orientierte Analyse und Design" eine meiner meinung nach praktikable ausgangsbasis.

    Ich weiß leider nicht mehr, wer dieses Modell erfunden hat. Kann sein, dass es Donald Knuth war?

    Die Idee dahinter ist, dass du das Verhalten von dem was du programmieren willst erst mal (mündlich oder schriftlich) beschreibst. Aus dieser Beschreibung lässt sich dann relativ einfach ein OO Diagramm ableiten.

    (Etwas übertriebenes) Beispiel:
    Ein TASCHENRECHNER hat ein EINGABEFELD und ein DISPLAY. Auf dem EINGABEFELD kann ich die ERSTE ZAHL EINGEBEN und die ZWEITE ZAHL EINGEBEN sowie eine RECHENOPERATION FESTLEGEN (+,-,/,*). Wenn ich auf die ERGEBNIS TASTE DRÜCKE zeigt das DISPLAY das ERGEBNIS AN.

    Die wichtigen wörter habe ich hier hervorgehoben.
    Prinzipiell hast du drei Klassen: Taschenrechner, Eingabefeld und Display. Taschenrechner ist sozusagen das "Überkonstrukt" und wird als Mittler zwischen Display und Eingabefeld fungieren.
    Das Eingabefeld hat drei Methoden: setZahl1(int), setZahl2(int) und setRechenmethode(char). (Anmerkung: Eventuell könntest du die Rechenmethode selbst nochmal zu ner Klasse machen, die eine Methode int calculateResult(int, int) hat und dafür sorgt, dass jede Rechenoperation für den Taschenrechner immer identisch ist, aber das wäre für dieses Beispiel nun *wirklich* zu aufwändig)
    Des weiteren gibt es noch die Methode calculateResult(), die das Ergebnis ausrechnet und die Methode showResult(int) vom Display, dass zu dem Taschenrechner gehört, zu dem auch das Eingabefeld gehört, aufruft.

    Das Problem beim Objektorientierten Design ist, dass es eigentlich so intuitiv ist, dass man gerade anfangs davor zurückschreckt! ;)

    Liebe Grüße,
    Matthias

    1. Hi,

      Erstmal danke, fuer die Versuche mir das Ganze etwas naeher zu bringen, aber ich bin wohl immernoch ziemlich weit von der Loesung entfernt.

      Die Idee dahinter ist, dass du das Verhalten von dem was du programmieren willst erst mal (mündlich oder schriftlich) beschreibst. Aus dieser Beschreibung lässt sich dann relativ einfach ein OO Diagramm ableiten.

      Aber wohl nur wenn man darin schon geuebt ist.

      (Etwas übertriebenes) Beispiel:
      Ein TASCHENRECHNER hat ein EINGABEFELD und ein DISPLAY. Auf dem EINGABEFELD kann ich die ERSTE ZAHL EINGEBEN und die ZWEITE ZAHL EINGEBEN sowie eine RECHENOPERATION FESTLEGEN (+,-,/,*). Wenn ich auf die ERGEBNIS TASTE DRÜCKE zeigt das DISPLAY das ERGEBNIS AN.

      Die wichtigen wörter habe ich hier hervorgehoben.
      Prinzipiell hast du drei Klassen: Taschenrechner, Eingabefeld und Display. Taschenrechner ist sozusagen das "Überkonstrukt" und wird als Mittler zwischen Display und Eingabefeld fungieren.
      Das Eingabefeld hat drei Methoden: setZahl1(int), setZahl2(int) und setRechenmethode(char). (Anmerkung: Eventuell könntest du die Rechenmethode selbst nochmal zu ner Klasse machen, die eine Methode int calculateResult(int, int) hat und dafür sorgt, dass jede Rechenoperation für den Taschenrechner immer identisch ist, aber das wäre für dieses Beispiel nun *wirklich* zu aufwändig)
      Des weiteren gibt es noch die Methode calculateResult(), die das Ergebnis ausrechnet und die Methode showResult(int) vom Display, dass zu dem Taschenrechner gehört, zu dem auch das Eingabefeld gehört, aufruft.

      Ich habe mal versucht, aus deinem Ansatz ein Klassendiagramm zu machen. Es sieht bis jetzt so aus:
      3 Klassen:
      -Rechner:
        Enthaelt die Attribute zahl1:int, zahl2:int, rechenart:char und ergebnis:float)
        Fuer jedes Attribut gibt es noch eine entsprechende get- und set-Methode.

      • Eingabefeld
          Enthaelt die Methoden setZahl1(void):void, setZahle2(void):void, setRechenart(void):void und berechne(void):void
          Die set-Methoden haben keine Argumente, weil die Eingabe auf der Kommandozeile erfolgen soll.
          Die berechne-Methode koennte vielleicht auch in die Rechner-Klasse?
      • Display
          Enthaelt die Methode zeigeErg(void):void

      Eingabefeld und Display enthalten jeweils noch ein Attribut rechner:Rechner, mit dem sie ueber die get-Methoden von Rechner dann auf dessen Attribute zugreifen koennen.
      Ich habe also jeweils eine gerichtete Assotiation von Display, bzw. Eingabefeld auf Rechner (1 zu 1 Beziehung).

      So erscheint es mir aber irgendwie unlogisch. Spricht irgendwas dagegen, das alles in eine Klasse zu packen? Weil ich ja eh nur 1 zu 1 Beziehungen habe sollte man das doch zusammenfassen koennen, oder?

      Vererbung von Rechner auf die beiden anderen Klassen bringt in diesem Fall wohl auch nichts, weil die beiden ja zur selben Instanz des Rechner-Objekts gehoeren?

      Hoffe es ist nicht aussichtslos.

      mfG,
      steckl

      1. Hi!
        Du hast recht, diese Vorgehensweise wäre definitiv übertrieben. Hätte ich das Programmiert, hätte ich auch alles in eine Klasse gepackt.

        Ein wichtiger Aspekt der Objektorientierung ist allerdings auch die Abstraktion und vereinfachung durch Kapselung. Die einzelnen "Codebrocken" werden kleiner und gekaspelter. Gerade bei dem Display müsstest du dich im Taschenrechnercode nicht mehr darum kümmern, wie das angezeigt wird (könnte ja beispielsweise ein sehr aufwändiger zeichenprozess sein, der dem ganzen ein grünlich-LCD-leuchtendes aussehen verleiht. Er sagt dem Display nur, was es anzeigen soll, das Display kümmert sich um den Rest.
        Außerdem könnte ich verschiedene Displays machen, die alle das gleiche tun, das eine allerdings eine "simple" darstellung hat, während andere verschieden verschnörkelt aussehen. Wie das Display dann in deinem Taschenrechner aussieht hängt davon ab, welches Display du initialisierst.
        Die Kapselung würde in dem Fall also auch für eine gute Austauschbarkeit sorgen.

        Soviel dazu! :)

        1. Hi,

          Ein wichtiger Aspekt der Objektorientierung ist allerdings auch die Abstraktion und vereinfachung durch Kapselung. Die einzelnen "Codebrocken" werden kleiner und gekaspelter. Gerade bei dem Display müsstest du dich im Taschenrechnercode nicht mehr darum kümmern, wie das angezeigt wird (könnte ja beispielsweise ein sehr aufwändiger zeichenprozess sein, der dem ganzen ein grünlich-LCD-leuchtendes aussehen verleiht. Er sagt dem Display nur, was es anzeigen soll, das Display kümmert sich um den Rest.

          Könntest du mir sagen, welche Beziehung du dann zwischen Rechner und Display machen würdest?
          Abstraktion ist doch nur bei Vererbung, oder? Macht es Sinn, die ganze Rechner-Klasse auf das Display zu vererben?
          Oder lieber eine gemeinsame Super-Klasse, von der dann beide das Ergebnis erben? aber wie könnte ich dann eine Beziehung zwischen den beiden herstellen?
          Die dritte Möglichkeit wäre eine (gerichtete) Assotiation, wenn ich z.B. im Display in ein Attribut eine Referenz auf das Rechner-Objekt speichere.

          Du siehst, theoretisch hab ich schon Ahnung, welche Möglichkeiten es gibt, aber praktisch hab ich null Plan. ;-)

          Außerdem könnte ich verschiedene Displays machen, die alle das gleiche tun, das eine allerdings eine "simple" darstellung hat, während andere verschieden verschnörkelt aussehen. Wie das Display dann in deinem Taschenrechner aussieht hängt davon ab, welches Display du initialisierst.

          Das leuchtet mir ein. Nur wie mach ich die Beziehung am besten?

          Die Kapselung würde in dem Fall also auch für eine gute Austauschbarkeit sorgen.

          Kapselung heisst in diesem Fall nur die Rechtevergabe mit Public, Private oder Protected, oder?
          Wobei Protected nur bei einer Vererbung Sinn macht, oder?

          Hoffe ich bin nicht zu nervig, aber ich würde das Prinzip einfach gern mal verstehen.

          mfG,
          steckl

          1. Hallo steckl,

            Abstraktion ist doch nur bei Vererbung, oder?

            Abstraktion bedeutet erstmal nur, von einer konkreten Sache zu einer Allgemeinen zu kommen. Vererbung ist eine Möglichkeit, Abstraktion im Programmcode nachzubilden.

            In deinem Taschenrechner-Beispiel könntest du dir z.B. ein Display vorstellen, das grüne Ziffern im LED-Design auf schwarzem Grund hast, eines für eine Textausgabe auf der Konsole und noch weitere. Wenn du jetzt überlegst, welche Eigenschaften alle Displays (und nicht nur die, die du dir gerade vorgestellt hast, sondern alle möglichen Displays) gemeinsam haben, hast du von konkreten Displaytypen auf die abstrakte "Idee" eines Displays abstrahiert.

            Du könntest dies jetzt mit einer Klasse AbstractDisplay von der die verschiedenen Display-Typen als einzelne Klassen abgeleitet werden nachbilden.

            Macht es Sinn, die ganze Rechner-Klasse auf das Display zu vererben?
            Oder lieber eine gemeinsame Super-Klasse, von der dann beide das Ergebnis erben?

            Weder noch. Zwar braucht man beim Abstrahieren nicht bei einem abstrakten Display stehen zu bleiben, sondern kann das beliebig weiter führen, bis man eine Klasse hat, die die gemeinsamen Eigenschaften eines Taschenrechners und eines Displays abbildet. In Java beispielsweise wird dies auf die Spitze getrieben, indem es eine Klasse Object gibt, von der letztendlich alle anderen Klassen abgeleitet sind. Ob es ein C++-Äquivalent dazu gibt, kann ich dir leider nicht sagen.

            Ableitung von einer gemeinsamen Elternklasse ist aber nicht die einzige Beziehung, in der zwei Klassen zueinander stehen können. Wenn die Klasse Taschenrechner beispielsweise ein Display-Objekt als Eigenschaft hat, besteht zwischen den beiden Klassen auch eine Beziehung.

            Die Kapselung würde in dem Fall also auch für eine gute Austauschbarkeit sorgen.
            Kapselung heisst in diesem Fall nur die Rechtevergabe mit Public, Private oder Protected, oder?

            Kapselung bedeutet, dass du die Art und Weise, wie das Programmmodul intern funktioniert versteckst, und dem Programmierer, der dieses Modul einbindet nur sagst, wie er die Methoden aufrufen soll, die er braucht, um dieses Modul zu verwenden. Die Schlüsselwörter private und protected helfen bei der Einhaltung dieser Vorgehensweise, indem Sie verbieten, andere als die für den Zugriff von außen vorgesehenen Methoden zu verwenden.

            Das bedeutet im Endeffekt, dass due nicht gegen eine konkrete Implementierung, sondern nur gegen die Beschreibung der Außenansicht einer Implementierung implementierst. Eine solche Beschreibung nennt man Schnittstelle.

            Bei deinem Taschenrechner ließe sich die Klasse AbstractDisplay als Schnittstelle ansehen. Indem du in dem Taschenrechner nur diese Klasse verwendest, ignorierst du automatisch diejenigen Eigenschaften konkreter Displaytypen, die für die Einbindung nicht relevant sind. Da auch alle Display-Arten, da sie als Unterklasse von AbstractDisplay abgeleitet sind, die Methoden der Klasse AbstractDisplay teilen, ist es später zudem problemlos möglich eine Art Display gegen eine andere auszutauschen, ohne dass der Taschenrechner-Code geändert werden müsste.

            Schöne Grüße,

            Johannes

            1. Hi,

              Danke fuer deine recht umfangreiche Antwort, aber ich denke wohl immernoch zu kompliziert.

              Wenn die Klasse Taschenrechner beispielsweise ein Display-Objekt als Eigenschaft hat, besteht zwischen den beiden Klassen auch eine Beziehung.

              Und wie koennte diese Beziehung dann konkret umsetzen?
              Bei Rechner ein Attribut "display:Display", das ich dann mit einer setDisplay-Methode aufrufe?
              Wie sollte dann die setDisplay-Methode aufgerufen werden?
              Entweder mit einem vorher angelegten Display-Objekt, oder ohne Parameter, so dass sie das Objekt selbst anlegt?
              Dann haette ich eine gerichtete Assoziation, bzw eine Komposition, oder?
              Waere es schlechter die Beziehung in die andere Richtung zu machen? Also in Display ein Attribut auf das Rechner-Objekt setzen?
              Oder braeuchte ich eh eine Beziehung in beide Richtungen?

              Bei deinem Taschenrechner ließe sich die Klasse AbstractDisplay als Schnittstelle ansehen. Indem du in dem Taschenrechner nur diese Klasse verwendest, ignorierst du automatisch diejenigen Eigenschaften konkreter Displaytypen, die für die Einbindung nicht relevant sind. Da auch alle Display-Arten, da sie als Unterklasse von AbstractDisplay abgeleitet sind, die Methoden der Klasse AbstractDisplay teilen, ist es später zudem problemlos möglich eine Art Display gegen eine andere auszutauschen, ohne dass der Taschenrechner-Code geändert werden müsste.

              Muesste AbstractDisplay dann im einfachsten Fall nur eine abstrakte Funktion "anzeigen(float ergebnis):void" haben, die dann in den abgeleiteten Klassen (z.B. GruenesLeuchtDisplay) ueberlagert wird?
              Wie koennte ich aber dann wieder die Beziehung von Rechner auf AbstractDisplay, bzw. GruenesLeuchtDisplay herstellen? Der Datentyp der Referenz in Rechner waere ja dann, je nach Display-Typ unterschiedlich.

              Das einzige was mir einfallen wuerde waere nur in die andere Richtung zu gehen, also von Display auf Rechner (mit einer Referenz auf Rechner in einem Attribut von AbstractDisplay). Was aber den Nachteil haette, das ich vom Rechner nicht auf sein Display schliessen kann.

              mfG,
              steckl

              1. Wenn die Klasse Taschenrechner beispielsweise ein Display-Objekt als Eigenschaft hat, besteht zwischen den beiden Klassen auch eine Beziehung.

                Und wie koennte diese Beziehung dann konkret umsetzen?
                Bei Rechner ein Attribut "display:Display", das ich dann mit einer setDisplay-Methode aufrufe?

                Ja.
                Ich bin zwar nicht der OO Crack (deshalb lese ich das auch interessiert mit) aber hier ist es doch klar, ein Rechner "hat ein" Display. Und hat ein bedeutet i.d.R. es ist eine Eigenschaft des Objektes (im gegensatz zu "ist ein" - ein GrünesDisplay ist ein AbstraktesDisplay).

                Wie sollte dann die setDisplay-Methode aufgerufen werden?
                Entweder mit einem vorher angelegten Display-Objekt, oder ohne Parameter, so dass sie das Objekt selbst anlegt?

                Das dürfte die überlassen sein. Wenn du mit setDisplay eine "Factory" aufrufst, also zur Laufzeit das Display ändern willst, ist das vielleicht elegant, aber ob es wirklich nötig ist musst du dir überlegen.

                Dann haette ich eine gerichtete Assoziation, bzw eine Komposition, oder?
                Waere es schlechter die Beziehung in die andere Richtung zu machen? Also in Display ein Attribut auf das Rechner-Objekt setzen?
                Oder braeuchte ich eh eine Beziehung in beide Richtungen?

                Braucht das Diplay eine Information über den Taschenrechner? Eigentlich nicht, es braucht nur die Information was es wann anzeigen soll. Also brauchst du nur Schnittstellen um Werte anzuzeigen oder zu löschen. Genau wie auch die Eingabefelder.

                Muesste AbstractDisplay dann im einfachsten Fall nur eine abstrakte Funktion "anzeigen(float ergebnis):void" haben, die dann in den abgeleiteten Klassen (z.B. GruenesLeuchtDisplay) ueberlagert wird?
                Wie koennte ich aber dann wieder die Beziehung von Rechner auf AbstractDisplay, bzw. GruenesLeuchtDisplay herstellen? Der Datentyp der Referenz in Rechner waere ja dann, je nach Display-Typ unterschiedlich.

                Nein, in Display. Das konkrete Display hat bestimmte Eigenschaften um Werte darzustellen. I.d.R. dürften dies einfach Strings sein, d.h. alle deine Werte die du übergibst müßten die Methode toString() haben oder du baust für jeden Datentyp ein anzeige() Funktion die die entsprechende Umwandlungen machen.

                Das einzige was mir einfallen wuerde waere nur in die andere Richtung zu gehen, also von Display auf Rechner (mit einer Referenz auf Rechner in einem Attribut von AbstractDisplay). Was aber den Nachteil haette, das ich vom Rechner nicht auf sein Display schliessen kann.

                Wieso möchtest du vom Rechner auf das Display schliessen? Das konkrete Display ist dem Rechner egal, der Rechner hat ein Display und ruft dessen Anzeige Funktion auf, fertig. Was dort konkret passiert ist Rechner egal.

                Struppi.

                --
                Javascript ist toll (Perl auch!)
                1. Hi,

                  aber hier ist es doch klar, ein Rechner "hat ein" Display. Und hat ein bedeutet i.d.R. es ist eine Eigenschaft des Objektes (im gegensatz zu "ist ein" - ein GrünesDisplay ist ein AbstraktesDisplay).

                  Heisst das, ich brauche im Rechner-Objekt nur eine Referenz vom Typ der Eltern-Klasse (AbstractDisplay) und kann dann darueber auf das entsprechende Display zugreifen, egal welcher Kind-Klasse es angehoert?
                  Das war mir neu. Ich bin davon ausgegangen, dass das Attribut mit der Referenz immer den konkreten Typ des Elements haben muss, auf den es verweist.

                  Wenn ich das jetzt richtig verstanden habe brauche ich also nur eine gerichtete Assoziation von Rechner auf AbstractDisplay.

                  Wie sollte dann die setDisplay-Methode aufgerufen werden?
                  Entweder mit einem vorher angelegten Display-Objekt, oder ohne Parameter, so dass sie das Objekt selbst anlegt?

                  Das dürfte die überlassen sein. Wenn du mit setDisplay eine "Factory" aufrufst, also zur Laufzeit das Display ändern willst, ist das vielleicht elegant, aber ob es wirklich nötig ist musst du dir überlegen.

                  Ich moechte bei der Erzeugung des Rechners aussuchen koennen, welches Display er hat. Nachher kann das Display dann immer gleich bleiben.
                  Also muesste ich in Main erst ein entsprechendes Display-Objekt erzeugen und dieses dann an Rechner (entweder im Konstruktor, oder mit einer set-Methode) weitergeben, oder?

                  Wieso möchtest du vom Rechner auf das Display schliessen? Das konkrete Display ist dem Rechner egal, der Rechner hat ein Display und ruft dessen Anzeige Funktion auf, fertig. Was dort konkret passiert ist Rechner egal.

                  Da hab ich mich falsch ausgedrueckt. Ich meinte nicht auf das Display "schliessen" sondern "zugreifen". Aber das hat sich ja eh gerade geklaert.

                  mfG,
                  steckl

                  1. aber hier ist es doch klar, ein Rechner "hat ein" Display. Und hat ein bedeutet i.d.R. es ist eine Eigenschaft des Objektes (im gegensatz zu "ist ein" - ein GrünesDisplay ist ein AbstraktesDisplay).

                    Heisst das, ich brauche im Rechner-Objekt nur eine Referenz vom Typ der Eltern-Klasse (AbstractDisplay) und kann dann darueber auf das entsprechende Display zugreifen, egal welcher Kind-Klasse es angehoert?

                    Nein, natürlich brauchst du ein konkretes Dispaly du kannst ja keine Abstrakte Klasse erzeugen.

                    Wenn ich das jetzt richtig verstanden habe brauche ich also nur eine gerichtete Assoziation von Rechner auf AbstractDisplay.

                    Da ich überwiegend in JS und Perl programmiere, bin ich nicht so fit in abstrakten Klassen, da diese dort nicht existieren. Aber ich denke theoretisch, ja. Du brauchst ein Objekt auf eine abstraktes Display, dass dann z.b. mit setDisplayType( typ ) konkretisiert würde (was, soweit ich das weiß, einer Factory entsprechen würde)

                    Also muesste ich in Main erst ein entsprechendes Display-Objekt erzeugen und dieses dann an Rechner (entweder im Konstruktor, oder mit einer set-Methode) weitergeben, oder?

                    ja bzw. jein, du kannst das Display Objekt durchaus auch im Rechnerobjekt erzeugen.

                    Struppi.

                    --
                    Javascript ist toll (Perl auch!)
                    1. Hi,

                      Heisst das, ich brauche im Rechner-Objekt nur eine Referenz vom Typ der Eltern-Klasse (AbstractDisplay) und kann dann darueber auf das entsprechende Display zugreifen, egal welcher Kind-Klasse es angehoert?

                      Nein, natürlich brauchst du ein konkretes Dispaly du kannst ja keine Abstrakte Klasse erzeugen.

                      Sorry, aber ich glaube du hast hier die Begriffe Objekt und Klasse verwechselt?

                      Ob man keine Objekte von abstrakten Klassen erzeugen kann bin ich mir nicht so sicher, aber ich muss ja nur ein Objekt einer Kind-Klasse davon erzeugen. Dieses muesste ich dann, mit Hilfe einer Referenz vom Typ der (abstrakten) Elternklasse, ansprechen koennen.
                      (Weiss net ob das von dir so gemeint war, oder ob ich deine Aussage falsch verstanden habe)

                      Wenn ich das jetzt richtig verstanden habe brauche ich also nur eine gerichtete Assoziation von Rechner auf AbstractDisplay.

                      Da ich überwiegend in JS und Perl programmiere, bin ich nicht so fit in abstrakten Klassen, da diese dort nicht existieren. Aber ich denke theoretisch, ja. Du brauchst ein Objekt auf eine abstraktes Display, dass dann z.b. mit setDisplayType( typ ) konkretisiert würde (was, soweit ich das weiß, einer Factory entsprechen würde)

                      Oder ich erzeuge einfach das entsprechende Kind-Objekt in main() und speichere eine Referenz darauf im Rechner-Objekt (in einer Referenz vom typ AbstractDisplay mit einer set-Methode).

                      mfG,
                      steckl

                      1. Heisst das, ich brauche im Rechner-Objekt nur eine Referenz vom Typ der Eltern-Klasse (AbstractDisplay) und kann dann darueber auf das entsprechende Display zugreifen, egal welcher Kind-Klasse es angehoert?

                        Nein, natürlich brauchst du ein konkretes Dispaly du kannst ja keine Abstrakte Klasse erzeugen.

                        Sorry, aber ich glaube du hast hier die Begriffe Objekt und Klasse verwechselt?

                        Ja tu ich. Ich meinte natürlich du kannst keine Objekte von abstrakten Klassen erzeugen.

                        Ob man keine Objekte von abstrakten Klassen erzeugen kann bin ich mir nicht so sicher, aber ich muss ja nur ein Objekt einer Kind-Klasse davon erzeugen. Dieses muesste ich dann, mit Hilfe einer Referenz vom Typ der (abstrakten) Elternklasse, ansprechen koennen.

                        genauso wird das gemacht, ein abstrakte Klasse zeichnet sich ja dadurch aus, dass es lediglich einen Rumpf darstellt, die davon abgeleitete Klasse füllt diesen. Aber du kannst eine abstrakte Klasse als Funktionsparameter benutzen.

                        Wenn ich das jetzt richtig verstanden habe brauche ich also nur eine gerichtete Assoziation von Rechner auf AbstractDisplay.

                        Da ich überwiegend in JS und Perl programmiere, bin ich nicht so fit in abstrakten Klassen, da diese dort nicht existieren. Aber ich denke theoretisch, ja. Du brauchst ein Objekt auf eine abstraktes Display, dass dann z.b. mit setDisplayType( typ ) konkretisiert würde (was, soweit ich das weiß, einer Factory entsprechen würde)

                        Oder ich erzeuge einfach das entsprechende Kind-Objekt in main() und speichere eine Referenz darauf im Rechner-Objekt (in einer Referenz vom typ AbstractDisplay mit einer set-Methode).

                        Das hat Johannes ja auch so beschrieben.

                        Struppi.

                        --
                        Javascript ist toll (Perl auch!)
              2. Hallo steckl,

                Wie sollte dann die setDisplay-Methode aufgerufen werden?
                Entweder mit einem vorher angelegten Display-Objekt, oder ohne Parameter, so dass sie das Objekt selbst anlegt?

                Ich würde die Methode mit einem Parameter vom Typ AbstractDisplay versehen. So kannst du ein beliebiges Display-Objekt hinzufügen.

                Dann haette ich eine gerichtete Assoziation, bzw eine Komposition, oder?
                Waere es schlechter die Beziehung in die andere Richtung zu machen? Also in Display ein Attribut auf das Rechner-Objekt setzen?

                Nein, das Display braucht eigentlich nicht zu wissen, von wem es benutzt wird. Es bekommt vom Rechner gesagt, was es anzeigen soll und alles weitere hat es nicht zu interessieren.

                Muesste AbstractDisplay dann im einfachsten Fall nur eine abstrakte Funktion "anzeigen(float ergebnis):void" haben, die dann in den abgeleiteten Klassen (z.B. GruenesLeuchtDisplay) ueberlagert wird?

                Genau.

                Wie koennte ich aber dann wieder die Beziehung von Rechner auf AbstractDisplay, bzw. GruenesLeuchtDisplay herstellen? Der Datentyp der Referenz in Rechner waere ja dann, je nach Display-Typ unterschiedlich.

                Der große Vorteil von Vererbung ist ja, dass man einer Variable vom Datentyp der Elternklasse auch eine Instanz der Kindklasse haben kann. Das gleiche gilt auch für Methoden-Aufrufe. Der oben erwähnten Methode setDisplay, die einen Parameter für Typ AbstractDisplay erwartet kannst du ein Objekt einer beliebigen Tochterklasse (nennen wir LEDDisplay und TextDisplay als Beispiele) zuweisen.

                Du brauchst dich letzendlich also nur ein einziges mal, nämlich dann, wenn das Objekt erzeugt wird, darum zu kümmern welche konkrete Klasse instantiiert werden könnte. Als Pseudo-Code:

                int main()  
                {  
                    ...  
                  
                    AbstractDisplay display;  
                    if (display_config == "LED") {  
                        display = new LEDDisplay ();  
                    } else if (display_config = "Text") {  
                        display = new TextDisplay ();  
                    } else {  
                        ...  
                    }  
                  
                    ...  
                }
                

                Häufig wird die konkrete Instantiierung dann noch mittels einer Fabrikmethode (Stichwort Factory Method Pattern) ausgelagert, aber das Thema Design Patterns würde jetzt zu weit führen. Falls du dich weiter dafür interessierst, rate ich dir, dir ein gutes Buch über Software-Design zu suchen. Das Thema ist weitaus umfangreicher, als in diesem Thread bisher behandelt.

                Schöne Grüße,

                Johannes

                1. Hi,

                  danke dir, die Erklaerungen waren sehr aufschlussreich.

                  Ich habe jetzt mal ein Klassendiagramm dazu entworfen:

                  +--------------------------------------+
                  |          Rechner                     |
                  +--------------------------------------+
                  | - zahl1:float                        |
                  | - zahl2:float                        |
                  | - rechenart:char                     |
                  | - ergebnis: float                    |
                  | - displayRef:AbstractDisplay         |
                  +--------------------------------------+
                  | + setZahl1(void):void                |
                  | + setZahl2(void):void                |
                  | + berechne(void):void                |
                  | + schreibeErg(void):void             |
                  | + setDisplay(AbstractDisplay):void   |
                  +--------------------------------------+
                                | 1
                                |
                                v 1
                  +---------------------------+
                  |     AbstractDisplay       |
                  +---------------------------+
                  |                           |
                  +---------------------------+
                  | + anzeigen(){abstract}    |
                  +---------------------------+
                              ^
                             /_\             |
                              +----------------------------------+
                              |                                  |
                  +---------------------------+  +---------------------------------+
                  |       LedDisplay          |  |       TextDisplay               |
                  +---------------------------+  +---------------------------------+
                  |                           |  | - color:farbe                   |
                  +---------------------------+  +---------------------------------+
                  | + anzeigen(float):void    |  | + anzeigen(float):void          |
                  +---------------------------+  | + setSchriftfarbe(farbe):void   |
                                                 +---------------------------------+

                  Hierzu noch eine Frage:
                  Geht das so ohne weiteres, dass die eine Kind-Klasse andere Attribute und Methoden hat als die andere? Muss ich dann noch was in der Eltern-Klasse ergaenzen?
                  Nach meinem Kenntnisstand muesste es eigentlich so gehen, oder?

                  Macht es jetzt Sinn, dieses Klassendiagramm nachzuprogrammieren, oder ist noch irgendwas drin, was so nicht sein sollte?

                  Häufig wird die konkrete Instantiierung dann noch mittels einer Fabrikmethode (Stichwort Factory Method Pattern) ausgelagert, aber das Thema Design Patterns würde jetzt zu weit führen. Falls du dich weiter dafür interessierst, rate ich dir, dir ein gutes Buch über Software-Design zu suchen. Das Thema ist weitaus umfangreicher, als in diesem Thread bisher behandelt.

                  Mache ich, wenn die Zeit gekommen ist.

                  mfG,
                  steckl

                  1. Hallo steckl,

                    Geht das so ohne weiteres, dass die eine Kind-Klasse andere Attribute und Methoden hat als die andere? Muss ich dann noch was in der Eltern-Klasse ergaenzen?

                    Natürlich geht das. In allem, was nicht aus der Elternklasse kommt, können sich die abgeleiteten Klassen beliebig unterscheiden.

                    Nach meinem Kenntnisstand muesste es eigentlich so gehen, oder?

                    Macht es jetzt Sinn, dieses Klassendiagramm nachzuprogrammieren, oder ist noch irgendwas drin, was so nicht sein sollte?

                    Ich denke nicht. Und im Zweifelsfall merkst du es spätestens beim Implementieren, wenn ein Entwurf nicht funktioniert ;-)

                    Schöne Grüße,

                    Johannes

                    1. Hi,

                      Ich habs jetzt mal in C++ umgesetzt.

                      Geht das so ohne weiteres, dass die eine Kind-Klasse andere Attribute und Methoden hat als die andere? Muss ich dann noch was in der Eltern-Klasse ergaenzen?

                      Natürlich geht das. In allem, was nicht aus der Elternklasse kommt, können sich die abgeleiteten Klassen beliebig unterscheiden.

                      Hab da noch ein Problem.
                      Hier sind paar Codeausschnitte:

                        
                      class AbstractDisplay  
                      {  
                        public:  
                          virtual void anzeigen(float){};  
                          virtual void setColor(string){};  
                      };  
                        
                        
                      class SimpleDisplay : public AbstractDisplay  
                      {  
                        public:  
                          void anzeigen(float zahl)  
                          {  
                               cout << COLOR_BLUE << "\n\nErgebnis: " << zahl << COLOR_OFF << "\n\n";  
                          }  
                      };  
                        
                        
                      class TextDisplay : public AbstractDisplay  
                      {  
                        private:  
                          string color;  
                        
                        public:  
                        
                          TextDisplay()     // Konstruktor  
                          {  
                              setColor("\033[39;31;1m"); // default-farbe: rot  
                          }  
                        
                          void anzeigen(float zahl)  
                          {  
                               cout << color << "\n\nErgebnis: " << zahl << COLOR_OFF << "\n\n";  
                          }  
                        
                          void setColor(string farbe)  
                          {  
                        
                               color = farbe;  
                          }  
                      };  
                        
                        
                      in main():  
                          AbstractDisplay* display;  
                        
                          display = new TextDisplay;  
                          display->setColor("\033[39;34;1m"); // Farbe auf blau setzen  
                      
                      

                      Wenn ich die Zeile "virtual void setColor(string){};" weglasse kommt vom Compiler eine Fehlermeldung:
                      line 154: no matching function for call to `AbstractDisplay::setColor (const char[11])'
                      Das ist die Zeile mit "display->setColor()".

                      Ist es also doch zwingend noetig, dass ich fuer jede Methode, die ich in einer Unterklasse verwende oben eine mit virtual (steht fuer abstrakt?) davor mache?
                      Oder habe ich die Elternklasse nicht richtig als abstrakt festgelegt?

                      Ansonsten funktioniert jetzt alles so weit ... dank eurer Hilfe :-)
                      Aber ich brauch wohl noch einige Uebung.

                      ... Und im Zweifelsfall merkst du es spätestens beim Implementieren, wenn ein Entwurf nicht funktioniert ;-)

                      Hatte doch was uebersehen, und zwar die setOpart-Methode. Aber habs ziemlich bald gemerkt.

                      mfG,
                      steckl

                      1. Hallo steckl,

                        Wenn ich die Zeile "virtual void setColor(string){};" weglasse kommt vom Compiler eine Fehlermeldung:
                        line 154: no matching function for call to `AbstractDisplay::setColor (const char[11])'
                        Das ist die Zeile mit "display->setColor()".

                        Zurecht. Da die Variable vom Typ AbstractDisplay ist, „sieht“ der Compiler auch nur die Methoden, die bereits in dieser Klasse deklariert sind. Wenn du weitere Eigenschaften, die nur für die Unterklasse TextDisplay definiert sind, musst du zuerst ein Objekt der Klasse TextDisplay erzeugen, die entsprechenden Methoden aufrufen und dann dieses Objekt der display-Variablen zuweisen.

                        Schöne Grüße,

                        Johannes

                      2. Ist es also doch zwingend noetig, dass ich fuer jede Methode, die ich in einer Unterklasse verwende oben eine mit virtual (steht fuer abstrakt?) davor mache?

                        Das C++ hier so streng ist wußte ich nicht, aber das ist ja was ich die weiter oben gemeint habe. Ob letztlich sollte eine abstrakte Klasse Display eine Funktion setColor() haben sollte musst du entscheiden, aber das ist auf jeden Fall der saubere Weg. Du musst dann eben in der SimpleDisplay Klasse einfach eine Dummy Funktion die nichts macht (oder eine Warnung ausgibt) implementieren. Wie schon gesagt, würde ich sowas eher mit einer setConfig() oder setProperty() Funktion machen.

                        Struppi.

                        --
                        Javascript ist toll (Perl auch!)
                  2. +---------------------------+  +---------------------------------+
                    |       LedDisplay          |  |       TextDisplay               |
                    +---------------------------+  +---------------------------------+
                    |                           |  | - color:farbe                   |
                    +---------------------------+  +---------------------------------+
                    | + anzeigen(float):void    |  | + anzeigen(float):void          |
                    +---------------------------+  | + setSchriftfarbe(farbe):void   |
                                                   +---------------------------------+

                    Wie schon gesagt ich würde hier anzeigen nicht auf float beschränken, was ist z.b. wenn du später einmal den Operator ebenfalls anzeigen lassen möchtest oder die komplette Rechnung: zahl1 operator zahl2. Die einzige Gemeinsamkeit dürfte sein, dass sich der Parameter zu einem String konvertieren läßt (wenn man davon ausgeht dass du keine Grafiken auf dem display darstellen möchtest)

                    Geht das so ohne weiteres, dass die eine Kind-Klasse andere Attribute und Methoden hat als die andere? Muss ich dann noch was in der Eltern-Klasse ergaenzen?

                    Das spielt keine Rolle, nur bei den Methoden wird es kniffelig. Wenn setSchriftfarbe aufrufst und du hast ein LedDisplay erhälst du einen Fehler. Wie man das am sinnvollsten vermeidet wüßte ich auch nich genau, ich würde vermutlich in der abstrakten Klasse eine setConfig() Funktion deklarieren, die ein Datenfeld (evtl. ein struct) übergibt, wo sich dann die jeweilige konkrete Klasse ihre Angaben rauszieht. Oder eine setProperty Funktion, die dann überprüft ob die Eigenschaft existiert und gesetzt werden kann.

                    Struppi.

                    --
                    Javascript ist toll (Perl auch!)
                    1. Das spielt keine Rolle, nur bei den Methoden wird es kniffelig. Wenn setSchriftfarbe aufrufst und du hast ein LedDisplay erhälst du einen Fehler.

                      Man könnte den Methodenaufruf ja von einer Abfrage nach seiner Existenz abhängig machen (ähnlich wie in JS).

                      Siechfred

                      --
                      Ein Selbständiger ist jemand, der bereit ist, 16 Stunden am Tag zu arbeiten, nur um nicht 8 Stunden für einen Anderen arbeiten zu müssen.
                      1. Das spielt keine Rolle, nur bei den Methoden wird es kniffelig. Wenn setSchriftfarbe aufrufst und du hast ein LedDisplay erhälst du einen Fehler.

                        Man könnte den Methodenaufruf ja von einer Abfrage nach seiner Existenz abhängig machen (ähnlich wie in JS).

                        Halte ich für einen unschönen Ansatz.

                        Man macht das in JS i.d.R. ja nur, weil man keinen Einfluss auf die Browserobjekte hat. Wenn du ein eigenes Objekt hast, würde man auf sowas ja eher verzichten.

                        Struppi.

                        --
                        Javascript ist toll (Perl auch!)
                        1. Das spielt keine Rolle, nur bei den Methoden wird es kniffelig. Wenn setSchriftfarbe aufrufst und du hast ein LedDisplay erhälst du einen Fehler.
                          Man könnte den Methodenaufruf ja von einer Abfrage nach seiner Existenz abhängig machen (ähnlich wie in JS).
                          Halte ich für einen unschönen Ansatz.

                          Ja, wenn ich so drüber nachdenke ...

                          Ich würde es dann eher so machen, dass ich ein Display-Objekt machen würde, das die Eigenschaften und Methoden bietet, die alle Displays gemeinsam haben, z.B. den anzuzeigenden Wert oder eine Anzeigemethode. Darunter könnte ich mir dann für die verschiedenen Anzeigearten Unterobjekte vorstellen, z.B. Display::LED oder Display::Text (um mal bei der mir aus Perl bekannten Schreibweise zu bleiben). Die Methoden werden dann direkt über die Instanz des nachrangigen Objektes aufgerufen. Hat man ein LED-Display, käme man also gar nicht in die Verlegenheit, eine Methode setSchriftfarbe aufzurufen.

                          Siechfred

                          --
                          Ein Selbständiger ist jemand, der bereit ist, 16 Stunden am Tag zu arbeiten, nur um nicht 8 Stunden für einen Anderen arbeiten zu müssen.
                          1. Ja, wenn ich so drüber nachdenke ...

                            denken ist gut, lesen aber auch ;-) - Wir sind hier im Prinzip schon weiter.

                            Ich würde es dann eher so machen, dass ich ein Display-Objekt machen würde, das die Eigenschaften und Methoden bietet, die alle Displays gemeinsam haben, z.B. den anzuzeigenden Wert oder eine Anzeigemethode. Darunter könnte ich mir dann für die verschiedenen Anzeigearten Unterobjekte vorstellen, z.B. Display::LED oder Display::Text (um mal bei der mir aus Perl bekannten Schreibweise zu bleiben). Die Methoden werden dann direkt über die Instanz des nachrangigen Objektes aufgerufen. Hat man ein LED-Display, käme man also gar nicht in die Verlegenheit, eine Methode setSchriftfarbe aufzurufen.

                            Es ging darum dass eine Klasse Rechner eine Klasse Display hat, dieses Klasse ist abstrakt. Wird nun eine konkretes Objekt erzeugt, kann dieses sowohl das eine oder andere sein, aber nur in dem Fall das es ein Display::LED ist, kann die Farbe gesetzt werden.

                            Das ganze ist nicht unbedingt 1:1 in Perl übetragbar, da es dort keine echten abstrakten Klassen gibt (manche sagen noch nicht mal echte Klassen). Aber Polymorphismus ist dort natürlich auch möglich. D.h. du weißt vorher nicht was für ein Art Objekt du hast und eigentlich sollte es dich auch nicht intersssieren, es reicht zu Wissen das du ein (abstraktes) Objekt Display hast.

                            Aber in dem Beispiel reicht das eben nicht, da setFarbe() nur ein LED Display kennt und das halte ich für nicht gewünscht im OOP Konzept.

                            Struppi.

                            --
                            Javascript ist toll (Perl auch!)
                            1. denken ist gut, lesen aber auch ;-) - Wir sind hier im Prinzip schon weiter.

                              Oh, ich wollte nicht dazwischenquaken ;)

                              Aber in dem Beispiel reicht das eben nicht, da setFarbe() nur ein LED Display kennt und das halte ich für nicht gewünscht im OOP Konzept.

                              Aber ist nicht genau das der Sinn abstrakter Klassen?

                              Siechfred

                              --
                              Ein Selbständiger ist jemand, der bereit ist, 16 Stunden am Tag zu arbeiten, nur um nicht 8 Stunden für einen Anderen arbeiten zu müssen.
    2. Das Problem beim Objektorientierten Design ist, dass es eigentlich so intuitiv ist, dass man gerade anfangs davor zurückschreckt! ;)

      Da ist was dran, allerdings ist das Problem beim OO Design auch, dass Zusammenhänge und beteiligte Entitäten von Anfang an möglichst vollständig verstanden werden müssen um mit dem Kodieren anfangen zu können. Für Mr.Spock kein Problem, für Harry Hirsch dagegen schon, Harry wird sich erst beim "prozeduralen" Kodieren darüber klar (oder auch nie ;), was sein Programm wirklich leistet. Schon so ein kleiner Taschenrechner ist nicht ganz einfach zu verstehen, was glaubst Du wie vollständig Du das Gerät verstanden und beschrieben hast? (Nichts gegen die Analyse, die ist gut, aber ist sie vollständig und fehlerfrei?)

      1. Nun meine Analyse ist sicherlich weder fehlerfrei noch vollständig. Das muss sie aber auch garnicht sein. Denn der von mir beschriebene Taschenrechner ist ein Modell eines Taschenrechnern, dass den Anforderungen von steckl genügt. Hätte ich es richtig machen wollen, hätte man auch noch Klammersetzungen und unäre Operatoren berücksichtigen müssen. Das hätte das ganze verkompliziert und dem Verständnis nicht weitergeholfen.