Skip to content

Datenkapselung

Objektorientiertes Programmieren

Definition: Datenkapselung

Datenkapselung (encapsulation) bezeichnet in der Informatik das bewusste Verbergen von Daten vor dem Zugriff von aussen. Der Zugriff von aussen wir durch eine Schnittstelle geregelt.

Beim Objektorientierten Programmieren heisst das, dass wir gewisse Eigenschaften und Methoden nur innerhalb der Klasse verwenden und den Zugriff von aussen beschränken. Z.B. könnte unser Sheep ein Geburtsdatum haben. Wir wollen aber nicht, dass dieses von aussen abgerufen werden kann oder gar geändert wird. Dafür könnte man von aussen z.B. eine Abfrage nach dem Alter des Schafes zulassen, die dann intern natürlich das Geburtsdatum verwendet.

Oder aber die Eigenschaft hunger: diese sollte man nicht einfach auf False stellen können. Damit das Schaf keinen Hunger mehr hat, muss es essen. Dafür haben wir ja die Methode eat.

Im Sinne der Datenkapselung können wir also Eigenschaften von Objekten verstecken und den Zugriff darauf über Methoden regeln. Dies ermöglicht uns ein modulares Design: Klassen können umprogrammiert/ersetzt werden, solange die Schnittstelle gleich bleibt!

Konstruktor

Eine Art der Datenkapselung haben wir bereits kennengelernt – nämlich beim Konstruktor: Jedes Schaf hat zu Beginn Hunger und muss ein Anfangsgewicht haben. Dabei wissen wir nicht, wie die Klasse diese Werte intern abspeichert. Dies ist nicht sichtbar, sondern eben gekapselt.

Getter und Setter

Das direkte Setzen der Eigenschaften mit der Punktnotation birgt Gefahren und deshalb vermeidet man wenn immer möglich solche direkten Zugriffe. Eleganter geht der Zugriff mit sogenannten Set- und Get-Methoden.

Als Konvention benennt man diese immer gleich: set resp. get und dann der Name der Eigenschaft in sogenanntem CamelCase[1].

python
class Sheep:
    
    def __init__(self):
        self.status = "Bin gerade erst erzeugt worden!"
    
    def setStatus(self, neuerStatus):
        self.status = neuerStatus

    def getStatus(self):
        return self.status
    
    def befinden(self):
        print(self.getStatus())
python
from sheep import Sheep

shaun = Sheep()

shaun.befinden()
shaun.setStatus("Kissenschlacht mit den Schweinen!")
shaun.befinden()
Zeile 6
die Setter-Methode der Eigenschaft status
Zeile 7
die Getter-Methode der Eigenschaft status

Dank dem indirekten Setzen der Eigenschaften über den Setter können wir durch Überprüfen der Werte nun sicherstellen, dass nur gültige Werte gesetzt werden.

Aufgabe: Getter/Setter

  1. Übernimm das obenstehende Beispiel und teste es
  2. erweitere die setStatus-Methode, so dass nur Befehle, also Strings die mit einem Ausrufezeichen enden zugelassen werden.
Lösung: Getter/Setter

In der Methode setStatus überprüfen wir den neuen Status bevor wir ihn übernehmen:

python
class Sheep:
    
    def __init__(self):
        self.status = "Bin gerade erst erzeugt worden!"
    
    def setStatus(self, neuerStatus):
        if neuerStatus.endswith("!"):
            self.status = neuerStatus
        else:
            print("Error: Status wird nicht geändert!")

    def getStatus(self):
        return self.status
    
    def befinden(self):
        print(self.getStatus())
python
from sheep import Sheep

shaun = Sheep()

shaun.setStatus("Kissenschlacht mit den Schweinen!")
shaun.befinden()

shaun.setStatus("kein Ausrufezeichen am Schluss")
shaun.befinden()

Sichtbarkeit

Um Eigenschaften und Methoden zu verstecken, benennt man diese in Python gemäss der folgenden Konvention:

protected

«Geschützte» Eigenschaften und Methoden (protected) werden mit einem Underscore beginnend benannt.

python
class Animal():
    def __init__(self):
        self._birthday = "today"

class Sheep(Animal):
    def sayBirthday(self):
        print(self._birthday)

