C++-Programmierung/ Objektorientierte Programmierung/ Abstrakte Klassen


EinleitungBearbeiten

Abstrakte Klassen sind Klassen in denen mindestens eine Methode als absichtlich nicht erfüllt deklariert wurde. Diese Methodeneigenschaft wird auch als "rein virtuell" bezeichnet. Die Erfüllung nicht-erfüllter Methoden wird den von einer abstrakten Klasse abgeleiteten Klassen überlassen.

Die wichtigsten Eigenschaften abstrakter Klassen sind:

  • können aus "reinen" Deklarationen bestehen
  • können nicht direkt instanziiert werden (wenngleich auch Objekte dieser Klasse existieren können)
  • erfordern für die Verwendung eine nicht-abstrakte abgeleitete Klasse
  • werden oft als Schnittstellenbeschreibungen (z.B. in umfangreichen Anwendungen) verwendet
  • treten häufig als Startpunkte von Vererbungshierarchien (z.B. in Klassensystemen) auf

DeklarierenBearbeiten

Eine Klasse wird dadurch abstrakt, indem man eine ihrer Mitgliedsmethoden als rein virtuell deklariert. Dazu schreiben Sie =0 hinter die Deklaration einer virtuellen Methode.

class StatusAusgeber
{
public:
	virtual void printStatus(void) = 0;	// Rein virtuelle Deklaration
        virtual ~StatusAusgeber() {}
};

Der Versuch, eine Instanz von dieser Klasse zu erstellen, schlägt fehl:

int main (void)
{
	StatusAusgeber instanz;  //Instanz von abstrakter Klasse kann nicht erstellt werden
	return 0; 
};

VerwendenBearbeiten

Nun beschreiben wir die Verwendung von abstrakten Klassen anhand eines Beispiels. Dazu verwenden wir die abstrakte Klasse StatusAusgeber aus dem vorigen Abschnitt.

An gewissen Stellen Ihrer Programme wollen Sie die Funktionalität einer Klasse sicherstellen und verwenden, ohne auf die Funktionalitäten abgeleiteter Klassen eingehen zu müssen.

Wir deklarieren die Klassen Drucker und Bildschirm.

class Drucker : public StatusAusgeber
{
	unsigned int m_nDruckeAusgefuehrt;
	unsigned int m_nLuefterAnzahl;
	// Diverse druckerspezifische Attribute
public:
	// Diverse druckerspezifische Methoden
	void printStatus(void)
	{
		std::cout 
			<< "Geraet: Drucker" 
			<< std::endl
			<< "Drucke ausgefuehrt: " << m_nDruckeAusgefuehrt
			<< std::endl
			<< "Verbaute Luefteranzahl: " << m_nLuefterAnzahl
			<< std::endl;
	}
};

class Bildschirm : public StatusAusgeber
{
	unsigned int m_nLeistungsaufnahmeWatt;
	unsigned int m_nDiagonaleAusdehnungZoll;
	// Diverse bildschirmspezifische Attribute
public:
	// Diverse bildschirmspezifische Methoden
	void printStatus(void)
	{
		std::cout 
			<< "Geraet: Bildschirm" 
			<< std::endl
			<< "Leistungsaufnahme (Watt): " << m_nLeistungsaufnahmeWatt
			<< std::endl
			<< "Bildschirmgroesse diagonal (Zoll): " << m_nDiagonaleAusdehnungZoll
			<< std::endl;
	}
};

Vorteil dieser Vorgehensweise ist die spätere Verwendung von Methoden der Basisklasse, bei denen die Implementierung erzwungen wurde. Der Verwender der abgeleiteten Klasse kann sich darauf verlassen, dass die Methode implementiert wurde, ohne die weiteren Teile der Hierarchie zu kennen.

Wir deklarieren die Klasse GeraeteMitStatusSpeicher

class GeraeteMitStatusSpeicher  : std::vector<StatusAusgeber*>
{
	static const char * _Trennzeile;	// Trennzeile zwischen den Statusangaben
public:
	void speichern(StatusAusgeber * GeraetMitStatus)
	{
		this->push_back(GeraetMitStatus); // Gerätezeiger im Vektor speichern
	}
	void printStatus(void)
	{
		std::vector<StatusAusgeber*>::const_iterator it = this->begin();
		while (it != this->end()) // Solange der Iterator nicht auf das Ende verweist
		{ // Iteration über den gesamten Inhalt
			(*it)->printStatus(); // Iterator dereferenzieren, enthaltenen Zeiger verwenden
			std::cout << _Trennzeile << std::endl; // Trennzeile ausgeben
			it++; // Nächsten möglichen Inhalt auswählen
		}
	}
};
const char * GeraeteMitStatusSpeicher::_Trennzeile = "---------------"; // statisch in GeraeteMitStatusSpeicher

GeraetMitStatusSpeicher ist von std::vector<StatusAusgeber*> abgeleitet, speichert Zeiger auf StatusAusgeber-Objekte.

Hinweis

Dies ist möglich, da Zeiger eine feste Größe haben. Speicherung von Objekten abstrakter Klassen ist hier nicht möglich, da an dieser Stelle unmöglich die Größe der effektiven Objekte zu erkennen ist, auf die diese Zeiger verweisen. Genau das war uns ja von vornherein klar, weil wir nur an der printStatus()-Methode interessiert sind, deren Existenz durch die abstrakte Basisklasse sichergestellt wird. Egal ob hier ein Drucker, Monitor oder irgendein anderes Objekt hineingerät, das von StatusAusgeber abgeleitet wurde, wir können den Status ausgeben.

  • speichern(StatusAusgeber *) speichert einen Zeiger auf ein StatusAusgeber-Objekt
  • printStatus() ruft die Methode printStatus() für alle gespeicherten Zeiger auf StatusAusgeber-Objekte auf