Dies ist eine alte Version des Dokuments!


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.

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())

Auftrag

  1. Definiere in Minecraft eine Klasse Haus und erstelle dann einige Haus-Objekte. Du kannst die Objektvariablen und Methoden frei wählen (z.B. xPos, yPos, zPos, breite, hoehe, baue(), loesche())
  2. Füge bei deinem Haus Datenkapselung hinzu, d.h. schütze die Instanzvariablen vom Zugriff von Aussen und füge Getter- und Setter-Methoden hinzu.

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
    1. Welche Klassen bzw. Objekte wurden definiert/erstellt?
    2. Gibt es Klassenvariablen und Klassenmethoden? Wenn „ja“, welche?
    3. Gibt es Instanzvariablen und Instanzmethoden? Welche?
    4. Warum wurde wohl die y-Position als Klassenvariable definiert?
    5. Warum wurden wohl xPos und yPos nicht privat definiert (xPos, yPos)?
    6. Erkläre die Vererbung an diesem Beispiel.
    7. Erkläre am konkreten Programm die Methode super().
    8. Wozu ist Datenkapselung gut? Erkläre, wie sie im gegebenen Programm implementiert wurde.
  2. Was bedeuten die Begriffen overriding (überschreiben) und overloading (überladen) in der Objektorientierten Programmierung. Wird eines dieser Konzepte in der Beispielklasse verwendet?

Objektorientierte Programmierung auf python-kurs.eu

OOP Tech with Tim

  • ef/objektorientierte_programmierung.1727780162.txt.gz
  • Zuletzt geändert: 2024/10/01 12:56
  • von lehmannr