I-PROGR2-HOME

  1. Einführung

  2. Beispiel mit geometrischen Objekten

  3. Testfragen und Aufgaben


I-PROGR2 SS03 - PROGRAMMIEREN2 - Vorlesung mit Übung
Abstrakte Klassen - Polymorphie durch Vererbung

 Achtung : Skript gibt den mündlichen Vortrag nicht vollständig wieder !
 Achtung : Skript noch nicht abgeschlossen !
                         

AUTHOR: Gerd Döben-Henisch
DATE OF FIRST GENERATION: May-19, 2003
DATE OF LAST CHANGE: May-21, 2003
EMAIL: Gerd Döben-Henisch



1. Einführung


Oberbegriffe, Typüberprüfung in C++, mit Vererbung oder mit Templates, Oberbegriffe als gemeinsame Basisklassen, virtuelle Funktionen, abstrakte Klassen mit garantierter 'Nicht-Erzeugung' durch Privatiierung des Konstruktors, aber virtueller Destruktor, konkrete Klassen

Reine virtuelle funktionen ('pure virtual functions') sind nicht nur virtuell, sondern besitzen nur eine Deklarierung, keine Implementierung; abstrakte Operation. Eine abgeleitete Klasse muss nicht implementieren, da sie selbst wiederum eine abstrakte Klasse sein kann.

Identität eines Objektes über Zeiger!

Verwaltung aller abgeleiteten Objekte durch eine Gruppenklasse

Runtime type Information (RTTI) mit static_cast<>() oder typeid(); insgesamt nicht gut

Abstrakte Klassen als Schnittstellenvereinbarung; darauf basierend abgeleitete Klassen bzw. Funktionen

[JOSUTTIS 1999]


START



2. Beispiel mit geometrischen Objekten




geobsp

Beispiel mit Basis-Klasse Geo


Man beachte den Unterschied zwischen Objekt:Klasse in UML zu Paket::Klasse. In C++ lässt sich ein Paket z.B. realisieren als namespace Geo{}, der dann die Basisklassen, die abgeleitete Klassen und mögliche Hilfsklassen enthält. In unserem Beispiel findet sich explizit folgende Struktur:

  namespace Geo {

/* Klasse Koord
 * - Hilfsklasse für geometrische Objekte
 * - nicht zur Vererbung geeignet
 */
class Koord {...};

/* abstrakte Basisklasse GeoObj
 * - gemeinsame Basisklasse für geometrische Objekte
 * - zur Vererbung vorgesehen
 */
class GeoObj { ...};

/* Klasse Kreis
 * - abgeleitet von GeoObj
 * - ein Kreis besteht aus:
 *     - Mittelpunkt (Referenzpunkt, geerbt)
 *     - Radius (neu)
 */
class Kreis : public GeoObj { ...};

/* Klasse Linie
 * - abgeleitet von GeoObj
 * - ein Linie besteht aus:
 *     - einem Anfangspunkt (Referenzpunkt, geerbt)
 *     - einem Endpunkt (neu)
 */
class Linie : public GeoObj { ...};

/* Klasse GeoGruppe
 * - abgeleitet von GeoObj
 * - eine GeoGruppe besteht aus:
 *     - einem Referenzpunkt (geerbt)
 *     - einer Menge von geometrischen Elementen (neu)
 */
class GeoGruppe : public GeoObj { ..};

}//End of namespace Geo

Makefile für Beispiele





objects1 = geotest1.o
objects2 = gruppe.o geotest2.o

geo1: $(objects1)
	g++ -o geo1 $(objects1)

geotest1.o: geotest1.cpp koord.hpp geoobj.hpp kreis.hpp linie.hpp
	g++ -c geotest1.cpp

geo2: $(objects2)
	g++ -o geo2 $(objects1)

geotest2.o: geotest2.cpp gruppe.cpp koord.hpp geoobj.hpp kreis.hpp linie.hpp gruppe.hpp
	g++ -c geotest2.cpp

gruppe.o: gruppe.cpp gruppe.hpp
	g++ -c gruppe.cpp


clean:
	rm geo1 $(objects1) geo2 $(objects2)





Klasse Koordinate




/* Die folgenden Code-Beispiele stammen aus dem Buch:
 *  Objektorientiertes Programmieren in C++
 *   Ein Tutorial für Ein- und Umsteiger
 * von Nicolai Josuttis, Addison-Wesley München, 2001
 *
 * (C) Copyright Nicolai Josuttis 2001.
 * Diese Software darf kopiert, verwendet, modifiziert und verteilt
 * werden, sofern diese Copyright-Angabe in allen Kopien vorhanden ist.
 * Diese Software wird "so wie sie ist" zur Verfügung gestellt.
 * Es gibt keine explizite oder implizite Garantie über ihren Nutzen.
 */
#ifndef KOORD_HPP
#define KOORD_HPP

// Headerdatei für I/O
#include <iostream>

namespace Geo {

/* Klasse Koord
 * - Hilfsklasse für geometrische Objekte
 * - nicht zur Vererbung geeignet
 */
class Koord {
  private:
    int x;     // X-Koordinate
    int y;     // Y-Koordinate