shaun = Sheep()
shaun.sayBirthday()

private

«Private» Eigenschaften und Methoden (private) werden mit einem doppelten Underscore beginnend benannt. Diese stehen in den Unterklassen nicht zur Verfügung.

python
class Animal():
    def __init__(self):
        self.__birthday = "today"

class Sheep(Animal):
    def sayBirthday(self):
        print(self.__birthday)

shaun = Sheep()
shaun.sayBirthday()

Das obenstehende Skript gibt einen Fehler: __birthday ist eine private Eigenschaft der Klasse Animal. In der Unterklasse Sheep versuchen wir darauf zuzugreifen – das klappt nicht!

Hingegen ist der Zugriff auf __birthday aus der Klasse Animal auch für ein Objekt vom Typ Sheep möglich:

python
class Animal():
    def __init__(self):
        self.__birthday = "today"
        
    def sayBirthday(self):
        print(self.__birthday)        

class Sheep(Animal):
    pass

shaun = Sheep()
shaun.sayBirthday()

Dies klappt weil jedes Sheep auch ein Animal ist (Polymorphie).

Aufgabe: Harry Potter

Wir erweitern die Harry-Potter-Aufgabe aus dem letzten Kapitel und bauen dort Datenkapselung ein. Im Diagramm ist neu die Sichtbarkeit der Eigenschaften und Methoden festgehalten:

+
public – öffentlich, also weder protected noch private
#
protected – Zugriff von Unterklasse möglich
-
private – Zugriff nur von eigener Klasse

Zudem wurden für einige Eigenschaften Getter- und Setter-Methoden eingebaut.

Baue also diese Änderungen ein und teste das Programm mit dem folgenden Skript:

python
from student import Student
from teacher import Teacher

snape = Teacher("Severus Snape", 32)
harry = Student("Harry Potter", 11)
hermione = Student("Hermione Granger", 11)

snape.addStudent(harry)
snape.addStudent(hermione)

for grade in [5, 4.5, 3]:
    harry.addGrade(grade)
    
for grade in [6, 6, 6]:
    hermione.addGrade(grade)

for student in snape.getStudents():
    student.sayName()
    print(student.getAverage())
Lösung: Harry Potter

Bei Person sind die Attribute age und name protected – die werden in person.py mit einem Underscore versehen. listOfStudents und grades in den Unterklassen sind private und erhalten einen doppelten Underscore.

python
from student import Student
from teacher import Teacher

snape = Teacher("Severus Snape", 32)
harry = Student("Harry Potter", 11)
hermione = Student("Hermione Granger", 11)

snape.addStudent(harry)
snape.addStudent(hermione)

for grade in [5, 4.5, 3]:
    harry.addGrade(grade)
    
for grade in [6, 6, 6]:
    hermione.addGrade(grade)

for student in snape.getStudents():
    student.sayName()
    print(student.getAverage())
python
class Person:

    def __init__(self, name, age):
        self._name = name
        self._age = age
        
    def _getAge(self):
        return self._age
        
    def sayName(self):
        print(self._name)
python
from person import Person

class Student(Person):
    
    def __init__(self, name, age):
        super().__init__(name, age)
        self.__grades = []
        
    def getAverage(self):
        return sum(self.__grades)/len(self.__grades)
    
    def addGrade(self, grade):
        self.__grades.append(grade)
python
from person import Person

class Teacher(Person):
    
    def __init__(self, name, age):
        super().__init__(name, age)
        self.__listOfStudents = []
        
    def addStudent(self, student):
        self.__listOfStudents.append(student)
        
    def getStudents(self):
        return self.__listOfStudents

Zusatzaufgabe

Erweitere das Programm mit deinen Ideen:

  • findest du weitere Unterklassen von Person?
  • findest du weitere Beziehungen zwischen den Klassen?
  • wie könnte man die Klassen erweitern? (Eigenschaften, Methoden)

  1. Da Namen in Python (und anderen Programmiersprachen) keine Leerschläge beinhalten dürfen, können wir nicht set status() schreiben. Bei Methoden schreiben wir stattdessen setStatus() (CamelCase). Bei normalen Unterprogrammen würden wir set_status() schreiben (snake_case) ↩︎

Gymnasium Kirchenfeld, fts & lem