Eine der wesentlichen Konzepte der Objektorientierung
ist die Kapselung. Was ist das eigentlich genau?
Kapselung:
Allgemein eine Technik zur Schnittstellendefinition. Durch Kapselung werden Realisierungsdetails hinter einer definierten Schnittstelle verborgen. Die Schnittstelle kann dadurch oft auch erhalten bleiben, wenn sich Interna ändern müssen. |
Nachfolgend versuche ich, an konkreten
einfachen Beispielen zu zeigen, was Kapselung in der Praxis bedeuten
kann.
Hierbei geht es vorrangig um das
Verständnis für die Kapselung von Objekten mit Hilfe von
C++-Klassen. Sie werden aber sehen, dass die Kapselung genau genommen
ein weit verbreiteter Prozess ist, der in gewisser Weise auch durch die
Einführung von Variablen und Funktionen bereits statt findet.
Beginnen wir mit der
C++-Programmierung.
Die Basis soll folgendes einfache Konsolenprogramm bilden:
int
main() { return 0; }; |
Sie benutzen hier einfach die Funktion main(...).
Sie müssen dies machen, damit die Sache ins Rollen kommt.
Das ist Ihre Schnittstelle zu C/C++. Auch
das ist in gewissem Sinne bereits Kapselung!
main(...) ist die Schnittstelle zum Compiler. Diese Schnittstelle ist schon über viele Jahre für Konsolen-Programme konstant, obwohl sich Editoren und Compiler gewandelt haben.
Im nächsten Schritt geben wir den
String "Hallo" auf dem Bildschirm aus. Wir verwenden hierzu cout.
Daher müssen wir den C++-Header
iostream.h einbinden:
#include
<iostream>
int
main() return
0; |
So kann man typischerweise anfangen, C++ zu lernen. Sie müssen hierzu nicht wissen, was iostream genau ist und wie es intern aufgebaut ist. Sie benutzen einfach std::cout<< ... << std::endl; . Sie verwenden es, und es funktioniert. In iostream sind die notwendigen Funktionalitäten versteckt, die Zeichen auf dem Bildschirm ausgeben. Auch hier findet sich eine gewisse Art der Kapselung! Ein Anfänger muss in keiner Weise wissen, was std::cout eigentlich genau ist. Man lenkt auszugebende Einheiten einfach auf dieses Standardausgabe-Objekt.
Wir erhöhen die Komplexität,
indem wir den auszugebenden String zunächst in einer Variable
speichern.
Diese Variable ist vom Typ char-Array mit
der Bezeichnung Text:
#include
<iostream>
int
main()
return 0; |
std::cout
<< pText << std::endl;
ist die
entscheidende
Zeile. std::cout << ... << std::endl "kapselt" die
Ausgabe,
pText "kapselt" den konkreten String (Zeichenfolge).
Diese Zeile kann
unverändert bleiben, wenn der Compilerentwickler in iostream
etwas ändert oder ein anderer konstanter String zugeordnet wird.
Die beiden oben durchgeführten
Aktionen
1) Zuordnung eines Strings zu Text[...]
2) Ausgabe des Inhalts von Text[...]
mittels std::cout
werden nachfolgend in eigene Funktionen
eingebunden.
Damit "kapseln/umhüllen" Sie zum
Beispiel die Ausgabe mittels std::cout:
#include
<iostream>
char
Text[255]; int
main()
return 0; |
SetzeText("Gruss
aus der Variablen Text mittels Funktion");
Ausgabe();
Diesen beiden
Funktionen sieht man in keiner Weise an, wie sie realisiert werden.
Das ist in gewisser
Weise Kapselung.
Jetzt "kapseln/umhüllen" wir das
Ganze noch einmal durch den Einsatz einer Klasse.
Die Zeiger-Variable pText wird Member-Variable.
Die beiden Funktionen werden Member-Funktionen.
Der Zugriff wird zunächst sowohl
direkt auf die Variable als auch auf die Funktionen gewährt.
Dafür sorgt die Anweisung "public:",
die uneingeschränkten Zugriff auf alle Member der Klasse sicher
stellt:
#include
<iostream>
class
X public: int
main()
MyX.pText = "Gruss aus dem Inneren der Klasse MyX mittels Variable";
return 0; |
Im nächsten Schritt kapseln
wir die Member-Variable pText der Klasse X. Das erledigt das kleine
Wort "private:" für uns.
Wir erhalten vom Compiler eine
Fehlermeldung, daß ein Zugriff auf ein "private" Element nicht
erlaubt ist.
So einfach kann man Member-Variablen, auch
Attribute genannt, innerhalb einer C++-Klasse kapseln.
#include
<iostream>
class
X public: int
main()
MyX.pText = "Gruss aus dem Inneren der Klasse MyX mittels Variable";
return 0; |
MSVC++6: error C2248: "pText" : Kein Zugriff auf private Element, dessen Deklaration in der Klasse "X" erfolgte |
Im Inneren des Objektes von der Klasse
X liegt
nun gekapselt die Zeiger-Variable pText.
Ein direkter Zugriff mittels MyX.pText ist
nicht mehr erlaubt! Dafür stehen an der Schnittstelle jetzt nur
noch
die Member-Funktionen bereit.
Wenn man auf diesen unerlaubten Zugriff
verzichtet, wird der Quellcode natürlich problemlos kompiliert:
#include
<iostream>
class X
public:
int main()
return 0; |
X MyX;
erstellt
übrigens aus der Klasse X das konkrete Objekt MyX.
Klassen sind
also Baupläne/Vorlagen für konkrete Objekte.
Wozu ist das gut? Betrachten Sie
folgenden Code:
class
X { //geheim }; |
#include
"X.h"
int
main()
return 0; |
Sie müssen absolut nicht wissen,
wie die Klasse X intern aufgebaut ist.
Alles was Sie benötigen, ist die
Information über die Verwendung der beiden Member-Funktionen
void X::SetzeText( char* )
void X::Ausgabe( ).
Hierbei ist das Wissen über die Member-Variable pText nicht wichtig. Sie können diese sowieso nicht direkt ansprechen.
Das bedeutet, dass die Aufgabe der
Erstellung des main-Programmes und die Pflege der Klasse X völlig
getrennt gesehen werden können.
Die Schnittstelle sind die Prototypen der
beiden Member-Funktionen.
Das bedeutet auch, daß an
folgenden rot gekennzeichneten Stellen Änderungen vorgenommen
werden dürfen:
//X.h
#include <iostream> class
X public:
|
#include
"X.h"
int
main() return
0; |
Die Grundidee der Kapselung ist somit
sichtbar. Es geht um die Aufteilung des Programmcodes in einzeln
pflegbare Module.
Damit wird Sourcecode mehrfach verwendbar
und übersichtlicher.
Normalerweise wird ein Verwender der
Klasse X aber wollen, daß wir die "Funktion" der
Member-Funktionen im Kern nicht verändern.
Eine gewünschte "Ausgabe
rückwärts" sollte daher zu einer neuen Member-Funktion void
X::AusgabeRückwaerts( ) führen.
Der innere Aufbau interessiert den
Anwender
hierbei nicht. Er möchte nur, dass es "funktioniert".
Der Anwender, der rückwärts
lesen üben möchte, benötigt natürlich eine
Eingabemöglichkeit. Daher muß die Klasse weiter wachsen.
Wir benötigen hierzu z.B. die
Funktion
void X::EingabeText(char*).
Das könnte auch versteckt ablaufen
mit einer "polymorphen" Funktion void X::SetzeText( ). Der
gleiche Name, aber ohne Parameter.
Das Einlesen eines Strings könnte
dann intern erfolgen.