  public:
    // Default-Konstruktor und Konstruktor aus zwei ints
    Koord () : x(0), y(0) {     // Default-Werte: 0
    }
    Koord (int newx, int newy) : x(newx), y(newy) {
    }

    Koord operator + (const Koord&) const;    // Addition
    Koord operator - () const;                // Negation
    void  operator += (const Koord&);         // +=
    void  printOn (ostream& strm) const;      // Ausgabe
};

/* Operator +
 * - X- und Y-Koordinaten addieren
 */
inline Koord Koord::operator + (const Koord& p) const
{
    return Koord(x+p.x,y+p.y);
}

/* einstelliger Operator -
 * - X- und Y-Koordinaten negieren
 */
inline Koord Koord::operator - () const
{
    return Koord(-x,-y);
}

/* Operator +=
 * - Offset auf X- und Y-Koordinaten aufaddieren
 */
inline void Koord::operator += (const Koord& p)
{
    x += p.x;
    y += p.y;
}

/* printOn()
 * - Koordinaten als Wertepaar ausgeben
 */
inline void Koord::printOn (ostream& strm) const
{
    strm << '(' << x << ',' << y << ')';
}

/* Operator <<
 * - Umsetzung für Standard-Ausgabeoperator
 */
inline ostream& operator<< (ostream& strm, const Koord& p)
{
    p.printOn (strm);
    return strm;
}

}  // namespace Geo

#endif // KOORD_HPP



Abstrake Klasse geoobj





#ifndef GEOOBJ_HPP
#define GEOOBJ_HPP

// Headerdatei für Koord
#include "koord.hpp"

namespace Geo {

/* abstrakte Basisklasse GeoObj
 * - gemeinsame Basisklasse für geometrische Objekte
 * - zur Vererbung vorgesehen
 */
class GeoObj {
  protected:
    // Jedes GeoObj hat einen Referenzpunkt
    Koord refpunkt;

    /* Konstruktor für Startwert vom Referenzpunkt
     * - nichtöffentlich
     * - damit ist kein Default-Konstruktor vorhanden
     */
    GeoObj (const Koord& p) : refpunkt(p) {
    }

  public:
    // GeoObj um relativen Offset verschieben
    virtual void move (const Koord& offset) {
        refpunkt += offset;
    }

    /* GeoObj ausgeben
     * - rein virtuelle Funktion
     */
    virtual void draw () const = 0;

    // virtueller Destruktor
    virtual ~GeoObj () {
    }
};

}  // namespace Geo

#endif  // GEOOBJ_HPP



Abgeleitete Klasse Kreis





#ifndef KREIS_HPP
#define KREIS_HPP

// Headerdatei für I/O
#include <iostream>

// Headerdatei der Basisklasse
#include "geoobj.hpp"

namespace Geo {

/* Klasse Kreis
 * - abgeleitet von GeoObj
 * - ein Kreis besteht aus:
 *     - Mittelpunkt (Referenzpunkt, geerbt)
 *     - Radius (neu)
 */
class Kreis : public GeoObj {
  protected:
    unsigned radius;    // Radius

  public:
    // Konstruktor für Mittelpunkt und Radius
    Kreis (const Koord& m, unsigned r)
         : GeoObj(m), radius(r) {
    }

    // Ausgabe (jetzt auch implementiert)
    virtual void draw () const;

    // virtueller Destruktor
    virtual ~Kreis () {
    }
};

/* Ausgabe
 * - inline definiert
 */
inline void Kreis::draw () const
{
    std::cout << "Kreis um Mittelpunkt " << refpunkt
              << " mit Radius " << radius << std::endl;
}

}  // namespace Geo

#endif // KREIS_HPP



Abgeleitete Klasse Linie





#ifndef LINIE_HPP
#define LINIE_HPP

// Headerdatei für I/O
#include <iostream>

// Headerdatei der Basisklasse
#include "geoobj.hpp"

namespace Geo {

/* Klasse Linie
 * - abgeleitet von GeoObj
 * - ein Linie besteht aus:
 *     - einem Anfangspunkt (Referenzpunkt, geerbt)
 *     - einem Endpunkt (neu)
 */
class Linie : public GeoObj {
  protected:
    Koord p2;    // zweiter Punkt, Endpunkt

  public:
    // Konstruktor für Anfangs- und Endpunkt
    Linie (const Koord& a, const Koord& b)
         : GeoObj(a), p2(b) {
    }

    // Ausgabe (jetzt auch implementiert)
    virtual void draw () const;

    // Verschieben (neu implementiert)
    virtual void move (const Koord&);

    // virtueller Destruktor
    virtual ~Linie () {
    }
};

/* Ausgabe
 * - inline definiert
 */
inline void Linie::draw () const
{
    std::cout << "Linie von " << refpunkt
              << " bis " << p2 << std::endl;
}

/* Verschieben
 * - inline neu implementiert
 */
inline void Linie::move (const Koord& offset)
{
    refpunkt += offset;    // entspricht GeoObj::move(offset);
    p2 += offset;
}

}  // namespace Geo

