Konzepte objektorientierter Programmierung Klaus Becker 2009 2 Objektorientierung
Konzepte objektorientierter Programmierung Klaus Becker 2009
2 Objektorientierung "Objektorientierung ist die derzeitige Antwort auf die gestiegene Komplexität der Softwareentwicklung. " Oestereich: Objektorientierte Software-Entwicklung
3 Teil 1 Objekte
4 Verwaltung von Bankkonten Ziel ist es, ein System zur Verwaltung von Bankkonten zu entwickeln. Dieses System soll recht einfach gestaltet werden, um es leicht durchschaubar zu halten. Aus diesem Grund werden wir auf viele Aspekte eines realen Bankkontenverwaltungssystem verzichten. S Konto-Nr. 234126 Freunde des Burggymnasiums Kontoauszug Blatt 342 Datum Erläuterung 01. 07. 2009 Miete der Fruchthalle - Sommerkonzert 08. 07. 2009 Spende eines ehemaligen Schülers 10. 07. 2009 Zuschuss zum Whiteboard Kontostand in EUR, 11. 07. 2009 Betrag 450. 00 1000. 00 + 800. 00 - 2130. 00 +
Verwaltung von Bankkonten 5 Wesentliche zu verwaltende Daten erkennt man auf einem Kontoauszug. Kontonummer Kontoinhaber S Konto-Nr. 234126 Freunde des Burggymnasiums Kontoauszug Blatt 342 Datum Erläuterung Betrag auszahlen 01. 07. 2009 Miete der Fruchthalle - Sommerkonzert 08. 07. 2009 Spende eines ehemaligen Schülers 10. 07. 2009 Zuschuss zum Whiteboard Kontostand in EUR, 11. 07. 2009 450. 00 1000. 00 + 800. 00 - 2130. 00 + einzahlen auszahlen Kontostand Ein Bankkonto hat eine bestimmte Kontonummer (und einen Kontoinhaber - von dem wir vorerst einmal absehen). Ein Bankkonto hat zudem einen bestimmten Stand - das ist der Geldbetrag, der aktuell auf dem Konto verfügbar ist. Von einem Bankkonto kann man Geldbeträge auszahlen, man kann aber Geldbeträge auf ein Konto einzahlen.
Objektorientierter Modellierungsansatz 6 So wie die zu verwaltende Welt aus Objekten - hier Konten - besteht, so soll das Verwaltungssystem aus Softwareobjekten aufgebaut werden. Softwareobjekt S Konto-Nr. 234126 Freunde des Burggymnasiums Kontoauszug Blatt 342 Datum Erläuterung 01. 07. 2009 Miete der Fruchthalle - Sommerkonzert 08. 07. 2009 Spende eines ehemaligen Schülers 10. 07. 2009 Zuschuss zum Whiteboard Kontostand in EUR, 11. 07. 2009 Betrag 450. 00 1000. 00 + 800. 00 - 2130. 00 +
7 Softwareobjekt in Aktion Wenn man das Programm konto. py ausführt, dann lässt sich folgender Python-Dialog führen. Probiere das einmal aus. >>> konto = Konto(234126) >>> konto. nr 234126 >>> konto. stand 0 >>> konto. stand = 2380. 0 >>> konto. stand 2380. 0 >>> konto. auszahlen(450. 0) >>> konto. stand 1930. 0 >>> konto. einzahlen(1000. 0) >>> konto. stand 2930. 0 >>> konto. auszahlen(800. 0) >>> konto. stand 2130. 0
8 Objekte Ein Objekt ist eine Einheit, die Daten mit Hilfe von Attributen verwalten und Operationen zur Verarbeitung der verwalteten Daten mit Hilfe von Methoden ausführen kann. Objekt Attribute sind - an Objekte gebundene Variablen zur Verwaltung von Daten. Diese entsprechen in der Regel den Eigenschaften der betreffenden Objekte. Methoden sind - an Objekte gebundene Prozeduren oder Funktionen zur Verarbeitung von Daten. Diese Methoden werden ausgeführt, wenn das betreffende Objekt Operationen ausführt. Ein Objekt befindet sich stets in einem bestimmten Zustand. Der aktuelle Objektzustand wird durch die aktuellen Werte der Attribute festgelegt. Attribute Attributwerte Ausführung einer Methode Objektdiagramm
9 Zugriff auf Attribute: objekt. attribut Objekte Objekt konto. stand = 2380. 0 Aktivierung von Methoden: objekt. methode konto. einzahlen(1000. 0) Attribute Attributwerte Ausführung einer Methode
Übungen 10 Aufgabe 1 (siehe www. inf-schule. de 1. 12. 1. 4) Lade die Datei wuerfel. txt herunter (benenne sie in wuerfel. py um) und führe einen Python. Dialog analog zum folgenden: >>> 1 >>> 6 w = Wuerfel() w. augen w. werfen() w. augen (a) Was leistet das Attribut augen des Objekts w, was die Methode werfen()? (b) Beschreibe den gezeigten Ablauf mit Hilfe von Objektdiagrammen.
11 Teil 2 Klassen
12 Ein Bauplan für Konto-Objekte class Konto(object): def __init__(self, nummer): self. nr = nummer self. stand = 0 def einzahlen(self, betrag): self. stand = self. stand + betrag def auszahlen(self, betrag): #. . . >>> k 1 = Konto(5) >>> k 1. nr 5 >>> k 1. stand 0 >>> k 1. einzahlen(100. 0) >>> k 1. stand 100. 0 Aufgabe 1: Analysiere diese Klassendeklaration und ergänze den fehlenden Teil #. . Überprüfen kannst du deinen Lösungsvorschlag, indem du die Klassendeklaration unter einem geeigneten Namen (z. B. konto. py) abspeicherst und ausführst. Wenn du alles richtig gemacht hast, dann sollte z. B. der oben gezeigte Python-Dialog möglich sein.
13 Ein Bauplan für Konto-Objekte class Konto(object): def __init__(self, nummer): self. nr = nummer self. stand = 0 >>> >>> k 1 = Konto(5) k 1. einzahlen(1000. 0) k 2 = Konto(8). . . def einzahlen(self, betrag): self. stand = self. stand + betrag def auszahlen(self, betrag): #. . . Aufgabe 2: Versuche auch einmal, mehrere Konto-Objekte zu erzeugen. Überweise mit passenden Methoden 500. 0 (Euro) vom Konto mit der Kontonummer 5 auf das Konto mit der Kontonummer 8.
14 Klassen Eine Klasse ist ein Bauplan für Objekte. Dieser Bauplan legt genau fest, welche Attribute die zu konstruierenden Objekte haben sollen und welche Methoden sie ausführen können sollen. Ein Objekt (als Exemplar einer Klasse) ist eine Einheit, die nach dem Bauplan der zugeordneten Klasse erzeugt wurde. Ein Objekt verfügt somit über die Attribute, die in der Klasse festgelegt sind. Diesen Attributen können - im Unterschied zur Klasse - Attributwerte zugewiesen werden. Ein Objekt kann zudem sämtliche Methoden der Klasse ausführen. Ausgenommen bleibt hier nur die Methode, deren Name mit dem Klassennamen übereinstimmt (s. u. ). Objekte können mit Namen versehen werden, über die sie dann gezielt angesprochen werden können. Klassendiagramm Objektdiagramm
15 Konstruktor / Destruktor Zur Erzeugung von Objekten verfügt eine Klasse über eine spezielle Methode, die sogenannte Konstruktormethode. Zur Vernichtung von Objekten verfügt eine Klasse über eine sogenannte Destruktormethode. Konstruktor Ein Software-Objekt hat - wie viele Objekte der realen Welt - eine bestimmte Lebensdauer. Es muss erzeugt werden, bevor es in Aktion treten kann, und kann auch wieder vernichtet werden. In einem Klassendiagramm wird eine Konstruktormethode dadurch gekennzeichnet, dass sie denselben Namen wie die Klasse selbst trägt. Oft wird diese spezielle Methode in Klassendiagrammen aber auch weggelassen. Beachte, dass eine Konstruktormethoden keine Methode ist, die ein Objekt ausführen kann. Destruktormethoden werden in der Regel in Klassendiagrammen weggelassen.
16 Klassendeklaration in Python Klassenname Schlüsselwort Oberklasse Doppelpunkt class Konto(object): def __init__(self, nummer): self. nr = nummer Attribute self. stand = 0 Einrückung Konstruktor def einzahlen(self, betrag): self. stand = self. stand + betrag Methode def auszahlen(self, betrag): self. stand = self. stand - betrag Methode Referenz auf Objekt
17 Objekterzeugung in Python class Konto(object): def __init__(self, nummer): self. nr = nummer self. stand = 0 def einzahlen(self, betrag): self. stand = self. stand + betrag def auszahlen(self, betrag): self. stand = self. stand - betrag >>> k = Konto(5) Erzeugung eines Objekts >>> k. stand 0. 0 >>> del k Vernichtung eines Objekts >>> k. stand Traceback (most recent call last): File. . . Name. Error: name 'k' is not defined >>> Klassendeklaration >>> >>> 5 >>> 0 >>> 8 >>> 0 >>> {'nr': k 1 = Konto(5) k 2 = Konto(8) k 1. nr k 1. stand k 2. nr k 2. stand k 1. __dict__ 5, 'stand': 0} k 2. __dict__ 8, 'stand': 0} Inspektion eines Objekts
Übungen 18 Aufgabe 1 (siehe www. inf-schule. de 1. 12. 2. 5) Gegeben ist eine Implementierung der Klasse Wuerfel(): from random import randint class Wuerfel(object): def __init__(self): self. augen = 1 def werfen(self): self. augen = randint(1, 6) Erzeuge drei Objekte der Klasse Wuerfel und würfele hiermit solange, bis mindestens einer der Würfel eine 6 liefert.
19 Übungen Aufgabe 2 (siehe www. inf-schule. de 1. 12. 2. 5) Gegeben ist das folgende Klassendiagramm zur Klasse Bruch: Was soll mit einem Objekt der Klasse Bruch beschrieben werden? Entwickle eine geeignete Implementierung und teste sie mit einem Python-Dialog.
20 Übungen Aufgabe 3 (siehe www. inf-schule. de 1. 12. 2. 5) Wenn man modulo einer vorgegebenen Zahl (man nennt sie auch Modul) zählt, dann bildet man jeweils den Rest bei der Division durch die vorgegebene Zahl. Betrachte als Beispiel die vorgegebene Zahl (Modul) 5. Wenn man modulo 5 zählt, dann geht das so: 0 modulo 5, 1 modulo 5, 2 modulo 5, 3 modulo 5, 4 modulo 5, 5 modulo 5, 6 modulo 5, . . . Berechnet man jeweils die Reste, dann ergibt das folgende die Zahlenfolge 0, 1, 2, 3, 4, 0, 1, . . Wenn man modulo einer vorgegebenen Zahl n zählt, dann ergibt das also die Zahlenfolge 0, 1, . . . , (n-1), 0, 1, . . Konzipiere eine Klasse Modulo. Zaehler (mit einem Klassendiagramm), die Python-Dialoge wie den folgenden ermöglicht. Implementiere die Klasse und teste sie mit geeigneten Python-Dialogen. >>> 3 >>> 0 >>> 1 >>> >>> 2 >>> >>> 0 z = Modulo. Zaehler(3) z. modul z. stand z. weiterzaehlen() z. stand z. zurueckzaehlen() z. stand z. nullsetzen() z. stand
Übungen 21 Aufgabe 5 (siehe www. inf-schule. de 1. 12. 2. 5) Die Klasse Schlange kann man verwenden, um Warteschlangen zu simulieren. Erläutere, was die Methoden der Klasse Schlange bewirken. Verdeutliche deine Erläuterungen jeweils mit einem geeigneten Python-Protokoll. class Schlange(object): def __init__(self): self. liste = [] def ist. Leer(self): if self. liste == []: return True else: return False def mit. Letztem(self, element): self. liste = self. liste + [element] . . . def ohne. Erstes(self): if not self. ist. Leer(): self. liste = self. liste[1: ] . . . def anzahl. Elemente(self): return len(self. liste) def get. Schlange(self): return self. liste def set. Schlange(self, liste): self. liste = liste
Übungen 22 Aufgabe 6 (siehe www. inf-schule. de 1. 12. 2. 5) Die folgende Deklaration des Konstruktors erlaubt es, Objekte flexibel mit Anfangswerten zu versehen: class Konto(object): def __init__(self, nummer=0, betrag=0): self. nr = nummer self. stand = betrag def einzahlen(self, betrag): self. stand = self. stand + betrag def auszahlen(self, betrag): self. stand = self. stand - betrag Erkläre, wie die jeweiligen Attributwerte der Objekte zustande kommen. >>> k 1 = Konto() >>> k 1. nr 0 >>> k 1. stand 0 >>> k 2 = Konto(1) >>> k 2. nr 1 >>> k 2. stand 0 >>> k 3 = Konto(2, 1000. 0) >>> k 3. nr 2 >>> k 3. stand 1000. 0 >>> k 4 = Konto(betrag=400. 0) >>> k 4. nr 0 >>> k 4. stand 400. 0
23 Teil 3 Verwaltung von Objekten
24 Objekte und ihre Identität >>> k 1 = Konto(3) >>> k 1 <__main__. Konto object at 0 x 0135 D 670> >>> id(k 1) 20305520 >>> hex(20305520) '0 x 135 d 670' >>> k 2 = Konto(4) >>> id(k 2) 20307184 Python-Dialog Aufgabe: Was verbirgt sich wohl hinter der Identitätsnummer eines Objekts? Prüfe mit geeigneten Python-Dialogen, ob sich die Identitätsnummer eines Objekts ändert, wenn sich der Zustand eines Objekts beim Ausführen einer Methode ändert.
Konto kopieren? 25 >>> >>> ? ? ? >>> >>> ? ? ? >>> k 1 = Konto(6) k 2 = k 1. stand k 2. einzahlen(100. 0) k 1. stand k 2 = Konto(7) k 1. stand k 2. stand Python-Dialog Aufgabe: Stell Vermutungen auf, was anstelle der drei Fragezeichen jeweils steht. Teste, ob deine Vermutungen stimmen. Kannst du dir die Ergebnisse erklären? Benutze auch den id-Operator, um Einsicht über die verwalteten Objekte zu erhalten.
26 Identität von Objekten (Daten-) Objekte haben - analog zu Objekten unserer Lebenswelt - ebenfalls eine Identität. Zur eindeutigen Identifizierung werden sie mit Identitätsnummern versehen. Verschiedene Objekte unterscheiden sich in ihrer Identitätsnummer. Sie können aber durchaus denselben Objektzustand haben. Ein Objekt behält während seiner Lebensdauer immer die einmal vergebene Identitätsnummer. Auch wenn sich der Zustand des Objekts verändert, so bleibt doch die Identitätsnummer des Objekts bestehen. Häufig verwendet hierzu man eine Adresse im Speicher des Rechners als Identitätsnummer. Die Identitätsnummer eines Objekts zeigt dann auf den Speicherbereich, in dem die Daten des Objekts abgelegt sind. Diese Identifikation von Objekten durch eine Lokalisierung im Speicher setzt natürlich voraus, dass Objekte im Speicher nicht hin und her wandern, sondern dass der einmal zugeteilte Speicherbereich während der Lebensdauer eines Objekts bestehen bleibt. Wir gehen im Folgenden von dieser Vorstellung aus.
27 Zeiger / Referenzen Eine Variable ist ein Name, der (in der Regel) mit einem Objekt verknüpft ist. Wenn eine Variable ein (Daten-) Objekt verwaltet, dann verwaltet es die Speicheradresse (bzw. Identitäsnummer) dieses Objekts. Da die Speicheradresse auf das Objekt zeigt bzw. das Objekt referenziert, nennt man eine solche Adresse auch Zeiger bzw. Referenz und die Variable zur Verwaltung der Adresse Zeigervariable bzw. Referenzvariable.
28 Zuweisungen bei Zeigervariablen k 1 = Konto(6) k 2 = k 1 k 2. einzahlen(100. 0) k 2 = Konto(7)
Objekte in Python 29 Veränderbares Objekt from konto import * def null(konto): print(id(konto)) konto. stand = 0. 0 print(id(konto)) k = Konto(9) k. einzahlen(100. 0) print(k. stand) print(id(k)) null(k) print(k. stand) print(id(k)) Unveränderbares Objekt def null(zahl): print(id(zahl)) zahl = 0. 0 print(id(zahl)) z = 100. 0 print(z) print(id(z)) null(z) print(id(z)) kein Seiteneffekt Python unterscheidet zwischen veränderbaren und unveränderbaren Objekten.
Übungen 30 Aufgabe 1 (siehe www. inf-schule. de 1. 12. 3. 5) Wenn man den Objektbezeichner vom Python-Interpreter auswerten lässt, dann wird der vom Bezeichner verwaltete Wert angezeigt. Warum zeigt der Python-Dialog, dass hier Zeiger verwaltet werden? class Konto(object): def __init__(self, nummer): self. nr = nummer self. stand = 0 def einzahlen(self, betrag): self. stand = self. stand + betrag def auszahlen(self, betrag): self. stand = self. stand - betrag >>> k 1 = Konto(6) >>> k 2 = k 1 >>> k 1 <__main__. Konto object at 0 x 01 DA 7 F 10> >>> k 2 = Konto(8) >>> k 2 <__main__. Konto object at 0 x 01 DB 5 CB 0>
Übungen 31 Aufgabe 2 (siehe www. inf-schule. de 1. 12. 3. 5) Jedes Objekt wird zur Identifierung mit einer eindeutigen Identitätsnummer versehen. Mit Hilfe des id-Operators lässt sich diese Identitätsnummer in Python ermitteln. Was zeigt der abgebildete Python-Dialog? class Konto(object): def __init__(self, nummer): self. nr = nummer self. stand = 0 def einzahlen(self, betrag): self. stand = self. stand + betrag def auszahlen(self, betrag): self. stand = self. stand - betrag >>> >>> >>> k 1 = Konto(6) k 2 = k 1 id(k 1) 31153488 id(k 2) 31153488 k 2 = Konto(8) id(k 2) 31096624
32 Übungen Aufgabe 3 (siehe www. inf-schule. de 1. 12. 3. 5) Die Funktion mit. Neuem. Ersten soll dazu dienen, in einer Liste das erste Element auszutauschen. Vegleiche die beiden folgenden Implementierungen und erkläre das unterschiedliche Verhalten. def mit. Neuem. Ersten(liste, element): liste[0] = element return liste def mit. Neuem. Ersten(liste, element): hilf = [element] + liste[1: ] return hilf L = [1, 2, 3] M = mit. Neuem. Ersten(L, 0) print(L) print(M)
33 Teil 4 Modularisierung und Datenkapselung
34 Konto überziehen Ein Konto soll höchstens um 1000 Euro überzogen werden dürfen. class Konto(object): def __init__(self, nummer): self. nr = nummer self. stand = 0 self. minimum = -1000. 0 def einzahlen(self, betrag): self. stand = self. stand + betrag def auszahlen(self, betrag): if self. stand - betrag >= self. minimum: self. stand = self. stand - betrag else: print("Auszahlung nicht möglich!") >>> k = Konto(9) >>> k. stand = 600. 0 >>> k. stand 600. 0 >>> auszahlungsbetrag = 2750. 0 >>> k. stand = k. stand - auszahlungsbetrag >>> k. stand -2150. 0 Nutzung der Klasse Implementierung der Klasse Aufgabe 1: Warum ist dieser Dialog nicht im Sinne des Bankkontenverwaltungssystems?
35 Konto überziehen Die Entwickler der Klasse Konto veröffentlichen folgende Schnittstelle dieser Klasse: from konto import Konto k = Konto(9) # Testlauf k. einzahlen(600. 0) print(k. get. Stand()) auszahlungsbetrag = 2750. 0 k. auszahlen(auszahlungsbetrag) print(k. get. Stand()) Schnittstelle zur Klasse Nutzung der Klasse Aufgabe 2: Warum macht es hier Sinn, die Attribute der Klasse Konto nicht zu veröffentlichen und eine Veränderung von Attributwerten nur über veröffentlichte Merhoden zu erlauben? Aufgabe 3: Im Testprogramm wird die Methode get. Stand benutzt, die in der Schnittstelle vorgesehen ist. Was soll diese Methode leisten? Ergänze die oben gezeigte Implementierung der Klasse Konto um die noch fehlenden Methoden und führe das Testprogramm aus.
36 Modularisierung ist ein Prinzip, nach dem viele Systeme entwickelt werden. Die Idee besteht darin, das Gesamtsystem nach dem Baukastenprinzip aus Einzelbausteinen (den sogenannten Modulen) zusammenzusetzen. "Unsere Partyzelte können in verschiedenen Größen aufgebaut werden. Da die Partyzelte und Festzelte aus Modulen bestehen, ist es sehr einfach, sie zu erweitern. Die Abbildung zeigt ein mögliches Kombinationsbeispiel der Module. " Ein Softwaremodul ist eine in sich abgeschlossene Einheit, die man vielfältig bei Problemlösungen einsetzen kann. Es reicht dabei zu wissen, welche Operationen die Einheit dem Benutzer zur Verfügung stellt. Wie die Operationen programmiert sind, muss man dagegen nicht wissen. Grundidee der objektorientierten Modularisierung ist es, Softwaremodule als Klassen zu konzipieren.
37 Modularisierung in Python from konto import Konto k = Konto(9) # Testlauf k. einzahlen(600. 0) print(k. get. Stand()) auszahlungsbetrag = 2750. 0 k. auszahlen(auszahlungsbetrag) print(k. get. Stand()) import konto k = konto. Konto(9) # Testlauf k. einzahlen(600. 0) print(k. get. Stand()) auszahlungsbetrag = 2750. 0 k. auszahlen(auszahlungsbetrag) print(k. get. Stand()) Der Name "Konto" wird in den aktuellen Namensraum übernommen. from konto import * k = Konto(9) # Testlauf k. einzahlen(600. 0) print(k. get. Stand()) auszahlungsbetrag = 2750. 0 k. auszahlen(auszahlungsbetrag) print(k. get. Stand())
38 Geheimnisprinzip / Datenkapselung Beim Autobau wird somit - zumindest in bestimmten Bereichen - das Geheimnisprinzip angewandt. Bestimmte Eigenschaften des Motors können nur über speziell hierfür vorgesehene Schnittstellen ermittelt werden. So kann der aktuelle Ölstand nur an einem hierfür vorgesehenen Messstab abgelesen werden. Änderungen am aktuellen Motorzustand können direkt ebenfalls nur an bestimmten hierfür vorgesehenen Stellen vorgenommen werden. Motoröl lässt sich nur in die hierfür vorgesehene Öffnung einfüllen. Alles weitere über das Innere des Motors bleibt für den normalen Autofahrer unzugänglich und in diesem Sinne geheim. Bei der objektorientierten Software-Entwicklung geht man völlig analog vor. Nach dem Geheimnisprinzip werden die internen Daten eines Objekts (die in der Regel über Attribute verwaltet werden) so verborgen, dass ein direkter Zugriff hierauf nicht möglich ist. Jeder Zugriff auf interne Daten und jede Veränderung von internen Daten darf nur über spezielle, hierfür vorgesehene Methoden erfolgen. Man nennt diese Vorgehensweise auch Datenkapselung.
39 Zugriffsrechte / Zugriffsmethoden Um interne Daten kapseln zu können, werden Zugriffrechte festgelegt. Der Entwickler einer Klasse hat die Möglichkeit, Attribute und Methoden einer Klasse als öffentlich oder privat zu deklarieren. Lesende und schreibende Zugriffe auf Attribute bzw. Methoden eines Objekts sind nur möglich, wenn diese öffentlich sind. Private Attribute bzw. Methoden können dagegen nur bei der Implementierung der betreffenden Klasse benutzt werden. Im Klassendiagramm werden die Zugriffsrechte auf die Attribute und Methoden mit Hilfe der Symbole + (für öffentlich) und - (für privat) festgelegt. Verfolgt man die Strategie, alle Attribute als privat zu deklarieren, so besteht keine Möglichkeit, direkt schreibend oder lesend auf Attributwerte zuzugreifen. Um dennoch solche Zugriffe zu erlauben, werden spezielle öffentliche Zugriffsmethoden bereitgestellt. Das Klassendiagramm wird daher um solche Zugriffsmethoden erweitert.
40 Zugriffsrechte in Python Ein Attribut / eine Methode wird in Python zu einem privaten Attribut, wenn der Attributname mit zwei Unterstrichen beginnt und nicht mit Unterstrichen endet. Beginnt der Attributname / Methodenname nicht mit einem Unterstrich, so ist das Attribut öffentlich. class Konto(object): def __init__(self, nummer): self. __nr = nummer self. __stand = 0 self. __minimum = -1000. 0 def einzahlen(self, betrag): self. __stand = self. __stand + betrag def auszahlen(self, betrag): if self. __stand - betrag >= self. __minimum: self. __stand = self. __stand - betrag else: print("Auszahlung nicht möglich!") def get. Stand(self): return self. __stand def set. Stand(self, stand): if stand >= self. __minimum: self. __stand = stand else: print("Initialisierung nicht möglich!") #. . .
41 Datenkapselung in Python >>> k = Konto(3) >>> k. __stand Traceback (most recent call last): File. . . Attribute. Error: 'Konto' object has no attribute '__stand' >>> k. __dict__ {'_Konto__minimum': -1000. 0, '_Konto__nr': 3, '_Konto__stand': 0} >>> k. _Konto__stand 0 Wie erwartet kann man auf das private Attribut __stand des neu erzeugten Objekts k nicht zugreifen. Python meldet als Fehler, dass es kein Attribut __stand gibt. Der Aufruf k. __dict__ verrät, woran das liegt. Ein Aufruf wie k. __dict__ listet sämtliche Attribute mit den zugehörigen Attributwerten des betreffenden Objekts auf. Interessant ist hier, dass sich das private Attribut __stand hinter einem anderen Namen versteckt. Wenn man weiß, wie der neue Name - hier _Konto__stand - gebildet wird, dann kann man durchaus auf das betreffende Attribut zugreifen. Also: Private Attribute werden in Python mit anderen Namen versehen, so dass kein direkter Zugriff möglich ist. Kennt man den Namen, hinter dem sich ein privates Attribut verbirgt, so kann man durchaus auf dieses Attribut zugreifen. Python liefert also keinen echten Zugriffsschutz.
42 Datenkapselung in Python >>> k = Konto(3) >>> k. __stand Traceback (most recent call last): File. . . Attribute. Error: 'Konto' object has no attribute '__stand' >>> k. __dict__ {_Konto__minimum': -1000. 0, '_Konto__nr': 3, '_Konto__stand': 0} >>> k. __stand = 100. 0 >>> k. __stand 100. 0 >>> k. __dict__ {_Konto__minimum': -1000. 0, '_Konto__nr': 3, '_Konto__stand': 0, '__stand': 100. 0} Ein erster Zugriff auf das private Attribut __stand scheitert. Dann aber ist es - entgegen aller Zugriffslogik - scheinbar möglich, dem privaten Attribut __stand einen Wert zuzuweisen. Der Aufruf k. __dict__ erklärt erst, was hier passiert ist. Neben dem privaten Attribut __stand, das sich hinter dem neuen Namen _Konto__stand versteckt, gibt es noch öffentliches Attribut __stand, auf das man direkt zugreifen kann. Wir werden im Folgenden bei der Implementierung von Klassen in Python keine Attribute als private Attribute deklarieren. In der Regel werden wir auch keine Zugriffsmethoden einführen und nutzen. Nur in begründeten Ausnahmefällen werden wir von dieser Vereinbarung abweichen.
43 Schnittstellen Die Schnittstelle einer Klasse liefert alle Informationen, die man benötigt, um die Klasse benutzen zu können. Hierzu gehört eine genaue Beschreibung aller öffentlichen Attribute und Methoden der Klasse. Für jedes Attribut benötigt man den erwarteten Datentyp, für jede Methode die Signatur (d. h. die genaue Festlegung der Parametertypen und bei Funktionen des Rückgabetyps) und eine Verhaltensbeschreibung. Konto(nummer: int) nachher: Ein Objekt der Klasse Konto ist erzeugt. Der Wert des Attributs nr entspricht dem übergebenen Wert des Parameters nummer, der Wert des Attributs stand beträgt 0, der Wert des Attributs minimum beträgt -1000. 0. auszahlen(betrag: float) vorher: Das Konto-Objekt hat einen beliebigen Zustand. nachher: Der Wert des Attributs stand des Konto-Objekts ist um den übergebenen Wert des Parameters betrag reduziert.
44 Übungen Aufgabe 1 (siehe www. inf-schule. de 1. 12. 4. 8) Zur Klasse Wuerfel soll folgende Implementierung in einer Datei wuerfel. py abgespeichert sein: from random import randint class Wuerfel(object): def __init__(self): self. augen = 1 def werfen(self): self. augen = randint(1, 6) (a) Was leistet das folgende Programm, das Modul Wuerfel als Baustein benutzt? (b) Es soll getestet werden, wie oft man drei Würfel werfen muss, bis mindestens einer eine 6 liefert. Entwickle ein geeignetes Simulationsprogramm, das die Klasse Wuerfel als Baustein benutzt. from wuerfel import * # Würfelprogramm w = Wuerfel() w. werfen() versuche = 1 while w. augen != 6: w. werfen() versuche = versuche + 1 print("Versuche: ", versuche)
45 Übungen Aufgabe 2 (siehe www. inf-schule. de 1. 12. 4. 8) Folgende Klassen sollen als Bausteine zur Simulation des Spiels "chuck a luck" zur Verfügung gestellt werden: (a) Implementiere diese Klassen. Mit Hilfe dieser Bausteine sollen dann Python-Dialoge wie der folgende möglich sein. (b) Mit Hilfe eines Simulationsprogramms soll ermittelt werden, ob das chuck-a-luck-Spiel fair ist. >>> >>> 99 >>> 3 >>> 6 >>> 2 >>> 101 k = Konto(100) s = Spielzahl() w. A = Wuerfel() w. B = Wuerfel() w. C = Wuerfel() k. abheben(1) k. stand s. setzen(3) s. zahl w. A. werfen() w. B. werfen() w. C. werfen() w. A. augen w. B. augen w. C. augen k. einzahlen(2) k. stand
Übungen 46 Aufgabe 3 (siehe www. inf-schule. de 1. 12. 4. 8) (a) Erstell ein Klassendiagramm und eine Schnittstellenbeschreibung zur Klasse Bruch. (b) Entwickle ein Testprogramm, das die Klasse Bruch als Modul benutzt. class Bruch(object): def __init__(self, z, n): self. zaehler = z self. nenner = n def erweitern(self, k): self. zaehler = self. zaehler * k self. nenner = self. nenner * k . . . def kuerzen(self, k): if (self. zaehler % k == 0) and (self. nenner % k == 0): self. zaehler = self. zaehler // k self. nenner = self. nenner // k . . . def vollstaendig. Kuerzen(self): # gg. T von Zähler und Nenner best. x = self. zaehler y = self. nenner while y > 0: h=x%y x=y y=h ggt = x # kürzen self. kuerzen(ggt) (c) Füge der def add(self, b): Klasse Bruch x 1 = self. zaehler weitere x 2 = self. nenner Operationen y 1 = b. zaehler hinzu und teste y 2 = b. nenner z 1 = x 1*y 2 + x 2*y 1 diese Erweiterung. z 2 = x 2*y 2 self. zaehler = z 1 self. nenner = z 2 self. vollstaendig. Kuerzen()
47 Übungen Aufgabe 5 (siehe www. inf-schule. de 1. 12. 4. 8) Teste die Einbindung folgender Modulimporte und ihre Auswirkung auf den globalen Namensraum. Warum wird bei sehr umfangreichen Modulen empfohlen, die erste oder dritte der oben aufgelisteten Einbindungsvarianten zu benutzen? >>> from random import randint >>> globals() >>> from random import * >>> globals() >>> import random >>> globals()
Übungen 48 Aufgabe 6 (siehe www. inf-schule. de 1. 12. 4. 8) (a) Teste diese Implementierung der Klasse Bruch. Irgend etwas stimmt hier nicht. Findest du den Fehler? Benutze die Operation __dict__() zur Fehlersuche. Erkläre, was hier schiefläuft. (b) Warum ist es so schwierig, Flüchtigkeitsfehler wie den oben gezeigten zu finden? class Bruch(object): def __init__(self, z, n): self. zaehler = z self. nenner = n def erweitern(self, k): self. zahler = self. zaehler * k self. nenner = self. nenner * k def kuerzen(self, k): if (self. zaehler % k == 0) and (self. nenner % k == 0): self. zaehler = self. zaehler // k self. nenner = self. nenner // k
49 Teil 5 Beziehungen
Verwaltung des Kontoinhabers 50 S Konto-Nr. 5 Adriana Müller Kontoauszug Blatt 3 Datum Erläuterung 01. 07. 2009 Handykosten 08. 07. 2009 Zuschuss von Oma und Opa 10. 07. 2009 Schuhe Kontostand in EUR, 11. 07. 2009 Vorschlag 1: Betrag 45. 00 - Der Kontoinhaber soll jetzt ebenfalls mitverwaltet werden. Hierzu muss das Objektmodell erweitert werden. Mehrere Vorschläge stehen zur Diskussion 100. 00 + 80. 00 - 50. 00 + Aufgabe: Vergleiche die folgenden Vorschläge. Vorschlag 2:
51 Verwaltung des Kontoinhabers Vorschlag 2: Vorschlag 3: Aufgabe: Welche Nachteile zeigen sich bei Vorschlag 2, wenn es mehrere Konten und mehrere Kunden gibt? Vergleiche Vorschlag 2 mit Vorschlag 3.
52 Verwaltung des Kontoinhabers class Konto(object): def __init__(self, nummer): self. nr = nummer self. stand = 0. 0 self. inhaber = None def einzahlen(self, betrag): self. stand = self. stand + betrag def auszahlen(self, betrag): self. stand = self. stand - betrag class Kunde(object): def __init__(self, name, vorname): self. name = name self. vorname = vorname from konto_kunde import * # Erzeugung der Objekte konto 1 = Konto(5) konto 2 = Konto(11) konto 2. stand = 200. 0 kunde 1 = Kunde("Müller", "Adriana") kunde 2 = Kunde("Meier", "Anatol") konto 1. inhaber = kunde 2 konto 2. inhaber = kunde 1 # Ausgaben print("Kontonummer: ", konto 1. nr) print("Inhaber(in): ", konto 1. inhaber. vorname, konto 1. inhaber. name) print("Kontostand: ", konto 1. stand) print("Kontonummer: ", konto 2. nr) print("Inhaber(in): ", konto 2. inhaber. vorname, konto 2. inhaber. name) print("Kontostand: ", konto 2. stand) Aufgabe: Erkläre, wie die Objektkonstellation aus Vorschlag 3 hier realisiert wird. Stell auch eine Vermutung auf, was das Testprogramm auf dem Bildschirm ausgibt.
53 Beziehung Wenn ein Objekt über einen Zeiger (eine Referenz) Zugriff auf ein anderes Objekt hat, so liegt eine (gerichtete) Beziehung zwischen den Objekten vor. Mit Hilfe eines Aufrufs wie z. B. konto 1. inhaber kann das Konto-Objekt konto 1 auf Daten des zugeordneten Kunde. Objekts zugreifen, z. B. mit konto 1. inhaber. name auf das entsprechende Attribut. Das Konto. Objekt konto 1 hat somit Zugriff auf ein Kunde. Objekt, z. B. kunde 2. Man sagt auch, dass das Konto -Objekt konto 1 in Beziehung zum Kunde. Objekt kunde 2 steht. Objektdiagramm Klassendiagramm
54 Beziehungsmuster Muster: Objekt der Klasse A kennt Objekt der Klasse B Muster: Objekt der Klasse A erzeugt Objekt der Klasse B Muster: Objekt der Klasse A kennt Objekt der Klasse B und umgekehrt Muster: Objekt der Klasse A kennt mehrere Objekte der Klasse B
55 Implementierung von Beziehungen class Konto(object): def __init__(self, nummer): self. nr = nummer self. stand = 0. 0 self. inhaber = None def einzahlen(self, betrag): self. stand = self. stand + betrag def auszahlen(self, betrag): self. stand = self. stand - betrag class Kunde(object): def __init__(self, name, vorname): self. name = name self. vorname = vorname Muster: Objekt der Klasse A kennt Objekt der Klasse B from bank 0 import * # Erzeugung der Objekte konto 1 = Konto(5) konto 2 = Konto(11) konto 2. stand = 200. 0 kunde 1 = Kunde("Müller", "Adriana") kunde 2 = Kunde("Meier", "Anatol") konto 1. inhaber = kunde 2 konto 2. inhaber = kunde 1
56 Implementierung von Beziehungen class Konto(object): def __init__(self, nummer): self. nr = nummer self. stand = 0. 0 self. inhaber = None def einzahlen(self, betrag): self. stand = self. stand + betrag def auszahlen(self, betrag): self. stand = self. stand - betrag class Kunde(object): def __init__(self, name, vorname): self. name = name self. vorname = vorname self. konto = None Muster: Objekt der Klasse A kennt Objekt der Klasse B und umgekehrt from bank 1 import * # Erzeugung der Objekte konto 1 = Konto(5) konto 2 = Konto(11) konto 2. stand = 200. 0 kunde 1 = Kunde("Müller", "Adriana") kunde 2 = Kunde("Meier", "Anatol") konto 1. inhaber = kunde 2 konto 2. inhaber = kunde 1. konto = konto 2 kunde 2. konto = konto 1
57 Implementierung von Beziehungen class Konto(object): def __init__(self, nummer): self. nr = nummer self. stand = 0. 0 self. inhaber = None def einzahlen(self, betrag): self. stand = self. stand + betrag def auszahlen(self, betrag): self. stand = self. stand - betrag class Kunde(object): def __init__(self, name, vorname): self. name = name self. vorname = vorname self. konten = [] Liste def konto. Hinzufuegen(self, konto): self. konten = self. konten + [konto] from bank 2 import * # Erzeugung der Objekte konto 1 = Konto(5) konto 2 = Konto(11) konto 2. stand = 200. 0 konto 3 = Konto(19) konto 3. stand = 150. 0 konto 4 = Konto(21) konto 4. stand = -50. 0 kunde 1 = Kunde("Müller", "Adriana") kunde 2 = Kunde("Meier", "Anatol") konto 1. inhaber = kunde 2 konto 2. inhaber = kunde 1 konto 3. inhaber = kunde 1 konto 4. inhaber = kunde 1. konto. Hinzufuegen(konto 2) kunde 1. konto. Hinzufuegen(konto 3) kunde 1. konto. Hinzufuegen(konto 4) kunde 2. konto. Hinzufuegen(konto 1) Muster: Objekt der Klasse A kennt mehrere Objekte der Klasse B
58 Implementierung von Beziehungen class Konto(object): def __init__(self, nummer): self. nr = nummer self. stand = 0. 0 self. inhaber = None def einzahlen(self, betrag): self. stand = self. stand + betrag from bank 3 import * from random import * # Erzeugung der Objekte bank = Bank() for i in range(8): bank. erzeuge. Konto() for konto in bank. konten: konto. einzahlen(float(randint(0, 100))) def auszahlen(self, betrag): self. stand = self. stand - betrag class Bank(object): def __init__(self): self. konten = [] self. naechste. Konto. Nr = 0 def erzeuge. Konto(self): konto = Konto(self. max. Konto. Nr) self. konten = self. konten + [konto] self. naechste. Konto. Nr = self. naechste. Konto. Nr + 1 Muster: Objekt der Klasse A erzeugt Objekte der Klasse B
59 Operationen als Dienste Objekte können (in aller Regel) bestimmte Operationen mit den von ihnen verwalteten Daten ausführen. Die Ausführung einer Operationen wird als Dienst anderen Objekten zur Verfügung gestellt. Andere Objekte können den zur Verfügung gestellten Dienst dann nutzen. Hier wird also die Anbieter-Nutzer-Sichtweise benutzt. Ein Bank-Objekt bietet z. B. den Dienst ueberweisen an. Ein Konto-Objekt bietet z. B. den Dienst einzahlen an.
60 Interaktion zwischen Objekten Die Analogien zur Lebenswelt ermöglichen es, die Ausführung objektorientierter Programme in einem Rollenspiel zu verdeutlichen, bei dem Personen die Rolle von Objekten übernehmen. Wenn das Bank-Objekt den Dienst "ueberweisen" ausführt, dann schickt es zunächst eine Nachricht auszahlen(. . . ) an das betreffende Konto-Objekt und anschließend eine Nachricht einzahlen(. . . ) an das andere betreffende Konto-Objekt. Das Bank-Objekt interagiert also hier mit zwei Konto-Objekten. Dies ist deshalb möglich, weil das Bank-Objekt gemäß Klassendiagramm oben in Beziehung zu den Konto-Objekten steht.
61 Interaktion zwischen Objekten Wenn ein Objekt den Dienst eines anderen Objekt nutzen will, dann schickt es ihm eine Nachricht. Das Senden einer Nachricht bedeutet, ein Objekt zu veranlassen, eine seiner als Dienste zur Verfügung gestellten Operationen. Das Versenden von Nachrichten wird als Interaktion zwischen Objekten gedeutet. Voraussetzung für eine Interaktion zwischen Objekten ist, dass diese miteinander in Beziehung stehen. Sequenzdiagramm Wenn das Bank-Objekt den Dienst "ueberweisen" ausführt, dann schickt es zunächst eine Nachricht auszahlen(. . . ) an das betreffende Konto-Objekt und anschließend eine Nachricht einzahlen(. . . ) an das andere betreffende Konto-Objekt. Das Bank-Objekt interagiert also hier mit zwei Konto-Objekten. Dies ist deshalb möglich, weil das Bank-Objekt gemäß Klassendiagramm oben in Beziehung zu den Konto-Objekten steht.
Übungen 62 Aufgabe 1 (siehe www. inf-schule. de 1. 12. 5. 6) Erzeuge Objekte, die folgende Situation modellieren: Kunde Werner Schmidt hat das Konto mit der Nummer 23. Kundin Theresa Meier hat das Konto mit der Nummer 42. Kundin Alexandra Roth hat das Konto mit der Nummer 15. class Konto(object): def __init__(self, nummer): self. nr = nummer self. stand = 0. 0 self. inhaber = None def einzahlen(self, betrag): self. stand = self. stand + betrag def auszahlen(self, betrag): self. stand = self. stand - betrag class Kunde(object): def __init__(self, name, vorname): self. name = name self. vorname = vorname
63 Übungen Aufgabe 2 (siehe www. inf-schule. de 1. 12. 5. 6) Ein Kunde soll neben einem Erstkonto auch ein Zweitkonto haben können. (a) Beschreibe eine typische Situation in dieser Bankwelt mit einem Objektdiagramm. (b) Beschreibe die Bankwelt auch mit einem Klassendiagramm. (c) Entwickle eine Implementierung zu dieser Bankwelt und ein Testprogramm passend zu Aufgabe (a).
64 Übungen Aufgabe 3 (siehe www. inf-schule. de 1. 12. 5. 6) In der freien Enzyklopädie Wikipedia werden Artikel zu Stichwörtern von Benutzern verfasst. Eine erste Modellierung der Klassen Artikel und Benutzer könnte wie folgt aussehen: (a) Implementiere die Klassen und erzeuge Objekte zur Beschreibung folgender Situation: * Der Artikel zum Stichwort HTML wurde vom Benutzer helmut 03 verfasst. * Der Artikel zum Stichwort CSS wurde vom Benutzer loreley verfasst. * Der Artikel zum Stichwort Barrierefreiheit wurde vom Benutzer loreley verfasst.
65 Übungen Aufgabe 3 (siehe www. inf-schule. de 1. 12. 5. 6) Das Modell so erweitert werden, dass Artikel von Benutzern überarbeitet werden können: (b) Implementiere das erweiterte Modell und erzeuge eine typische Objektsituation. Verdeutliche die erzeugte Objektsituation auch mit einem Objektdiagramm. (c) Wie müsste man die Klasse Benutzer erweitern, wenn hier auch die Mitarbeit an Artikeln mit einem Referenzattribut erfasst werden soll.
66 Übungen Aufgabe 4 (siehe www. inf-schule. de 1. 12. 5. 6) Ein Geometrieprogramm soll verschiedene geometrische Objekte verwalten. Kannst du passende Klassen konzipieren und implementieren, so dass folgendes Testprogramm sinnvoll ausgeführt werden kann. from geometrie import * # Punkte p 1 = Punkt(0, 0) p 2 = Punkt(0, 10) p 3 = Punkt(10, 10) p 4 = Punkt(10, 0) p 5 = Punkt(5, 15) # Rechtecke und Dreieck (als n-Eck) rechteck = Rechteck(p 1, p 3) dreieck = Neck([p 2, p 3, p 5]) # Haus des Nikolaus als Figur haus_nikolaus = Figur() haus_nikolaus. hinzufuegen(rechteck) haus_nikolaus. hinzufuegen(dreieck) # Punkt verschieben und ausgeben p 1. verschieben(4, 1). . .
67 Teil 6 Miniwelt und Datenmodell
Bankwelt im Computer 68 S Konto-Nr. 5 Adriana Müller Kontoauszug Blatt 3 Datum Erläuterung 01. 07. 2009 Handykosten 08. 07. 2009 Zuschuss von Oma und Opa 10. 07. 2009 Schuhe Kontostand in EUR, 11. 07. 2009 Die Bankwelt mit bestimmten Gegenständen (Kunden, Konten, . . . ) und Vorgängen (Ein-, Auszahlungen, . . . ) soll in einem Programm geeignet erfasst werden. Betrag 45. 00 100. 00 + 80. 00 - 50. 00 +
Bankwelt im Computer 69 S Konto-Nr. 5 Adriana Müller Kontoauszug Blatt 3 Datum Erläuterung 01. 07. 2009 Handykosten 08. 07. 2009 Zuschuss von Oma und Opa 10. 07. 2009 Schuhe Kontostand in EUR, 11. 07. 2009 Aufgabe: Welche Objekte sind für welche Aufgaben zuständig? Welche Vereinfachungen sind bei dieser Beschreibung einer Bankwelt vorgenommen? Betrag 45. 00 100. 00 + 80. 00 - 50. 00 +
70 Bankwelt im Computer from bank import * bank = Bank() # Konten eröffnen bank. konto. Eroeffnen("Müller", "Adriana", 2101) bank. konto. Eroeffnen("Meier", "Adrian", 1507) bank. konto. Eroeffnen("Schuster", "Christiane", 1313) bank. konto. Eroeffnen("Weber", "Alexander", 2276) # Ausgabe der konten for kunde in bank. kunden: print("Kunde: ") print("Name: ", kunde. name) print("Vorname: ", kunde. vorname) print("Kontonummer: ", kunde. konto. nr) print("Kontostand: ", kunde. konto. stand) print() # Transaktion print("Einzahlung") pin = int(input("PIN: ")) if bank. existiert. PIN(pin): meine. Konto. Nr = bank. get. Konto. Nr(pin) betrag = float(input("Betrag: ")) bank. einzahlen(meine. Konto. Nr, betrag). . . Aufgabe: Lade dir die Datei bank. py mit einer Implementierung der Klassen herunter. Versuche, mit den Objekten Vorgänge der Bankwelt durchzuspielen. Das folgende Python -Testprogramm zeigt, wie das gehen könnte.
Miniwelt 71 Mit Miniwelt bezeichnen wir den Weltausschnitt / Gegenstandsbereich, der dem zu entwickelnden Programm (Software-System) zu Grunde liegt. S Konto-Nr. 5 Adriana Müller Kontoauszug Blatt 3 Datum Erläuterung 01. 07. 2009 Handykosten 08. 07. 2009 Zuschuss von Oma und Opa 10. 07. 2009 Schuhe Kontostand in EUR, 11. 07. 2009 Betrag 45. 00 100. 00 + 80. 00 - 50. 00 +
72 Datenmodell Ein Modell ist eine vereinfachende Darstellung einer Miniwelt, die (für einen bestimmten Zweck ausgewählte) Teilaspekte der Miniwelt strukturgetreu beschreibt. Aufgabe eines objektorientiertes Datenmodells ist es, die Struktur der Miniwelt mit Hilfe von Objekten und deren Beziehungen zu beschreiben.
73 Zuständigkeiten Bei der Entwicklung komplexer Software-Systeme ist es günstig, dieses System aus mehreren Objekten zusammenzusetzen. Jedes Objekt sollte dabei für einen bestimmten Aufgabenbereich zuständig sein. Ein solches System aus Objekten mit klar umgrenzten Zuständigkeiten erhöht die Durchschaubarkeit des gesamten Software-Systems und erleichtert es, das System nachträglich abzuändern oder zu erweitern. Objekt der Klasse Kunde: verwaltet die Kundendaten Objekt der Klasse Konto: verwaltet die Kontodaten; führt Einund Auszahlungen aus Objekt der Klasse Bank: erzeugt und verwaltet alle Kunde. Objekte und Konto-Objekte; veranlasst alle Geldtransfers zwischen Konten; . . .
74 UML steht für uniform modeling language und ist eine normierte Bildsprache, mit der man objektorientierte Modelle beschreiben kann. Objektdiagramm Klassendiagramm Sequenzdiagramm
75 UML-Editor Violet ist ein sehr einfaches, frei nutzbares Werkzeug zur Erstellung von UML-Diagrammen. siehe: http: //www. horstmann. com/violet/
76 Übungen Aufgabe 1 (siehe www. inf-schule. de 1. 12. 6. 6) Beim chuck-a-luck-Spiel soll ein Objekt der Klasse Spiel alle beteiligten Objekte verwalten und die Spielaktionen als Operationen zur Verfügung stellen. (a) Skizziere ein Objektdiagramm zur Miniwelt chuck-a-luck. (b) Woran erkennt man im Klassendiagramm, dass das Spiel. Objekt für die Erzeugung der weiteren Objekte zuständig ist? Welche weiteren Zuständigkeiten hat das Spiel-Objekt? (c) Implementiere dieses Modell. Teste es wie folgt. from chuckaluck import * # Erzeugung der Objekte spiel = Spiel() # Durchführung eines Spiels spiel. einsatz. Zahlen() spielzahl. Setzen(5) spiel. wuerfel. Werfen() spiel. gewinn. Auszahlen() # Ausgabe der Spiel print("Würfel A: ", spiel. wuerfel. A. augen). . .
77 Übungen Aufgabe 2 (siehe www. inf-schule. de 1. 12. 6. 6) Das folgende Klassendiagramm zeigt ein System, mit dem man die Entwicklung einer Population simulieren kann. (a) Implementiere die Klasse Population so, dass die Mäusepopulation aus. . . (siehe Einstieg Mäusepopulation) als Miniwelt erfasst wird. (b) Implementiere die Klasse Simulator so, dass sie die Entwicklung der verwalteten Population beschreibt. (c) Erstell ein geeignetes Testprogramm, um die Implementierung zu überprüfen. . (d) Was müsste am Datenmodell verändert werden, wenn man die Population aus. . . (siehe Miniprojekt) modellieren wollte?
78 Übungen Aufgabe 3 (siehe www. inf-schule. de 1. 12. 6. 6) Ein Zahlenschloss bestehe aus drei Einstellrädern, mit denen die Ziffern der Schlüsselzahl einzeln eingestellt werden können. Hat man die Schlüsselzahl richtig getroffen, dann ist das Schloss offen. Entwickle ein objektorientiertes Datenmodell, das die vorliegende Miniwelt möglichst strukturgetreu beschreibt. Stell das Datenmodell auch mit Hilfe von UML-Diagrammen dar. Implementiere und teste das Datenmodell.
79 Teil 7 Datenmodell und grafische Benutzeroberfläche
80 Eine GUI zur Bankwelt Das bisher entwickelte Programm zur Verwaltung von Bankkonten soll um eine grafische Benutzeroberfläche erweitert werden. Der Benutzer soll die Möglichkeit haben, wie an einem Terminal in der Bank seine Bankgeschäfte zu erledigen. Am Terminal kann man sich den aktuellen Kontostand anzeigen lassen, Geldbeträge ein- und auszahlen und auch Überweisungen vorzunehmen.
81 Eine GUI zur Bankwelt #-----------------------------# Datenmodell #-----------------------------from bank import * bank = Bank() bank. init. Bank() #-----------------------------# GUI #-----------------------------from tkinter import * #. . . Prozeduren zur Ereignisverarbeitung # Erzeugung des Fensters fenster = Tk() fenster. title("Bank") fenster. geometry('230 x 330'). . . Aufgabe: Das gezeigte Programm (siehe www. inf-schule. de. . . ) enthält zwar noch Implementierungslücken, ist aber nichtsdestotrotz lauffähig. Teste zunächst das Programm. Mache dich auch mit dem Quelltext vertaut. Analysiere insbesondere die schon implementierten Ereignisverarbeitungsprozeduren. Implementiere die noch fehlenden Implementierungen.
82 from tkinter import * Eine GUI zur Bankwelt Aufgabe: Der folgende Quelltextauszug zeigt eine andere Implementierung der grafischen Benutzeroberfläche. Hier wird ein zusätzliches Objekt benutzt, um sämtliche GUI-Objekte zu verwalten. Analysiere das Programm. class GUIBank(object): def __init__(self, bank): # Referenzattribute zum Datenmodell self. bank = bank # Erzeugung des Fensters self. fenster = Tk() self. fenster. title("Bank") self. fenster. geometry('230 x 330') # Rahmen PIN self. rahmen. PIN = Frame(master=self. fenster, background="#FFCFC 9") self. rahmen. PIN. place(x=5, y=5, width=220, height=30) # Label mit Aufschrift PIN self. label. PIN = Label(master=self. rahmen. PIN, background="white", text="PIN") self. label. PIN. place(x=5, y=5, width=145, height=20) # Entry für die PIN self. entry. PIN = Entry(master=self. rahmen. PIN) self. entry. PIN. place(x=155, y=5, width=60, height=20). . . def Button_Anzeigen_Click(self): pin = int(self. entry. PIN. get()). . .
83 Eine GUI zur Bankwelt #-----------------------------# Datenmodellobjekte #-----------------------------from bank import * bank = Bank() bank. init. Bank() #-----------------------------# GUI-Objekte #-----------------------------from guibank import * guibank = GUIBank(bank) Aufgabe: Welche Beziehungen bestehen zwischen den Objekten? Verdeutliche dies mit einem Objektund Klassendiagramm.
84 Zwei-Komponenten-Architektur Das bisher entwickelte System zur Verwaltung von Bankkonten hat eine Zwei-Komponenten. Architektur. Die eine Komponente wird vom Datenmodell gebildet. Diese Komponente ist so konzipiert, dass sie ohne eine grafische Benutzeroberfläche benutzt werden kann. Die andere Komponente umfasst alle Klassen, die für die Erzeugung und Verwaltung der grafischen Benutzeroberfläche benötigt werden. Da Objekte dieser Klassen u. a. für die Darstellung des Datenmodells zuständig sind, dürfen sie Zugriff auf Datenmodell-Objekte haben. #-----------------# Datenmodellobjekte #-----------------from bank import * bank = Bank() bank. init. Bank() #-----------------# GUI-Objekte #-----------------from guibank import * guibank = GUIBank(bank)
85 Trennung Datenmodell - GUI Die Trennung zwischen Datenmodell und GUI ist ein Prinzip bei der Entwicklung von Systemen mit grafischer Benutzeroberfläche, das hilft, klar strukturierte und gut wartbare Programme zu erstellen: Während GUI-Objekte auf Objekte des Datenmodells zugreifen dürfen, ist der umgekehrte Zugriff nicht erlaubt.
86 Model - View - Control Bisher war das GUI-Objekt sowohl für die Präsentation der Daten auf der Benutzeroberfläche, als auch für die Ereignisverarbeitung zuständig. Diese beiden Zuständigkeiten sollen jetzt aufgeteilt werden: Einerseits soll es Präsentationsobjekte geben, die nur für die Darstellung der Daten zuständig sind, andererseits Steuerungsobjekte, die Verbindungen zwischen Präsentationsobjekten und Datenmodellobjekten herstellen und die Ereignisverarbeitung festlegen. Diese weitere Aufteilung der Zuständigkeiten soll spätere Änderungen oder Erweiterungen des Softwaresystems erleichtern und eine Wiederverwendbarkeit einzelnen Komponenten ermöglichen. View Control Model
87 Model - View - Control class Konto(object): def __init__(self, nummer): self. nr = nummer self. stand = 0. 0 self. inhaber = None. . . class Kunde(object): def __init__(self, name, vorname, pin): self. name = name self. vorname = vorname self. pin = pin self. konto= None. . . class Bank(object): def __init__(self): self. konten = [] self. kunden = [] self. naechste. Konto. Nr = 0. . . Model
88 Model - View - Control from tkinter import * class GUIBank(object): def __init__(self, bank, cb. Anzeigen, cb. Einzahlen, cb. Auszahlen, cb. Ueberweisen): # Referenzattribute zum Datenmodell self. bank = bank self. cb. Anzeigen = cb. Anzeigen self. cb. Einzahlen = cb. Einzahlen self. cb. Auszahlen = cb. Auszahlen self. cb. Ueberweisen = cb. Ueberweisen # Erzeugung des Fensters self. fenster = Tk() self. fenster. title("Bank") self. fenster. geometry('230 x 330'). . . # Button Anzeigen self. button. Anzeigen = Button(master=self. rahmen. Konto, text="anzeigen", command=self. cb. Anzeigen) self. button. Anzeigen. place(x=5, y=55, width=145, height=20). . . # kein mainloop hier View
89 Model - View - Control from model_bank import * Control from view_bank import * class Controler(object): def __init__(self): # Erzeugung der Datenmodell-Objekte self. bank = Bank() self. bank. init. Bank() # Erzeugung und Initialisierung der GUI self. guibank = GUIBank(self. bank, self. button. Anzeigen, self. button. Einzahlen, . . . ) # Ereignisschreife self. guibank. fenster. mainloop() def button. Anzeigen(self): pin = int(self. guibank. entry. PIN. get()) if self. bank. existiert. PIN(pin): kunde = self. bank. get. Kunde(pin) self. guibank. label. Kontonummer. config(text=str(kunde. konto. nr)) self. guibank. label. Kontostand. config(text=str(kunde. konto. stand)) else: self. guibank. label. Kontonummer. config(text="") self. guibank. label. Kontostand. config(text=""). . . controler = Controler()
90 Model - View - Control Aufgabe eines GUIBank-Objekts ist es, die grafische Benutzeroberfläche zu erzeugen. View Control Aufgabe eines Model-Objekts ist es, einen Aspekt der Miniwelt abzubilden. . Model Aufgabe eines Controler-Objekts ist es, die Datenmodell- und GUI -Objekte zu erzeugen und zu verknüpfen. Beachte, dass ein Controler-Objekt Zugriff auf Model- und View-Objekte hat.
Callback-Funktion 91 Eine Callback-Funktion (bzw. Rückruffunktion) ist eine Funktion, die einer anderen Funktion als Parameter übergeben wird und von dieser unter gewissen Bedingungen aufgerufen wird. class GUIBank(object): Parameter für eine Callback-Funktion def __init__(self, bank, cb. Anzeigen, cb. Einzahlen, cb. Auszahlen, cb. Ueberweisen): # Referenzattribute zum Datenmodell self. bank = bank self. cb. Anzeigen = cb. Anzeigen self. cb. Einzahlen = cb. Einzahlen self. cb. Auszahlen = cb. Auszahlen self. cb. Ueberweisen = cb. Ueberweisen # Erzeugung des Fensters self. fenster = Tk() self. fenster. title("Bank") self. fenster. geometry('230 x 330'). . . # Button Anzeigen self. button. Anzeigen = Button(master=self. rahmen. Konto, text="anzeigen", command=self. cb. Anzeigen) self. button. Anzeigen. place(x=5, y=55, width=145, height=20). . .
Callback-Funktion 92 Eine Callback-Funktion (bzw. Rückruffunktion) ist eine Funktion, die einer anderen Funktion als Parameter übergeben wird und von dieser unter gewissen Bedingungen aufgerufen wird. class Controler(object): def __init__(self): # Erzeugung der Datenmodell-Objekte self. bank = Bank() self. bank. init. Bank() Callback-Funktion # Erzeugung und Initialisierung der GUI self. guibank = GUIBank(self. bank, self. button. Anzeigen, self. button. Einzahlen, . . . ) # Ereignisschreife self. guibank. fenster. mainloop() def button. Anzeigen(self): Callback-Funktion pin = int(self. guibank. entry. PIN. get()) if self. bank. existiert. PIN(pin): kunde = self. bank. get. Kunde(pin) self. guibank. label. Kontonummer. config(text=str(kunde. konto. nr)) self. guibank. label. Kontostand. config(text=str(kunde. konto. stand)) else: . . .
93 Strategie: Befragen Das Objekt guiuhr nutzt eine Strategie, die man Befragen oder Polling nennt. Das Objekt guiuhr weiß genau, wann sich das Datenmodell ändert (nach jedem Anklicken der Schaltfläche) und besorgt sich die geänderten Daten zur Anzeige auf dem vorgesehenen Label. class Uhr(object): . . . def tick(self): if self. sekunden < 59: self. sekunden = self. sekunden + 1 else: self. sekunden = 0 if self. minuten < 59: self. minuten = self. minuten + 1 else: . . . # Datenmodell from uhr import * uhr = Uhr() # GUI-Objekt from guiuhr import * guiuhr = GUIUhr(uhr) guiuhr. fenster. mainloop() from tkinter import * class GUIUhr(object): def __init__(self, uhr): # Referenzattribute zum Datenmodell self. uhr = uhr # Erzeugung des Fensters. . . # Button self. button. Tick = Button(. . . , command=self. Button_Tick_Click). . . def Button_Tick_Click(self): Änderung # Verarbeitung der Daten self. uhr. tick() Befragen # Anzeige der Daten uhrzeit = str(self. uhr. stunden) + ": " + str(self. uhr. minuten) + ": " + str(self. uhr. sekunden) self. label. Uhr. config(text=uhrzeit)
Strategie: Beobachten 94 Das Objekt guiuhr nutzt hier eine Strategie, die man Beobachten nennt. Das Objekt guiuhr wird als registriertes beobachtendes Objekt jeweils benachrichtigt, wenn sich das Datenmodell verändert hat. import threading, time class Timer(threading. Thread): . . . class Uhr(object): def __init__(self): . . . self. aktiv = False self. beobachter = None def set. Beobachter(self, beobachter): self. beobachter = beobachter . . . def stoppen(self): self. aktiv = False def tick(self): if self. sekunden < 59: self. sekunden = self. sekunden + 1 else: self. sekunden = 0. . . self. beobachter. anzeige. Aktualisieren() if self. aktiv: self. timer = Timer(1, self. tick) self. timer. start() Änderung def stellen(self, h, m, s): self. stunden. setzen(h) def ticken(self): Beobachter self. minuten. setzen(m) self. aktiv = True benachrichtigen self. sekunden. setzen(s) self. timer = Timer(1, self. tick) self. beobachter. anzeige. Aktualisieren() self. timer. start()
Strategie: Beobachten 95 Beachte, dass die Klassen Uhr und GUIUhr so gestaltet sind, dass die Trennung zwischen Datenmodell und GUI weiterhin Bestand hat. Erst zur Laufzeit wird der Beobachter des Datenmodellobjekts festgelegt. from tkinter import * class GUIUhr(object): def __init__(self, uhr): # Referenzattribute zum Datenmodell self. uhr = uhr. . . def Button_Start_Click(self): # Verarbeitung der Daten self. uhr. ticken() # Datenmodell from uhr import * uhr = Uhr() # GUI-Objekt from guiuhr import * guiuhr = GUIUhr(uhr) # Beobachter festlegen uhr. set. Beobachter(guiuhr) # Ereignisschleife starten guiuhr. fenster. mainloop() def Button_Stopp_Click(self): # Verarbeitung der Daten self. uhr. stoppen() def anzeige. Aktualisieren(self): # Anzeige der Daten uhrzeit = str(self. uhr. stunden)+": "+ str(self. uhr. minuten)+": " +str(self. uhr. sekunden) self. label. Uhr. config(text=uhrzeit)
96 Übungen Aufgabe 1 (siehe www. inf-schule. de 1. 12. 7. 8) Die Klasse Modulo. Zaehler beschreibe Zähler, die modulo einer vorgegebenen Zahl zählen. Entwickle einfache grafische Benutzeroberfläche, mit der man das Verhalten eines Modulo-Zählers verdeutlichen kann.
97 Übungen Aufgabe 2 (siehe www. inf-schule. de 1. 12. 7. 8) Ziel ist es, ein objektorientieres Datenmodell mit einer grafische Benutzeroberfläche zu verknüpfen. Als Miniwelt betrachten wir das Spiel chuck a luck.
98 Übungen Aufgabe 2 (siehe www. inf-schule. de 1. 12. 7. 8) Für die Benutzeroberfläche kannst du eine bereits entwickelte Implementierung übernehmen. Implementiere das gesamte Programm so, dass eine klare Trennung zwischen Datenmodell und GUI erkennbar ist.
99 Teil 6 Vererbung
100 Sparkonto Ein Sparkonto ist ein Konto, bei dem das eingezahlte Geld mit einem bestimmten, mit der Bank vereinbarten Zinssatz verzinst wird. Das folgende Klassendiagramm zeigt ein Sparkonto im Vergleich zu einem normalen Konto. Aufgabe: Vergleiche die Klassendiagramme. Inwiefern unterscheiden sich die Attribute und Methoden der beiden Klassen? Überleg dir auch, ob sich bestimmte Methoden evtl. anders verhalten. Beachte, dass man das den Klassendiagrammen nicht entnehmen kann. Hinweis: Welche Regelungen gibt es bei Auszahlungen von einem Sparkonto?
101 Sparkonto Ein Sparkonto kann als spezielles Konto angesehen werden, das - im Vergleich zu einem herkömmlichen Konto - nur in einigen Bestandteilen abgeändert worden ist. Aufgabe: Wie würde man ein solches Klassendiagramm wohl lesen?
102 Sparkonto class Konto(object): def __init__(self, nummer): self. nr = nummer self. stand = 0 self. minimum = -1000. 0 def einzahlen(self, betrag): self. stand = self. stand + betrag def auszahlen(self, betrag): if self. stand - betrag >= self. minimum: self. stand = self. stand - betrag else: print("Auszahlung nicht möglich!") Aufgabe: Analysiere die Implementierung der Klasse Sparkonto. Alles klar? Teste auch diese Implementierung. class Sparkonto(Konto): def __init__(self, nummer): Konto. __init__(self, nummer) self. zinssatz = None self. minimum = 0 self. max. Auszahlung = 2000. 0 def set. Zinssatz(self, zinssatz): self. zinssatz = zinssatz def auszahlen(self, betrag): if betrag <= self. max. Auszahlung: if self. stand - betrag >= self. minimum: self. stand = self. stand - betrag else: print("Auszahlung nicht möglich!") def zinsen. Gutschreiben(self): zinsen = self. stand * (self. zinssatz / 100) self. einzahlen(zinsen)
103 Vererbung beschreibt die Vorgehensweise, eine neue Klasse als Erweiterung einer bereits bestehenden Klasse (oder mehrerer bereits bestehender Klassen) zu entwickeln. Die neue Klasse wird auch Subklasse genannt, die bestehende Basisklasse oder Superklasse. Basisklasse / Superklasse Subklasse Übernehmen, ergänzen und überschreiben: Beim Vererben übernimmt die Subklasse die Attribute und Methoden der Basisklasse. Eine übernommene Methode kann dabei überschrieben (d. h. neu definiert) werden. Die Subklasse kann dann noch zusätzliche Attribute und Methoden ergänzen.
104 Implementierung in Python class Konto(object): def __init__(self, nummer): self. nr = nummer self. stand = 0 self. minimum = -1000. 0 Vererbung class Sparkonto(Konto): def __init__(self, nummer): Konto. __init__(self, nummer) self. zinssatz = None Ergänzen self. minimum = 0 Übernehmen self. max. Auszahlung = 2000. 0 def einzahlen(self, betrag): self. stand = self. stand + betrag def auszahlen(self, betrag): if self. stand - betrag >= self. minimum: self. stand = self. stand - betrag else: print("Auszahlung nicht möglich!") def set. Zinssatz(self, zinssatz): self. zinssatz = zinssatz Überschreiben def auszahlen(self, betrag): if betrag <= self. max. Auszahlung: if self. stand - betrag >= self. minimum: self. stand = self. stand - betrag else: print("Auszahlung nicht möglich!") def zinsen. Gutschreiben(self): zinsen = self. stand * (self. zinssatz / 100) self. einzahlen(zinsen)
105 Übungen Aufgabe (siehe www. inf-schule. de 1. 12. 7. 1 und 1. 12. 8. 5) Auf den folgenden Folien werden verschiedene Implementierungen einer grafischen Benutzeroberfläche gezeigt. Analysiere und erkläre, was in den jeweiligen Version unterschiedlich implementiert wird.
106 Übungen # Datenmodell from random import randint from wuerfel import * wuerfel = Wuerfel() class Wuerfel(object): # GUI def __init__(self): from tkinter import * self. augen = 1 # Ereignisverarbeitung def Wuerfel_Click(): def werfen(self): # Verarbeitung der Daten self. augen = randint(1, 6) wuerfel. werfen() # Anzeige der Daten label. Wuerfel. config(text=str(wuerfel. augen)) # Erzeugung des Fensters fenster = Tk() fenster. title("Würfel") fenster. geometry('120 x 110') # Eingabefeld für die Zahl label. Wuerfel = Label(master=fenster, text="1", background="#FBD 975") label. Wuerfel. place(x=45, y=40, width=30, height=30) # Button zum Auswerten button. Wuerfel = Button(master=fenster, text="Würfel werfen", command=Wuerfel_Click) button. Wuerfel. place(x=10, y=80, width=100, height=20) # Aktivierung des Fensters fenster. mainloop()
107 from tkinter import * Übungen # Datenmodell from wuerfel import * wuerfel = Wuerfel() # GUI-Objekt from guiwuerfel import * guiwuerfel = GUIWuerfel(wuerfel) guiwuerfel. fenster. mainloop() class GUIWuerfel(object): def __init__(self, wuerfel): # Referenzattribute zum Datenmodell self. wuerfel = wuerfel # Erzeugung des Fensters self. fenster = Tk() self. fenster. title("Bank") self. fenster. geometry('120 x 110') # Eingabefeld für die Zahl self. label. Wuerfel = Label(master=self. fenster, text="1", background="#FBD 975") self. label. Wuerfel. place(x=45, y=40, width=30, height=30) # Button zum Auswerten self. button. Wuerfel = Button(master=self. fenster, text="Würfel werfen", command=self. Wuerfel_Click) self. button. Wuerfel. place(x=10, y=80, width=100, height=20) def Wuerfel_Click(self): # Verarbeitung der Daten self. wuerfel. werfen() # Anzeige der Daten self. label. Wuerfel. config(text=str(self. wuerfel. augen))
108 from tkinter import * Übungen # Datenmodell from wuerfel import * wuerfel = Wuerfel() # GUI-Objekt from guiwuerfel import * guiwuerfel = GUIWuerfel(wuerfel) guiwuerfel. mainloop() class GUIWuerfel(Tk): def __init__(self, wuerfel): # Referenzattribute zum Datenmodell Tk. __init__(self) self. wuerfel = wuerfel # Erzeugung des Fensters self. title("Würfel") self. geometry('120 x 110') # Eingabefeld für die Zahl self. label. Wuerfel = Label(master=self, text="1", background="#FBD 975") self. label. Wuerfel. place(x=45, y=40, width=30, height=30) # Button zum Auswerten self. button. Wuerfel = Button(master=self, text="Würfel werfen", command=self. Button_Wuerfel_Click) self. button. Wuerfel. place(x=10, y=80, width=100, height=20) def Button_Wuerfel_Click(self): # Verarbeitung der Daten self. wuerfel. werfen() # Anzeige der Daten self. label. Wuerfel. config(text=str(self. wuerfel. augen))
109 Literaturhinweise Es gibt eine Vielzahl von fachwissenschaftlichen Darstellungen zur objektorientierten Modellierung und Programmierung. Hier wurden folgende Lehrwerke benutzt: - D. J. Barnes, M. Kölling: Objektorientierte Programmierung mit Java. Pearson Studium 2003. - Helmut Balzert: Lehrbuch Grundlagen der Informatik. Spektrum Ak. Verlag 1999. - Bernd Oestereich: Objektorientierte Softwareentwicklung. Oldenbourg 1998. Dagegen gibt es nur wenige Schulbücher, die systematisch in die objektorientierte Programmierung einführen, z. B. : - Siegfried Spolwig: Objektorientierung im Informatikunterricht. Dümmler-Verlag 1997. - P. Damann, J. Wemßen: Objektorientierte Programmierung mit Delphi, Band 2. Klett-Verlag 2003. Viele interessante Artikel mit Unterrichtsvorschlägen bzw. fachdidaktischen Auseinandersetzungen findet man in der Zeitschrift LOG IN. Das Themenheft 128/129 ist speziell dem Thema „Objektorientiertes Modellieren und Programmieren“ gewidmet. .
110 Literaturhinweise Im Internet findet man ebenfalls sehr viele schulgerechte Darstellungen der objektorientierten Modellierung und Programmierung, z. B: http: //informatikag. bildung-rp. de/ Die AG-Informatik des LMZ in RLP stellt u. a. auch Fortbildungsmaterialien zu diesem Thema bereit. http: //hsg. region-kaiserslautern. de/faecher/inf/index. php Auf der Homepage des HSG in Kaiserslautern findet man Unterrichtsmaterialien und Links zu weiteren interessanten Seiten. . Die Darstellung hier orientiert sich an den Materialien auf den Webseiten: http: //www. inf-schule. de
- Slides: 110