Dies ist eine alte Version des Dokuments!
Objektorientierte Programmierung
1. Grundlagen
1.1 Konzept
Wenn wir bisher ein Programm geschrieben haben, war der Ansatz „prozedural“ oder „imperativ“, d.h. wir haben uns überlegt, aus welchen Einzelschritten ein Algorithmus besteht und haben dann ein Programm erstellt, welches diese Schritte durchgeführt hat. Mithilfe von Funktionen kann man ein solches Programm strukturieren und Redundanz vermeiden.
Die objektorientierte Programmierung geht von einem anderen Ansatz aus: Ein Programm besteht nicht aus einem Ablauf von Anweisungen und Funktionen, sondern aus Objekten. Diese Objekte haben bestimmte Eigenschaften und Methoden und sie stehen miteinander in Verbindung. Oftmals wird das Programm dann durch Events gesteuert (z.B. Tastaturanschläge, Mausklicks etc.).
1.2 Begriffe
| Klasse vs. Objekt | Eine Klasse ist ein allgemeiner Bauplan für konkrete Objekte. So kann man z.B. eine Klasse Auto definieren, aus welcher man die beiden Objekte Ferrari oder VW ableiten kann. Die Objekte (oder auch Instanzen genannt) sind dann konkrete Exemplare dieser Klasse. |
| Eigenschaften | Die Objekte besitzen bestimmte Eigenschaften (oder Attribute genannt), dies sind im Prinzip die Variablen, welche die konkreten Objekte beschreiben. Z.B. Kilometerstand, Tankfüllung, MaxSpeed, VerprauchPro100km |
| Methoden | Die Methoden entsprechen den Funktionen in der üblichen Programmierung. Sie operieren auf den Objekten/Klassen und auf deren Eigenschaften. So könnte man beispielsweise eine Methode fahren(km) haben, oder eine Methode auftanken(liter) |
Definition einer Klasse und eines Objektes in Python
class Auto(): # eine Klasse Auto definieren
pass
Ferrari = Auto() # ein neues Auto-Objekt erzeugen
Instanzattribute vs. Klassenattribute und Instanzmethoden vs. Klassenmethoden
Man muss nun unterscheiden, ob eine Eigenschaft zu einem konkreten Objekt gehört (Instanzattribut, Instanzvariable), oder ob sie für die gesamte Klasse gleich bleibt (Klassenattribut, Klassenvariable). Bei der Klasse Auto könnte man z.B. das Klassenattribut Anzahl_Raeder haben. Da dies für alle Objekte der Klasse Auto gleich sein wird (4) und nicht vom konkreten Objekt abhängt, muss es nur einmal unter der KLasse Auto gespeichert werden.
Die Attribute Kilometerstand,Tankfuellung, MaxSpeed, Verbrauch etc. wären Instanzattribute, sie gehören zu einer bestimmten Instanz und beschreiben ein konkretes Objekt.
Genauso ist es bei den Methoden (Funktionen): Instanzmethoden verwenden Instanzvariablen und sind deshalb an ein konkretes Objekt gebunden. Klassenmethoden hingegen hängen nicht vom konkreten Objekt ab, sondern nur von der Klasse und dürfen somit auch nicht auf die Instanzattribute zugreifen. Man kann sich z.B. eine Hilfsfunktion steuerwert vorstellen, welche den Startwert des Autos und das aktuelle Alter des Autos als Parameter übernimmt und dann den aktuellen Steuerwert berechnet. Dies könnte man als Klassenmethode implementieren. Man könnte natürlich auch die Instanzvariablen startwert und alter einführen, auf welche die Funktion steuerwert zugreift - dann wäre die Methode steuerwert eine Instanzmethode.
1.3 Diagramm der Klasse Auto mit den Instanzen Ferrari und VW
| Konstruktor | Wenn man aus einer Klasse ein konkretes Objekt (d.h. eine Instanz) erstellt, dann wird eine spezielle Methode aufgerufen, der Konstruktor. In Python ist dies die init()-Methode. Im Konstruktor werden die Objektvariablen für das konkrete Objekt gesetzt und andere Dinge erledigt, die bei der Erstellung eines Objektes gemacht werden müssen. |
| self | Wenn man Instanzmethoden definiert, muss man jeweils zusätzlich den Parameter self übergeben. Dies ist eine Referenz auf das aktuell bearbeitete Objekt. Mit self.tankfuellung kann man dann z.B. auf die Tankfüllung des aktuell betrachteten Autos zugreifen. Wenn man also eine Instanzmethode definiert, hat diese immer einen Parameter mehr (self), als wenn man sie aufruft (!). |
| Vererbung | Es ist möglich, dass man eine „Kindklasse“ definiert, welche alles von der Oberklasse (d.h. von der Vater- bzw. Mutterklasse) erbt. So könnte z.B. die Klasse „Säugetier“ eine Unterklasse der Klasse „Tier“ sein. Ruft man z.B. eine Methode eines Säugetier-Objektes auf, so wird diese auch funktionieren, wenn sie eigentlich in der Klasse „Tier“ definiert wurde (sie wurde vererbt). Will man in der Kindklasse (z.B. bei der Definition des Konstruktors) die Methode der Vaterklasse aufrufen, so kann man super() verwenden, um auf die Vaterklasse zuzugreifen. |
| Datenkapselung | Definiert man Instanzvariablen mit „normalen“ Namen, so kann man von aussen darauf zugreifen, die Variablen sind „public“. Definiert man Instanzvariablen mit einem anführenden Unterstrich, so zeigt man, dass diese eigentlich nicht für den Gebrauch von aussen gedacht sind (dies ist nur eine Konvention). Die Variablen sind „protected“ (es besteht aber keinerlei Schutz). Definiert man Instanzvariablen mit doppelten Underscores, so weiss Python, dass diese nur innerhalb des Objektes verwendet werden sollen. Diese sind „private“ und man kann von aussen nicht darauf zugreifen (bzw. nur mit einem Trick). Man muss Getter- und Setter-Methoden schreiben, um diese Variablen von aussen zu manipulieren. |
2. Umsetzung in Python
2.1 Eine Klasse Auto
class Auto():
anzahl_Raeder = 4
def __init__(self, ps, km, tank, verbrauch, maxspeed,alter, neupreis):
self.PS = ps
self.Kilometerstand = km
self.Tankfuellung = tank
self.MaxSpeed = maxspeed
self.Verbrauch = verbrauch
self.Alter = alter
self.Neupreis = neupreis
def tanken(self, liter):
self.Tankfuellung = self.Tankfuellung + liter
def fahren(self, km):
self.Kilometerstand += km
self.Tankfuellung = self.Tankfuellung - km * self.Verbrauch/100
def steuerWert2(self):
return self.Neupreis*0.8**self.Alter
@classmethod
def steuerwert(cls, neupreis, alter):
return neupreis* (0.8**alter)
Ferrari = Auto(550,0,40,14,300,0,200000)
VW = Auto(103, 87987, 38, 7, 145,4,26000)
Ferrari.fahren(30)
print(Ferrari.Kilometerstand)
print("VW Steuerwert:" + str(Auto.steuerwert(26000, 4)))
print(VW.steuerWert2())
2.2 Eine Klasse Haus in Minecraft mit einer Elternklsse 'Objekt'. Die Position wird vererbt
class Objekt():
yPos = (-1)*60 # kleiner Trick, um Minecraft zu überlisten (-60)
def __init__(self, x, z): # jedes Objekt hat eine x- und z-Position
self.xPos = x
self.zPos = z
def baue(self): # wird in der Kindklasse überschrieben
pass
class Haus(Objekt): # Haus erbt von Objekt
def __init__(self, x, z, b, h):
super().__init__(x, z) # Konstruktor der Elternklasse aufrufen
self.__breite = b # private Variablen
self.__hoehe = h
def getBreite(self): # Getter-Methode
return self.__breite
def setBreite(self, b): # Setter-Methode
if b > 30:
player.say("zu breit, sorry")
else:
self.__breite = b
def baue(self): # Die Methode 'baue' wird überschrieben
blocks.fill(CONCRETE,world(self.xPos, -61, self.zPos), world(self.xPos+self.__breite, -61+self.__hoehe, self.zPos+self.__breite),FillOperation.HOLLOW)
for xPos1 in range(self.xPos+2, self.xPos+self.__breite, 3):
for hoehe1 in range(-61+3, -61+self.__hoehe, 4):
blocks.place(GLASS, world(xPos1, hoehe1, self.zPos))
blocks.place(GLASS, world(xPos1, hoehe1, self.zPos+self.__breite))
for zPos1 in range(self.zPos+2, self.zPos+self.__breite, 3):
for hoehe1 in range(-61+3, -61+self.__hoehe, 4):
blocks.place(GLASS, world(self.xPos, hoehe1, zPos1))
blocks.place(GLASS, world(self.xPos+self.__breite, hoehe1, zPos1))
# Loch für Türe
blocks.place(AIR, world(self.xPos+2, -60, self.zPos))
blocks.place(AIR, world(self.xPos+2, -59, self.zPos))
# Türe
blocks.place(blocks.block_with_data(CRIMSON_DOOR, 4),world(self.xPos+2, -60, self.zPos))
def loesche(self):
blocks.fill(AIR,world(self.xPos, -61, self.zPos), world(self.xPos+self.__breite, -60+self.__hoehe, self.zPos+self.__breite))
blocks.fill(GRASS, world(self.xPos, self.yPos-2, self.zPos),world(self.xPos+self.__breite, self.yPos-1, self.zPos+self.__hoehe))
Hauser = []
Haus1 = Haus(200,200,10,6)
Haus1.baue()
player.say(Haus1.xPos) #xPos ist in der Elternklasse definiert, wurde aber vererbt.
# player.say(Haus1.__breite) geht nicht, da breite private ist
player.say(Haus1.getBreite()) # die Breite mit der Getter-Methode holen
Auftrag 1
- Betrachte das Programm von oben
- Welche Klassen bzw. Objekte wurden definiert/erstellt?
- Gibt es Klassenvariablen und Klassenmethoden? Wenn „ja“, welche?
- Gibt es Instanzvariablen und Instanzmethoden? Welche?
- Warum wurde wohl die y-Position als Klassenvariable definiert?
- Warum wurden wohl
xPosundyPosnicht privat definiert (xPos)?,yPos - Erkläre die Vererbung an diesem Beispiel.
- Erkläre am konkreten Programm die Methode
super(). - Wozu ist Datenkapselung gut? Erkläre, wie sie im gegebenen Programm implementiert wurde.
- Was bedeuten die Begriffen overriding (überschreiben) und overloading (überladen) in der Objektorientierten Programmierung. Wird eines dieser Konzepte in der Beispielklasse verwendet?
Auftrag 2
Installiere die Umgebung processing.org - ich empfehle hier die ältere Version 3.5.4 herunterzuladen: https://processing.org/releases und füge rechts oben den Modus „Python für Processing 3“ hinzu.
- Betrachte das Car-Beispiel und vesuche es zu verstehen.
- Erweitere das Car-Beispiel.