#endif // LINIE_HPP



Usage1-Datei





// Headerdateien für verwendete Klassen
#include "linie.hpp"
#include "kreis.hpp"
#include "geoobj.hpp"

// Vorwärtsdeklaration
void geoObjAusgeben (const Geo::GeoObj&);

int main()
{
    Geo::Linie l1 (Geo::Koord(1,2), Geo::Koord(3,4));
    Geo::Linie l2 (Geo::Koord(7,7), Geo::Koord(0,0));
    Geo::Kreis k (Geo::Koord(3,3), 11);

    // inhomogene Menge von geometrischen Objekten:
    Geo::GeoObj* menge[10];

    menge[0] = &l1;     // Menge enthält: - Linie l1
    menge[1] = &k;      //     - Kreis k
    menge[2] = &l2;     //     - Linie l2

    /* Elemente in der Menge ausgeben und verschieben
     * - es wird automatisch die richtige Funktion aufgerufen
     */
    for (int i=0; i<3; i++) {
        menge[i]->draw();
        menge[i]->move(Geo::Koord(3,-3));
    }

    // Linien einzeln über Hilfsfunktion ausgeben
    geoObjAusgeben(l1);
    geoObjAusgeben(k);
    geoObjAusgeben(l2);
}

void geoObjAusgeben (const Geo::GeoObj& obj)
{
    /* es wird automatisch die richtige Funktion aufgerufen
     */
    obj.draw();
}



Abgeleitete Klasse Gruppe als Factory Klasse





#ifndef GEOGRUPPE_HPP
#define GEOGRUPPE_HPP

// Headerdatei der Basisklasse einbinden
#include "geoobj.hpp"

// Headerdatei für die interne Verwaltung der Elemente
#include <vector>

namespace Geo {

/* Klasse GeoGruppe
 * - abgeleitet von GeoObj
 * - eine GeoGruppe besteht aus:
 *     - einem Referenzpunkt (geerbt)
 *     - einer Menge von geometrischen Elementen (neu)
 */
class GeoGruppe : public GeoObj {
  protected:
    std::vector<GeoObj*> elems;    // Menge von Zeigern auf GeoObjs

  public:
    // Konstruktor mit optionalem Referenzpunkt
    GeoGruppe (const Koord& p = Koord(0,0)) : GeoObj(p) {
    }

    // Ausgabe (jetzt auch implementiert)
    virtual void draw () const;

    // Element einfügen
    virtual void add (GeoObj&);

    // Element entfernen
    virtual bool remove (GeoObj&);

    // virtueller Destruktor
    virtual ~GeoGruppe () {
    }
};

}  // namespace Geo

#endif  // GEOGRUPPE_HPP



Implementierung der Factory-Klasse





#include "gruppe.hpp"
#include <algorithm>

namespace Geo {

/* add
 * - Element einfügen
 */
void GeoGruppe::add (GeoObj& obj)
{
    // Adresse vom geometrischen Objekt eintragen
    elems.push_back(&obj);
}

/* remove
 * - Element löschen
 */
bool GeoGruppe::remove (GeoObj& obj)
{
    // erstes Element mit dieser Adresse finden
    std::vector<GeoObj*>::iterator pos;
    pos = std::find(elems.begin(),elems.end(),
                    &obj);
    if (pos != elems.end()) {
        elems.erase(pos);
        return true;
    }
    else {
        return false;
    }
}

/* draw
 * - alle Elemente unter Berücksichtigung des Referenzpunktes ausgeben
 */
void GeoGruppe::draw () const
{
    for (unsigned i=0; i<elems.size(); ++i) {
        elems[i]->move(refpunkt);   // Offset für den Referenzpunkt addieren
        elems[i]->draw();           // Element ausgeben
        elems[i]->move(-refpunkt);  // Offset wieder entfernen
    }
}

}  // namespace Geo



Usage2-Datei mit Factory-Klasse





// Headerdatei für I/O
#include <iostream>

// Headerdateien für verwendete Klassen
#include "linie.hpp"
#include "kreis.hpp"
#include "gruppe.hpp"

int main()
{
    Geo::Linie l1 (Geo::Koord(1,2), Geo::Koord(3,4));
    Geo::Linie l2 (Geo::Koord(7,7), Geo::Koord(0,0));
    Geo::Kreis k (Geo::Koord(3,3), 11);

    Geo::GeoGruppe g;

    g.add(l1);            // GeoGruppe enthält: - Linie l1
    g.add(k);             //     - Kreis k
    g.add(l2);            //     - Linie l2

    g.draw();             // GeoGruppe ausgeben
    std::cout << std::endl;

    g.move(Geo::Koord(3,-3));  // GeoGruppen-Offset verschieben
    g.draw();                  // GeoGruppe nochmal ausgeben
    std::cout << std::endl;

    g.remove(l1);         // GeoGruppe enthält nur noch k und l2
    g.draw();             // GeoGruppe nochmal ausgeben
}



START

3. Aufgaben



START