2.3 Wir erstellen eine neue Klasse THaus
Transcrição
2.3 Wir erstellen eine neue Klasse THaus
Seite 11 Informatik 10 [email protected] 2.3 Wir erstellen eine neue Klasse THaus zX Menschen kommen heim, öffnen die Haustür, schalten das Licht an, ... x Die Grundidee der objektorientierten Programmierung (OOP) ist es, ein Modell der Wirklichkeit zu schaffen mit Hilfe von Klassen gleichartiger Objekte. Die Objekte müssen intelligent genug sein, Aufgaben in eigener Verantwortung zu erledigen. Sie müssen miteinander kommunizieren können, Aufträge ausführen oder Anfragen beantworten. Dabei kann man einem Objekt sagen, was es tun soll, aber nicht, wie es etwas tun soll. Das muss das Objekt selber wissen (das heisst: der Programmierer muss diese Fähigkeiten der Klasse hinzugefügt haben). In diesem Abschnitt entwickeln wir eine Klasse THaus, deren Objekte so intelligent sind, dass sie sich selber zeichnen können, dass sie das Licht anschalten können und die Türe öffnen und schließen. Und sie sollen Fragen beantworten: „Ist das Licht an?“ und „Ist die Türe offen?“ Breite div 5 zY y Breite (= Höhe) Erstellt man in Delphi eine neue Objektklasse, so tut man dies normalerweise in einer eigenen Unit. Denn eine neue Klasse hat ja nicht direkt etwas mit unserer Unit1 und dem Hauptformular Form1 zu tun. Das gesamte Delphi-System besteht aus vielen Modulen, den Units, in denen die Objektklassen deklariert und implementiert wurden. ► Erstelle zunächst eine neue Delphi-Anwendung und speichere das Projekt in einem neuen Ordner. Öffne dann noch eine neue Unit (Datei/Neu.../Unit). Diese trägt den Namen Unit2. Ändere diesen in der erste Zeile in: unit Gebaeude1; Speichere diese Unit in den gleichen Ordner wie das ganze Projekt. Der Dateiname der Unit muss exakt gleich lauten wie der gerade festgelegte Name ’Gebaeude1’. Jetzt wechsle in die Unit1 und füge Gebaeude1 bei der Uses-Klausel hinzu, damit später das Hauptformular Form1 die Objekte der Klasse THaus verwenden kann. Damit ein Haus-Objekt die verlangten Fertigkeiten ausführen kann, muss es sich verschiedene Dinge merken. Die Zeichenfläche, auf die es sich zeichnen soll, gehört einem anderen Objekt (FBesitzer), zum Beispiel einem Fenster von der Klasse TForm. Das Haus muss dieses Objekt kennen, dazu die Position (FX, FY) und seine eigene Größe (FBreite) und Farbe (FFarbe). Beim Zeichnen der Fenster muss es wissen, ob gerade das Licht an ist oder nicht (FLichtAn). Die Werte dieser Attribute bestimmen den Zustand des Objekts. THaus = class(TObject) <<Attribute>> - FX, FY: integer - FBreite:integer - FFarbe: TColor - FBesitzer: TForm - FLichtAn: boolean <<Methoden>> + Create() {constructor} + Zeichnen() + LichtEinschalten() + LichtAusschalten() + IstLichtAn(): boolean Es ist eine stille Übereinkunft zwischen den Delphi-Programmierern, die Attribut-Bezeichner mit ’F’ (Field) zu beginnen Klassendiagramm von THaus, für den und die Klassen-Bezeichner mit ’T’ (Type). Anfang noch nicht vollständig Jede Klasse braucht eine Methode, welche die Objekte im [email protected] Informatik 10 Seite 12 Speicher des Computers erstellt. Diese nennt man Konstruktor. Ihr Bezeichner lautet meistens create(). Bisher haben wir Delphi-Objekte erzeugt, indem wir mit der Maus z.B. ein Button1 aus der Komponentenpalette geholt haben. In diesem Moment hat Delphi die Methode TButton.create ausgeführt und dem neuen Objekt den Bezeichner Button1 gegeben. Da wir Haus1, Haus2 ... nicht mit der Maus holen, müssen wir später an geeigneter Stelle der Unit1 dafür sorgen, dass THaus.create aufgerufen wird. ► Aufruf in einer Anwendung erklären, zB. TButton.Create oder TShape.Create : Aber wir wissen doch gar nicht, wie man ein Objekt erzeugt!?! Das ist auch nicht nötig, weil eine neue Klasse sehr elegant durch Vererbung von einer schon vorhandenen Klasse abgeleitet („vererbt“) wird. Die Klasse TForm1 einer Anwendung wird von der Klasse TForm vererbt. TForm ist der Vorfahre. TForm1 hat alle Eigenschaften und Methoden, die TForm besitzt, und dann kommen in unserer Unit1 noch weitere Eigenschaften, Objekte, Methoden dazu. Wie ist das mit unserer Klasse THaus? type THaus = class (TObject) Die Klasse TObject ist die Basis-Klasse für alle anderen Delphi-Klassen. Sie hat fast nichts! Aber eine Methode constructor create. Diese kann ein Objekt der Klasse erzeugen. Wenn wir im Konstruktor von THaus als erstes die geerbte Methode aufrufen mit inherited create, so brauchen wir nur noch das festlegen, was unsere Klasse zusätzlich hat: FX, FY, FBreite ... . constructor THaus.create(xPos,yPos,Breite: integer; F: TColor; Besitzer: TForm); begin inherited create; FX:= xPos; FY:= yPos; FBreite:=Breite; ... [email protected] Informatik 10 Seite 13 private oder public? Attribute und Methoden, die ich im Klassendiagramm mit einem ’-’ versehen habe, wollte ich unter private deklarieren, die mit einem ’+’ unter public. Für andere Objekte ist die Privatsphäre eines THaus-Objekts nicht sichtbar. Man kann also von Form1 aus nicht das Attribut FX ansprechen mit Haus1.FX, wohl aber seine Methode zeichnen aufrufen mit Haus1.zeichnen. Der Zweck ist ganz einfach: Wenn du die Unit Gebaeude1 fertig hast, soll nicht ein anderer Benutzer dieser Unit auf die Attribut-Werte zugreifen können. Er kennt ja meist gar nicht deren Bedeutung und würde ein heilloses Durcheinander anrichten. Nur über die public-Methoden des Objekts sollen die Attributwerte verändert werden können. Nur der Programmierer der Unit hat vollen Zugriff auf alle Attribute und Methoden. Merke: Attribute werden in der Regel unter private deklariert, Methoden unter public, sodass andere Objekte Zugriff auf die Attribut-Werte eines Objekts nur über seine Methoden erhalten. (Prinzip der Datenkapselung) [email protected] ► Informatik 10 Seite 14 Gib den Quelltext der Unit Gebaeude ein. Beschränke dich zuerst auf die Prozeduren create, zeichnen und zeichneWand. Bevor du weiterschreibst, teste deine neue Klasse THaus: Füge in der Unit1 der Klasse TForm1 unter private die Bezeichner Haus1, Haus2: THaus bei. Benutze das onClick-Ereignis eines Button (oder besser: das onCreate-Ereignis von Form1), um die beiden Objekte zu erschaffen: Haus1:=THaus.create(...); Haus2:=THaus.create(...); Wenn alles richtig ist, solltest du jetzt auf dem Formular zwei Rechtecke sehen. Das sind die noch unvollständigen Objekte Haus1 und Haus2. ► Schrittweises Fertigstellen der Klasse THaus ► Besprechen: Parameter-Übergabe, Unterschied zwischen procedure (Methode) und function (Funktions-Methode) ► Gib Form1 ein ganzes Array von Häusern H: array[1..5] of THaus; und initialisiere das Array mit for i := 1 to 5 do H[i]:=THaus.create(...) Dabei musst du die Position der Häuser von der Zählvariablen i abhängen lassen. ► Füge Lichtschalter hinzu. Aufgabe: ► Programmiere die Methoden TuereOeffnen() und TuereSchließen(), und die Funktionsmethode IstTuereOffen:boolean . Orientiere dich dabei an LichtEinschalten() . Bedenke, dass man durch eine offene Tür auch sieht, ob das Licht an ist. Informatik 10 [email protected] Seite 15 2.4 Wir modellieren eine einfache Bank (Vergleiche Diagramm S. 1 !) Systemanalyse: Was macht eine Bank? Abgrenzung: Unsere Bank soll nur Giro-Konten haben und nur mit ganzen €-Werten rechnen. Abstraktion: Wir brauchen mehrere Konten (meinKonto, Konto2 ...) und ein Objekt, das (zunächst) nur meinKonto verwaltet, nennen wir es Bank. Aggregation: Wir bilden eine Klasse TKonto und eine Klasse TBank. Idealisierung: TKonto braucht die Attribute Nummer, Inhaber, Kontostand und Methoden für Ein-/ Auszahlung, Überweisung und Einsichtnahme, also um den Kontostand zu erhöhen oder zu erniedrigen und mitzuteilen und den Kontostand eines anderen zu erhöhen. TBank braucht einen Automaten mit Buttons, Eingabefenster und die Fähigkeit, Konto-Objekte zu erzeugen und die Methoden der Konto-Objekte zu nutzen. (Später wird die Bank dazu eine Liste der Konto-Objekte führen müssen.) Strukturanalyse: Eine Bank verwaltet mehrere Konten auf diese Art. Modellbildung: Wir benutzen die Delphi-IDE als Programmierumgebung und definieren dort eine neue Delphi-Klasse TKonto in einer eigenen Unit. Als Bank benutzen wir ein gewöhnliches Delphi-Formular, nennen1 die Klasse TBank (statt TForm1) und das Objekt Bank statt Form1 und geben ihr Konten, Buttons, ein Edit (später eine ComboBox) und die Überschrift „Automat für mein Bank-Konto“. Dann bekommen TKonto und TBank noch ihre Methoden. TBank legen wir so einfach an, dass sie zunächst nur meinKonto verwaltet und von diesem auf ein zweites überweisen kann. TKonto = class(TObject) <<Attribute>> <<Eigenschaften>> - FNummer: string (viele geerbte Eigenschaften von TForm) - FInhaber: string meinKonto, Konto2...: TKonto - FKontostand: integer Edit1: TEdit <<Methoden>> EinzButton: TButton + constructor create (nr,inh:string) AuszButton: TButton + einzahlen(Betrag:integer) UeberwButton: TButton + auszahlen(Betrag:integer) ... + ueberweisen(Empfaenger:TKonto; Betrag:integer) <<Methoden>> + gibStand: string + gibNummerUndInhaber:string Beachte: Objekte von TKonto sind unsichtbare Objekte, im Gegensatz zu den Objekten der bisher besprochenen Delphi-Klassen. 1 TBank = class(TForm) (viele geerbte Methoden von TForm) EinzButtonClick AuszButtonClick UeberwButtonClick FormCreate Die Hauptklasse einer Delphi-Anwendung heißt standardmäßig immer TForm1 und das zugehörige Fenster-Objekt hat den Bezeichner Form1. Wenn du diese, wie oben vorgeschlagen, tatsächlich umbenennen willst, musst du das in Unit1.pas, in Unit1.dfm und in Project1.dpr machen. [email protected] Informatik 10 Seite 16 ► Die Unit Konten.pas vorstellen und besprechen, zusammen mit den Schülern schreiben; Teile können die Schüler selber schreiben. ► Neues Projekt erstellen, die Unit „dem Projekt hinzufügen“. Uses-Klausel ► Dann das Formular ’Bank’ Schritt für Schritt aufbauen und immer sofort testen. (Auch Abschreiben von Code bringt Lerneffekte.) Erst wenn die Anwendung in dieser Form funktioniert, dann: Aufgaben: 1. Auch von Konto2 soll beim Programmstart Nummer und Inhaber angezeigt werden. 2. Nach jeder Aktion der Bank soll das Editfenster gelöscht werden. 3. Füge noch ein drittes Konto hinzu, auf das man mit einem weiteren Button überweisen kann. Wer schon Programmierkenntnisse hat, - kann die Geldbeträge auch mit zweistelligen Dezimalzahlen machen. - der Bank eine Kontenliste (Objekt der Klasse TComboBox) hinzufügen, aus welcher der Empfänger einer Überweisung ausgewählt wird. [email protected] Informatik 10 Quellcode der Unit Konten mit der Klasse TKonto Seite 17 [email protected] Quellcode der Unit1 mit der Klasse TBank Informatik 10 Seite 18 [email protected] Informatik 10 Seite 19 anspruchsvolle Aufgabe: Folgendes Problem gibt es in vielerlei Versionen: Ein Weinhändler hat einen 8-Liter-Krug und einen 3Liter-Krug. Er möchte aus einem großen Fass genau 1 Liter Wein abfüllen. Wie muss er vorgehen? (Zu beachten ist: Die Krüge haben keinerlei Liter-Marken, und man hat auch sonst keine Hilfsmittel. Man kann nur vom Weinfass Wein auslaufen lassen, bis ein Krug randvoll ist oder von einem Krug in den anderen umfüllen, bis dieser voll oder der erste leer ist, oder den Krug ins Fass zurückschütten.) Das Umfüllen des Weins soll in einem Delphi-Programm simuliert werden. Dazu soll eine Klasse TGefaess in einer Unit Gefaesse deklariert werden, ähnlich zu der Klasse TKonto oder THaus. In einer Delphi-Anwendung sollen kleinerKrug, grosserKrug und Fass Objekte der Klasse TGefaess sein. Du kannst so vorgehen wie in THaus, wo die Objekte sich auf dem Canvas von Form1 zeichnen. Einfacher geht es jedoch, wenn du TGefaess von einer sichtbaren Delphi-Klasse vererbst, z.B. von TProgressBar. Diese hat schon die Fähigkeit sich zu zeichnen aber den Nachteil, dass sie rechteckig aussieht. Als Füllhöhe des Gefässes kannst du ihre Eigenschaft Position benutzen. Die Klassendeklaration lautet dann: TGefaess = class(TProgressBar) public constructor create(Besitzer:TForm; Volumen, Menge:integer; xPos, yPos:integer); procedure umfuellen(Ziel: TGefaess); end; Ein Objekt von der Klasse TProgressBar muss wie alle sichtbaren Delphi-Objekte seinen Besitzer (z.B. Form1) kennen, damit es sich auf dessen Canvas zeichnen kann. Deswegen übergeben wir den Besitzer auch in der create-Methode von TGefaess. Es hat außerdem alle Eigenschaften und Methoden, die TProgressBar hat, also z.B. Left, Top, Width, Max ... das kannst du wunderbar brauchen! Du kannst also in der create-Methode das Volumen an die Eigenschaft Max übergeben und die Füllmenge an die Eigenschaft Position. Dann zeichnet sich die Progressbar automatisch mit der richtigen Füllmenge. Du musst dann in der create-Methode als erstes die geerbte create-Methode von TProgressbar aufrufen. In der Hilfe siehst du, dass diese Methode als Parameter ebenfalls den Besitzer braucht. inherited create(Besitzer); parent:=Besitzer; Sichtbare Delphi-Objekte haben alle das Attribut parent (wie unser THaus das Attribut zBesitzer), in der sie sich ihren Besitzer merken. Deine Delphi-Anwendung braucht drei Objekte von der Klasse TGefaess, nämlich kleinerKrug, grosserKrug und Fass. In der Unit1 kannst du dann solche Objekte erschaffen z.B. so: kleinerKrug.create(Form1, 3, 0, 100,50); Damit erzeugst du einen Krug mit dem Volumen 3Liter, der Startmenge 0Liter an der Stelle (100,50). Mit mehreren Buttons soll man dann vom Fass in die Krüge umfüllen können und auch vom einen in den anderen Krug oder ins Fass zurück. Informatik 10 [email protected] Seite 20 2.5 Objektbeziehungen und Klassenbeziehungen Die objektorientierte Denkweise geht davon aus, dass ein System nur aus Objekten und ihren Beziehungen untereinander besteht. Gleichartige Objekte werden zu einer Klasse zusammengefasst und zwar nicht nur so, wie man Briefmarken in ein Album gibt oder gleichaltrige Schüler in eine Schulklasse. Eine Objekt-Klasse stellt den Bauplan für alle von ihr erzeugten Objekte dar. Diese sind absolut gleichartig und unterscheiden sich nur in den Attribut-Werten. Zwischen den Objekten bestehen Beziehungen. Form1 besitzt die Objekte Button1, Button2 und Edit1, verwaltet diese und benutzt ihre Methoden. Button1 sendet beim Ereignis onClick eine Botschaft an Form1. Form1 kann mit einer Ereignis-Methode darauf reagieren. Bank erzeugt und verwaltet mehrere TKonto-Objekte. Haus1 kennt seinen Besitzer Form1 und benutzt dessen Canvas, um sich darauf zu zeichnen. Manche Objekte können gar nicht ohne andere existieren: Jedes sichtbare Objekt wie Button1 braucht ein Objekt der Klasse TWinControl, von dem es verwaltet wird. Die Klasse TForm ist ein TWinControl (weil TForm in mehreren Schritten aus der Klasse TWinControl vererbt wurde). Objekte der Klasse TForm können deswegen dieses Aufgabe übernehmen. Um Beziehungen zwischen Objekten darzustellen benutzt man Klassendiagramme der Unified Modelling Language (UML). Diese Diagramme sind beim Entwurf umfangreicherer Programme unentbehrlich. Die Kennt-Beziehung (auch: Benutzt-Beziehung, in UML: „Assoziation“) Die Objekte unserer Klasse THaus kenTForm1 THaus nen ihren Besitzer, ein Objekt der Klasse 1 +Canvas: TCanvas -FBesitzer: TForm TForm1. Ihre Methode zeichnen() benutzt zeichnet sich auf ... ... die Zeichenfläche Canvas des Besitzers, * ...() ohne dass die Zeichenfläche die THaus- +zeichnen() ...() Objekte kennen muss. Diese Kennt-Beziehung ist eine gerichtete Assoziation zwischen den Objekten der beiden Klassen. An den Pfeilenden wird die Kardinalität der Beziehung angegeben: In unserem Programm benutzen beliebig viele (*) THaus-Objekte genau ein (1) TForm-Objekt. Die Hat-Beziehung (auch: Besitzt-Beziehung, in UML: „Aggregation“) Die Verbindungslinie mit der Raute zeigt an, dass ein Objekt der Klasse TForm jeweils ein Objekt der Klassen TCanvas, TBrush, TFont ... besitzt und verwaltet. Das gleiche gilt für Objekte, die wir in der Klassendeklaration von TForm1 hinzufügen. Ein Objekt der Klasse TForm1 (nämlich Form1) verwaltet sieben THaus-Objekte, ein TButton-Objekt und fünf TCheckbox-Objekte. Und weil TForm1 von TForm vererbt wurde, verwaltet Form1 auch ein TCanvas-Objekt, ein TBrush-Objekt usw.. Diese Hat-Beziehung bedeutet, dass das besitzende Objekt für die Existenz der anderen Objekte zuständig ist. Die Linien TForm TForm1 ... 1 TCanvas 1 TBrush 1 TFont ... 7 THaus 1 TButton 5 TCheckbox Informatik 10 [email protected] Seite 21 beschreiben gleichzeitig auch die möglichen Nachrichtenwege: Das besitzende Objekt hat die Möglichkeit, den verwalteten Objekten Nachrichten zu schicken, also ihre Methoden aufzurufen. Das bedeutet auch: Die Hat-Beziehung umfasst die Kennt-Beziehung. Insbesondere bedeutet Verwalten eines Objekts: Das besitzende Objekt führt das verwaltete Objekt in einer Objektvariablen oder in der Komponentenliste, erzeugt es durch Aufruf des Konstruktors der Klasse und ist auch verantwortlich dafür, dass es irgendwann aus dem Speicher entfernt wird. Von außerhalb des Besitzers ist ein verwaltetes Objekt nur über den Besitzer ansprechbar, z.B. Form1.Haus3.LichtAusschalten; oder: Paintbox1.Canvas.Moveto(20,20); Die Ist-Beziehung (auch: Is-a-Beziehung) Die Ist-Beziehung beschreibt die Hierarchie bei der Vererbung. Während „Hat“ und „Kennt“ Beziehungen zwischen zwei Objekten darstellen, beschreibt die Ist-Beziehung den Zusammenhang zwischen zwei Klassen: Die Klasse TForm1 ist ein TForm, weil sie von dieser Klasse abgeleitet wurde. TForm ist ein TWinControl, weil sie über mehrere Stufen von dieser Klasse abstammt. TWinControl ist Vorfahre (Oberklasse), TForm Nachkomme (Unterklasse). Der Nachkomme hat alle Merkmale des Vorfahren aber darüber hinaus noch zusätzliche Merkmale (Spezialisierung). TWinControl TScrollingWinControl TForm +Name: string +Caption: string +Color: TColor ... +Create() +Destroy() +Close() +Print() ...() TObject über mehrere Stufen +Create() +Destroy() TCustomform THaus TForm1 TForm TForm1 +Name: string +Caption: string +Color: TColor +Button1: TButton ... +Create() +Destroy() +Close() +Print() +Button1Click() ...() -FX, FY: integer -FBreite: integer -FFarbe: TColor -FBesitzer: TForm -FLichtAn: boolean +Create() +Destroy() +Zeichnen() +LichtEinschalten() +LichtAusschalten() +IstLichtAn(): boolean ► Delphi kann die Hat-Beziehungen zwischen den verwendeten Objekten im DiagrammFenster anzeigen. Hole dazu mit der Maus die Objekte aus dem Objekthierarchie-Fenster ins Diagramm. ► Studiere in der Delphi-Hilfe die Klassenhierarchie einiger Klassen. ► Zeichne zu unserem Bankprogramm die Klassendiagramme. ► Komplexere Strukturen werden häufig durch UML-Diagramme dargestellt. Ein UML-Modell von Wetterstationen findest du unter: http://ifgivor.uni-muenster.de/vorlesungen/Geoinformatik/kap/kap4/k04_2.htm Informatik 10 [email protected] Seite 22 2.6 Objekte und Klassen in einem Vektorgrafik-Programm Grafik-Programm ’Dia’ (Freeware) Klasse Ellipse Klasse Bogen <<Attribute>> <<Attribute>> Liniendicke Liniendicke Linienfarbe Linienfarbe Füllfarbe Startpfeil ... Endepfeil <<Methoden>> ... verschieben() <<Methoden>> strecken() verschieben() kopieren() strecken() ... biegen() kopieren() ... Die Eigenschaftswerte der Objekte werden mit der Maus eingestellt oder im Fenster ’Properties’ (Doppelclick auf das Objekt!). Die Methoden der Objekte erreicht man ebenfalls mit Maus oder Tastatur. ► UML-Klassen-Diagramm zeichnen ► Zustandsdiagramm (sh. 11.Kl.) zeichnen (z.B. für Parallelschaltung) ► ER-Diagramm zeichnen (sh. Datenbank-Skript)