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].
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())
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
- Übernimm das obenstehende Beispiel und teste es
- 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:
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())
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.
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.
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:
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:
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.
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())
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)
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)
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)
Da Namen in Python (und anderen Programmiersprachen) keine Leerschläge beinhalten dürfen, können wir nicht
set status()
schreiben. Bei Methoden schreiben wir stattdessensetStatus()
(CamelCase). Bei normalen Unterprogrammen würden wirset_status()
schreiben (snake_case) ↩︎