====== Datenkapselung ======
Eine Klasse ist ein Datentyp und ein Objekt eine Variable, deren Datentyp eine Klasse ist. Deshalb kann man ein Objekt wie eine Variable eines einfachen Datentyps definieren. Es enthält dann alle Elemente der Klasse. Diese kann man unter dem Namen des Objekts ansprechen, auf den ein Punktoperator „** . **“ und der Name des Elements folgt.
**Beispiel 1:** Mit der Klasse C2DPunkt erhält man durch die folgenden Definition ein Objekte p dieser Klasse:
C2DPunkt p;
Diese Objekte enthalten dann die Datenelemente
''p.x'' und ''p.y''
Solange eine Klasse keine virtuellen Elementfunktionen oder statischen Elemente (mehr darüber später) enthält, ergibt sich der Speicherplatzbedarf für ein Objekt nur aus dem für seine Datenelemente. Die Elementfunktionen tragen nicht dazu bei. Falls der Compiler für die Elemente eines Objekts nicht mehr Platz als notwendig reserviert, belegt ein Objekt genauso viel Speicherplatz wie alle Datenelemente seiner Klasse zusammen:
''sizeof(C2DPunkt) = sizeof(double) + sizeof(double) = 16''
===== Drei verschiedene Zugriffsrechte =====
Mit den Zugriffsrechten **private, protected** und **public** kann man für jedes Klassenelement explizit festlegen, ob man über ein Objekt darauf zugreifen kann.
Diese //Spezifizierer// definieren ab ihrer Angabe einen Abschnitt mit Zugriffsrechten, die für alle folgenden Elemente bis zum nächsten solchen Spezifizierer oder bis zum Ende der Klasse gelten. Ein Element aus einem //private, protected// oder
//public// Abschnitt heißt auch //private, protected// oder //public// Element.
* Ein **//public//** Element kann ohne Einschränkungen angesprochen werden, d.h. sowohl über ein Objekt als auch in einer Elementfunktion der Klasse.
* Ein **//private//** Element kann nur in einer Element- oder friend-Funktion der Klasse angesprochen werden, aber nicht über ein Objekt.
* Ein **//protected//** Element kann wie ein privates Element und außerdem noch in einer abgeleiteten Klasse angesprochen werden.
Ohne die Angabe eines Zugriffsrechts sind alle Elemente einer mit //class// definierten Klasse //private//, während alle Elemente einer mit //struct// definierten Klasse //public// sind. Dieses voreingestellte Zugriffsrechte ist der einzige Unterschied zwischen einer mit //class// und einer mit //struct// definierten Klasse.
**Beispiel 2a:** Alle Elemente der Klassen C0 und C1 haben dieselben Zugriffsrechte:
class C0
{int x;
int f() { return x; }
};
struct C1
{private:
int x;
int f() { return x; }
};
Jeder Zugriff auf diese Elemente über ein Objekt führt zu einer Fehlermeldung:
void test(C0 a, C1 b)
{
a.x=1;//Fehler: Zugriff auf 'C0::x' nicht möglich
a.f();//Fehler: Zugriff auf 'C0::f()' nicht mögl.
b.x=1;//Fehler: Zugriff auf 'C1::x' nicht möglich
b.f();//Fehler: Zugriff auf 'C1::f()' nicht mögl.
}
Mit dem Zugriffsrecht //public// sind alle diese Zugriffe zulässig:
class C0
{public:
int x;
int f() { return x; }
};
struct C1
{int x;
int f() { return x; }
};
Bemerkung 1: Der Compiler prüft das Zugriffsrecht auf ein Klassenelement allerdings nur bei der Verwendung seines Namens. Wenn man den Speicherbereich eines private Elements über seine Adresse (via Zeiger) anspricht, kann man die Zugriffsrechte umgehen. Von solchen Manipulationen kann aber nur abgeraten werden.
Bemerkung 2: Eine Klasse kann eine **beliebige Anzahl von Abschnitten** mit verschiedenen Zugriffsrechten in einer **beliebigen Reihenfolge** enthalten. Die Reihenfolge der Abschnitte ist dabei ohne Bedeutung. Es wird aber gelegentlich empfohlen, sie in der Reihenfolge //public, protected// und //private// aufzuführen. Dann kommen die Elemente zuerst, die für einen Anwender der Klasse von Bedeutung sind, und dieser muss dann den Rest der Klasse überhaupt nicht mehr anschauen, der nur für einen Entwickler von abgeleiteten Klassen (//protected// Elemente) oder dieser Klasse (//private// Elemente) von Bedeutung ist.
**Beispiel 2b:** Die beiden Klassen C1 und C2 sind gleichwertig. Oft werden die verschiedenen Abschnitte wie bei C1 in der Reihenfolge //private, protected// und //public// aufgeführt. Wenn man sie aber wie bei C2 in der umgekehrten Reihenfolge anordnet, kommen die Elemente, die für das breiteste Publikum interessant sind, am Anfang:
class C1
{int x;
public:
int f(C p);
};
class C2
{public:
int f(C p);
private:
int x;
};
===== Vorteile der Datenkapselung =====
Ein **Benutzer** einer Klasse ist dadurch charakterisiert ist, dass er eine Variable des Klassentyps definiert (ein Objekt) und dann auf ihre Elemente zugreift (z.B. Elementfunktionen aufruft). Da man über ein Objekt nur auf die public Elemente zugreifen kann, werden diese auch als **Schnittstelle** der Klasse bezeichnet.
**Beispiel 3a:** Die Schnittstelle der Klasse //Datum_1// besteht aus den Datenelementen //Tag, Monat// und //Jahr//, und die der Klasse //Datum_2// aus den Funktionen setze, //Tag, Monat// und //Jahr//. Über ein Objekt der Klasse //Datum_2// ist kein Zugriff auf die Elemente //Tag_, Monat_// und //Jahr_// möglich.
class Datum_1
{public:
int Tag, Monat, Jahr;
};
class Datum_2
{
public:
bool gueltigesDatum(int Tag,int Monat,int Jahr)
{int MaxTag=31;
if ((Monat==4)||(Monat==6)||(Monat==9)||(Monat==11)) MaxTag=30;
else if (Monat==2)
{bool Schaltjahr =((Jahr%4 == 0) && (Jahr%100 != 0)) || (Jahr%400 == 0);
if (Schaltjahr) MaxTag=29;
else MaxTag=28;
}
return ((1<=Monat) && (Monat<=12) && (1<=Tag) && (Tag<=MaxTag));
}
void setze(int Tag, int Monat, int Jahr)
{if (gueltigesDatum(Tag, Monat, Jahr))
{Tag_=Tag;
Monat_=Monat;
Jahr_=Jahr;
}
else Fehlermeldung("Ungültiges Datum");
}
int Tag() { return Tag_;}
int Monat() { return Monat_;}
int Jahr() { return Jahr_;}
private:
int Tag_, Monat_, Jahr_;
};
Obwohl es auf den ersten Blick unsinnig erscheinen mag, den Zugriff auf Datenelemente zu beschränken (**Datenkapselung, information hiding**), können Schnittstellen ohne Datenelemente gravierende Vorteile haben:
* Das Zugriffsrecht //private// ermöglicht die **Trennung** der **Implementation** einer Klasse von ihrer **Schnittstelle**. Dann kann ein Benutzer die Klasse auch nach einer Änderung ihrer Implementation wie bisher verwenden, ohne seine Aufrufe zu ändern.
**Beispiel 3b:** Um mit Kalenderdaten rechnen zu können, stellt man ein Datum oft durch die Anzahl der Tage seit einem bestimmten Stichtag dar. Bei einer Klasse wie //Datum_2// kann man die interne Darstellung und die Implementation der Funktionen ändern, so dass ein Anwender den bisher geschriebenen Code weiterverwenden kann. Bei einer Klasse wie //Datum_1// ist das dagegen nicht möglich.
Obwohl solche Änderungen nach einer vollständigen Problemanalyse eigentlich nicht vorkommen dürften, sind sie in Praxis nicht selten: Oft erkennt man erst während der Entwicklung eines Systems alle Anforderungen, bzw. nach der Fertigstellung, dass Algorithmen zu langsam sind und optimiert werden müssen. Bei großen Projekten ändern sich die Anforderungen oft während ihrer Realisierung (z.B. durch neue Gesetze).
* Bei privaten Datenelementen ist der Bereich im Quelltext eines Programms, in dem sie verändert werden können, kleiner als bei public Elementen. Je kleiner dieser Bereich ist, desto kleiner ist der Bereich, in dem sie einen Fehler verursachen können und in dem man nach seiner Ursache suchen muss.
**Beispiel 3c:** In der Klasse //Datum_2// können die Datenelemente nur in der Funktion setze verändert werden. Falls ein Objekt dieser Klasse ein ungültiges Datum darstellt, muss die Ursache dieses Fehlers in der Funktion setze sein. Mit der Klasse //Datum_1// kann ein ungültiges Datum dagegen durch jedes Objekt dieser Klasse verursacht werden.
* Oft müssen verschiedene Datenelemente einer Klasse immer in einer bestimmten **Beziehung** zueinander stehen. Wenn diese Datenelemente //private// sind und der Entwickler jede //public// Elementfunktion so schreibt, dass diese Beziehung nach ihrem Aufruf gilt, hat der Anwender keine Möglichkeit, eine **Konsistenzbedingung** zu verletzen.
**Beispiel 3d:** Da die Datenelemente der Klasse Datum_2 nur in der Funktion setze verändert werden können, und in dieser Funktion die Konsistenz der Daten geprüft wird, ist sichergestellt, dass der Anwender nicht mit inkonsistenten Daten arbeiten kann.
Mit //public// Datenelementen oder globalen Variablen kann der Entwickler dem Anwender dagegen keine solche Garantie geben. **Datenkapselung** bietet dem Entwickler also die Möglichkeit, den Zugriff auf die Datenelemente so zu beschränken, dass der Anwender überhaupt keine Möglichkeit hat, solche **Fehler** zu machen.
* Bei einem private Datenelement kann man gezielt festlegen, ob es nur gelesen oder auch geändert werden kann, indem man entsprechende Elementfunktionen zur Verfügung stellt.
**Beispiel 3e:** In der Klasse //Datum_2// können die einzelnen Datenelemente nur gelesen, aber nicht geändert werden.
Deshalb wird oft empfohlen, **alle Datenelemente** einer Klasse //private// zu deklarieren, und den Zugriff auf die //private// Elemente nur über **//public// Elementfunktion** zur ermöglichen. Falls durch den Zugriff auf Datenelemente eine Konsistenzbedingung verletzt werden kann, ist das aber nicht nur eine gut gemeinte Empfehlung, sondern ein Muss.
Oft sind auch **//private// Elementfunktionen sinnvoll**. Das sind meist Hilfsfunktionen, die nur in den Elementfunktionen aufgerufen werden, aber einem Benutzer der Klasse ausdrücklich nicht zur Verfügung stehen sollen.