UML-Klassendiagramm: Sichtbarkeitsmodifikatoren
https://bildung.social/@oerinformatik/
https://oer-informatik.de/uml-klassendiagramm-sichtbarkeitsmodifikatoren
tl/dr; (ca. 4 min Lesezeit): Kapselung ist ein Konzept der objektorientierten Programmierung (OOP), das verhindern soll, dass Anpassungen des Codes zu Inkonsistenzen führen. Der Zustand der Objekte soll nicht direkt änderbar sein, sondern nur über Methoden, die ggf. weitere Anpassungen vornehmen können: Getter und Setter. (Zuletzt geändert am 09.11.2024)
Dieser Text ist ein Teil der Infotext-Serie zu UML-Klassendiagrammen:
Information Hiding Principle (IHP)
Klassen sollten in der OOP so wenig wie möglich nach außen veröffentlichen. Attribute und Methoden, die lediglich den inneren Zustand eines Objekts betreffen oder nur innerhalb der Objekte benötigt werden, sollten von außen nicht erreichbar sein. Dadurch wird zum einen sichergestellt, dass bei Code-Anpassungen der inneren Struktur der Klasse keine Aufrufe von außen korrumpiert werden. Zum anderen erhält man so definierte Schnittstellen, über die Objekte nach außen kommunizieren. Plausibilisierungen, Zugriffskontrolle und Protokollierung beispielsweise können so viel einfacher umgesetzt werden.
Daher gibt es die Möglichkeit, Attribute und Methoden (beides sind Member einer Klasse) in ihrer Sichtbarkeit und im Zugriff einzuschränken. In einigen Programmiersprachen kann man lediglich Member verstecken, sie sind also nicht ohne weiteres zu finden. Der Zugriff ist aber weiterhin möglich. In anderen Programmiersprachen lässt sich der Zugriff komplett einschränken. Daher verwende ich hier “Sichtbarkeitsmodifikator” und “Zugriffsmodifikator” synonym und beschreibe mit beiden Begriffen das gleiche Konzept (in unterschiedlichen Programmiersprachen).
Zugriffsmodifikatoren
In der UML unterscheidet man vor allem Member, die nur innerhalb der Instanz einer Klasse zugreifbar und sichtbar sind (private, gekennzeichnet mit “-”) und Member, die auch von außen aufgerufen werden können (public, gekennzeichnet mit “+”). Bei Membern kann es sich sowohl um Attribute als auch um Methoden handeln. Weitere Varianten, die deutlich seltener benutzt werden, sind die Sichtbarkeit innerhalb einer Vererbungsstruktur (protected, “#”) und innerhalb von Namensräumen / Packages (package, “~”).

Eine verbreitete Art, die Sichtbarkeit von Membern über die Zugriffsmodifikatoren private und public zu konfigurieren, ist die Kapselung von Attributen.
Kapselung
Sinn und Zweck der Kapselung ist es, den inneren Zustand von Objekten zu verstecken. Hierbei werden Attribute versteckt (private) und nur durch öffentliche Zugriffsmethoden (Getter- und Settermethoden, public) von Außen verfügbar gemacht. Versteckt werden die Attribute, in dem sie über den Zugriffsmodifikator private nur innerhalb des Objekts zugreifbar sind. Je nachdem, ob die Attribute von Außen les- oder änderbar sein sollen werden öffentliche (public) Getter- und/oder Settermethoden erstellt:

In Java wäre die Implementierung folgendermaßen:
class Konto{
private String iban;
public String getIban() {
return iban;
}
public void setIban(String iban) {
this.iban = iban;
} }Der direkte Zugriff auf das Attribut von Außen wäre dann nicht mehr möglich (Auszug aus der Java-Shell):
Konto konto1 = new Konto();
| konto1 ==> Konto@4671e53b
konto1.iban="DE123";
| Error:
| iban has private access in Konto
| konto1.iban="DE123";
| ^---------^Über die Getter- und Settermethoden kann jedoch zentral auf das Attribut zugegriffen werden.
Durch Kapselung wäre es möglich, dafür zu sorgen, dass nur gültige IBAN-Werte übergeben werden können. Ein zentraler Setter könnte absichern, dass die Gültigkeit einer IBAN immer geprüft ist.
An anderes Beispiel wäre eine Überprüfung des Kontostands, bevor Abhebungen den aktuellen Kontostand verändern. Ein Setter könnte prüfen, ob der resultierende Betrag kleiner als ein Dispolimit ist, und andernfalls die Anpassung verhindern (beispielsweise eine Exception werfen).
Soll beispielsweise eine Telefonnummer immer in internationaler Form ausgegeben werden, so kann es reichen, einen Getter anzupassen. Die Repräsentationsart, in welcher Form der Zustand eines Objekts nach außen dringt, kann somit auch nachträglich angepasst werden, ohne, dass der Zustand selbst veränder werden muss.
Zugriffsmodifikation innerhalb von Namensräumen (Packages)
Die UML stellt zur Gliederung von Klassen Namensräume (Packages) zur Verfügung, in den meisten Programmiersprachen sind die unterschiedlichen Module in Packages organisiert. Um eine Möglichkeit zu schaffen, dass Member nicht komplett öffentlich zugreifbar sind, sondern nur innerhalb eines Namensraums, gibt es einen weiteren Zugriffsmodifikator package:

Im Beispiel oben ist der Konstruktor Konto() nur innerhalb des Paketes CRM nutzbar. Die Klasse Buchungen nutzt zwar ein Konto (und verfügt auch über ein Attribut vom Typ Konto), kann dieses jedoch selbst nicht erzeugen, da der Konstruktor paketsichtbar ist (~Konto()), und nicht für den Namensraum (also das package) Abrechnung, in dem Buchungen ist. Buchungen ist also darauf angewiesen, dass innerhalb des Pakets CRM ein Konto erzeugt wird und per Setter gesetzt wird. Danach kann Buchungen aber auf die public-Methoden von Konto zugreifen.
In Java ist Paketsichtbarkeit die default-Einstellung, daher werden dafür gar keine Sichtbarkeitsmodifizierer angegeben. Im Beispiel oben steht anstelle von public, private oder protected also nichts vor dem Konstruktor Konto():
In Python gibt es kein Konstrukt für Paketsichtbarkeit.
Zugriffsmodifikation innerhalb Vererbungshierarchien
Es besteht darüber hinaus noch eine vierte Möglichkeit, die Sichtbarkeit von Membern einzuschränken, hierzu müssen wir kurz etwas vorgreifen: Mit private annotierte Attribute und Methoden sind auch für Instanzen erbender Klassen (siehe übernächsten Artikel) nicht zugreifbar. Im Beispiel unten verfügt eine Instanz von PrivatKonto zwar intern über die Information iban, kann aber selbst nicht darauf zugreifen, das es als private gekennzeichnet ist. Die erbenden Klassen PrivatKonto und GeschaeftsKonto müssen also die Getter und Setter nutzen, um iban anzusprechen.
Als Zwischenstufe zwischen private und public kommt hier protected (#) ins Spiel: So annotierte Member sind nur innerhalb von Instanzen der eigenen Klasse oder innerhalb abgeleiteter Klassen der Vererbungsstruktur (Subklassen und Subsubklassen) sichtbar/ zugreifbar. Auf das Attribut kontostand können PrivatKonto und GeschaeftsKonto also auch ohne Getter und Setter direkt zugreifen, externe Instanzen aber nicht.

Im Java-Quellcode wird analog zur UML auch das Keyword protected genutzt. Für das Beispiel oben sieht der Codeauszug etwa so aus:
Python verfügt über kein Konzept, das Sichtbarkeiten auf Vererbungsstrukturen begrenzt.
Übersicht der Zugriffsmodifikatoren
Im Ganzen gibt es vier Sichtbarkeitsmodifikatoren wird im Rahmen der Vererbung / der Packages genauer eingegangen. Die Umsetzung der Berechtigungen in Java ist in folgender Tabelle wiedergegeben, für Python gibt es keine Entsprechungen:
| Umsetzung in Java | Darstellung im UML- Klassen- diagramm |
sieht eigene Klasse |
sieht Klasse im gleichen Paket |
sieht Unterklasse im anderen Paket |
sieht Klasse in anderem Paket |
|---|---|---|---|---|---|
privat:private String iban |
-iban |
ja | nein | nein | nein |
öffentlich: public double getKontostand() |
+getKontostand(): double |
ja | ja | ja | ja |
geschütztprotected double kontostand |
#kontostand |
ja | ja | ja | nein |
paketsichtbar:Konto() |
~Konto() |
ja | ja | nein | nein |
Das ganze Beispiel als Java-Code
Wer versucht hat, das ganze per UML geplante OOP-Programm in Java nachzuvollziehen, sollte etwa diesen Quelltext am Ende vor sich haben und ausführen können:
Eine Implementierung der kompletten Beispiele oben im Java erfordert drei Dateien - da in Java in jeder Datei immer nur eine öffentliche (public) Klasse sein darf:
Die Datei Investmentrechner.java erzeugt Objekte der beiden anderen Klassen und veknüpft diese:
package de.oerinformatik.crm;
import de.oerinformatik.abrechnung.Buchungen;
public class Investmentrechner {
public static void main(String[] args) {
Konto hannesKonto = new Konto("DE123");
Konto tobiasKonto = new Konto("456", "10010010");
Buchungen meineBuchungen = new Buchungen();
meineBuchungen.setKonto(tobiasKonto);
System.out.println(Konto.listeStatusAllerKonten());
}
}Die Datei Konto.java enthält neben Konto auch die beiden Spezialisierungen aus obigem Beispiel:
package de.oerinformatik.crm;
import java.util.ArrayList;
public class Konto{
private String iban;
protected double kontostand =0.0;
static ArrayList<Konto> kontenListe = new ArrayList<>();
public String getIban() {
return iban;
}
public void setIban(String iban) {
this.iban = iban;
}
public boolean einzahlen(double betrag) {
kontostand = kontostand + betrag;
return true;
}
public boolean auszahlen(double betrag) {
if (kontostand - betrag > 0){
kontostand = kontostand - betrag;
return true;
}else{
return false;
}
}
static String listeStatusAllerKonten(){
String ausgabe ="";
for (Konto einKonto: kontenListe){
ausgabe = ausgabe + "\n" + einKonto.toString();
}
return ausgabe;
}
String toString(){
return "KontoNr "+this.iban+" Kontostand: " + Double.toString(this.kontostand);
}
Konto(String iban){
this.iban = iban;
kontenListe.add(this);
}
Konto(String ktn, String blz){
this.iban = "DE__"+blz+ktn;
kontenListe.add(this);
}
}
class PrivatKonto extends Konto{
private String vorname;
private String nachname;
public PrivatKonto(String iban, String vorname, String nachname){
super(iban);
this.vorname = vorname;
this.nachname = nachname;
}
public String getName(){
return this.vorname + " " + this.nachname;
}
}
class GeschaeftsKonto extends Konto {
private String firmenname;
private double verfuegungsrahmen;
public double getVerfuegungsrahmen() {
return verfuegungsrahmen;
}
public void setVerfuegungsrahmen(double verfuegungsrahmen) {
this.verfuegungsrahmen = verfuegungsrahmen;
}
public GeschaeftsKonto(String iban, String firmenname, double verfuegungsrahmen) {
super(iban);
this.firmenname = firmenname;
this.setVerfuegungsrahmen(verfuegungsrahmen);
}
@Override
public boolean auszahlen(double betrag) {
if (betrag > verfuegungsrahmen) {
return false;
} else {
return super.auszahlen(betrag);
}
}
}
class SparKonto extends PrivatKonto{
private double zinssatz;
public double getZinssatz() {return zinssatz;}
public void setZinssatz(double zinssatz) {this.zinssatz = zinssatz;}
public SparKonto(String iban , String vorname, String nachname, double startzins ){
super(iban, vorname, nachname);
this.setZinssatz(startzins);
}
public void verzinse(){
this.kontostand = this.kontostand * (1+zinssatz);
}
}Die Datei Buchungen.java liegt in einem anderen Paket wie die beiden anderen Klassen (in Java wird sie in einen anderen Ordner kopiert):
Nächste Bestandteile des UML-Klassendiagramms:
In weiteren Artikel geht es um andere Eigenschaften des UML-Klassendiagramms, als Nächstes wird auf Objektbeziehungen (Assoziation, Aggregation, Komposition) eingegangen.
Links und weitere Informationen
Hinweis zur Nachnutzung als Open Educational Resource (OER)
Dieser Artikel und seine Texte, Bilder, Grafiken, Code und sonstiger Inhalt sind - sofern nicht anders angegeben - lizenziert unter CC BY 4.0. Nennung gemäß TULLU-Regel bitte wie folgt: “UML-Klassendiagramm: Sichtbarkeitsmodifikatoren” von oer-informatik.de (H. Stein), Lizenz: CC BY 4.0. Der Artikel wurde unter https://oer-informatik.de/uml-klassendiagramm-sichtbarkeitsmodifikatoren veröffentlicht, die Quelltexte sind in weiterverarbeitbarer Form verfügbar im Repository unter https://gitlab.com/oer-informatik/uml/umlklasse. Stand: 09.11.2024.
[Kommentare zum Artikel lesen, schreiben] / [Artikel teilen] / [gitlab-Issue zum Artikel schreiben]
