Lerneinheit 1: Klassen und Objekte Wir beschäftigen uns mit ● der Erstellung gekapselter Klassen sowie der Instanzierung von Objekten, ● Methoden, Eigenschaften, Zugriffsmethoden und Konstruktoren, ● der Nutzung von Vererbung und Polymorphie, ● dem Zugriff auf XML-Dateien und Datenbanken sowie ● der Verwendung generischer Listen und Funktionszeiger. Lerneinheit 2: Vererbung ro Lerneinheit 1: Klassen und Objekte 54 54 57 59 61 63 67 69 71 71 Lernen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 Subklassen . . . . . . . . . . . . . . . . . . . . . . . 2 Konstruktoren in Subklassen . . . . . . . . . 3 Methoden überschreiben . . . . . . . . . . . . 4 Polymorphie . . . . . . . . . . . . . . . . . . . . . . 5 Abstrakte und versiegelte Klassen . . . . . 6 Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . Üben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Sichern . . . . . . . . . . . . . . . . . . . . . . . . . . . . Wissen . . . . . . . . . . . . . . . . . . . . . . . . . . . . sep Lernen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 Klasse erstellen . . . . . . . . . . . . . . . . . . . . 2 Eigenschaften und Methoden . . . . . . . 3 Kapselung . . . . . . . . . . . . . . . . . . . . . . . 4 Zugriffsmethoden . . . . . . . . . . . . . . . . . 5 Konstruktor und Destruktor . . . . . . . . . . 6 Statische Elemente . . . . . . . . . . . . . . . . . Üben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Sichern . . . . . . . . . . . . . . . . . . . . . . . . . . . Wissen . . . . . . . . . . . . . . . . . . . . . . . . . . . . Lerneinheit 3: Weitere OOP-Konzepte Le Lernen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 Generics . . . . . . . . . . . . . . . . . . . . . . . . . . 2 Innere Klassen . . . . . . . . . . . . . . . . . . . . . 3 Indexer . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 Delegates und Events . . . . . . . . . . . . . . . Üben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Sichern . . . . . . . . . . . . . . . . . . . . . . . . . . . . Wissen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73 73 76 78 79 81 83 85 87 87 Lerneinheit 4: Datenspeicherung 89 89 92 93 95 97 98 99 Lernen . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 Drei-Schichten-Architektur . . . . . . . . . . 2 Textdateien . . . . . . . . . . . . . . . . . . . . . . 3 Dateisystem-Operationen . . . . . . . . . . . 4 XML-Serialisierung . . . . . . . . . . . . . . . . 5 Datenbankzugriff mit ADO.NET . . . . . 6 DataGridView-Control . . . . . . . . . . . . . Üben . . . . . . . . . . . . . . . . . . . . . . . . . . . . Sichern . . . . . . . . . . . . . . . . . . . . . . . . . . . Wissen . . . . . . . . . . . . . . . . . . . . . . . . . . . 100 100 101 104 106 110 117 119 120 120 Kapitelrückblick . . . . . . . . . . . . . . . . . . . . 122 Ich bin Ms. Check! Ich kenne mich sehr gut aus und habe auf jede Frage eine verständliche Antwort. Ich bin Mr. What! Ich kenne mich ganz gut aus, habe aber immer wieder einige Zwischenfragen … Softwareentwicklung 53 Objektorientiertes Programmieren Programmieren 22 Objektorientiertes In diesem Kapitel erarbeiten wir die Konzepte der objektorientierten Programmierung, wie gekapselte Klassen, Instanzierung von Objekten und Verwendung von Vererbung und Polymorphie, anhand der Programmiersprache C#.NET. be 2 Objektorientiertes Programmieren Lernen Üben Sichern Wissen Lerneinheit 1 Klassen und Objekte Alle SbX-Inhalte zu dieser Lerneinheit findest du unter der ID: 1178. In dieser Lerneinheit lernen wir die wichtigsten Basiskonzepte objektorientierter Programmierung, wie z.B. Klassen und Objekte sowie deren Eigenschaften und Methoden, kennen. Wir beschäftigen uns mit dem Grundprinzip der Kapselung sowie deren Auswirkung auf Klassen und Objekte. Außerdem sehen wir uns an, wie die Kapselung anhand von Zugriffsmethoden realisiert wird. In einem immer komplexer werdenden Lehrbeispiel beschäftigen wir uns mit 1 Klasse erstellen Das Objekt-Datenmodell ro Lernen be ● der Erstellung von Klassen und der Instanzierung von Objekten, ● gekapselten Eigenschaften, Methoden und Zugriffsmethoden, ● Konstruktoren, Destruktoren sowie dem Überladen von Methoden und ● mit der Verwendung statischer Elemente. sep Wie könnten wir einem Außerirdischen erklären, was wir unter einem Auto verstehen? Natürlich wissen wir alle, was ein Auto ist. Aber wie können wir ein Auto ganz allgemein beschreiben? Es hat z.B. meist vier Räder, aber auch ein Auto mit sechs Rädern wäre vorstellbar. Es hat ein Lenkrad, aber auch ein Fahrschulauto mit zwei Lenkrädern wäre denkbar. Es hat einen Motor, aber auch ein Modellauto ohne Motor ist ein Auto. Also, was ist nun eigentlich ein Auto? Ü 1: Beschreibe generelle Merkmale für jedes auf der Welt existierende Auto. Le Wie wir gesehen haben, ist es kaum möglich, für Objekte der Wirklichkeit allgemeingültige, konkrete Eigenschaften zu nennen. Nicht jedes Auto hat vier Räder, nicht jedes Auto hat nur ein Lenkrad. Aber – jedes Auto hat Räder und mindestens ein Lenkrad! Wie viele Räder und Lenkräder es hat, kann im Allgemeinen nicht festgelegt werden. Zugegeben – diese Art der Darstellung ist sehr abstrakt, aber für jedes Auto gültig. Klasse und Objekt Eine Klasse beschreibt generelle Merkmale und Funktionen. Ein Objekt ist eine konkrete Ausprägung einer Klasse. 1 Eine Klasse beschreibt allgemeine, abstrakte Merkmale, die für alle der Klasse zugehörigen Objekte gültig sind. Die Klasse Auto weist z.B. folgende Merkmale auf: Räder, Lenkrad, Türen, Sitze, Fahren, Tanken. Jedes Auto muss Maut bezahlen, wenn es die Autobahn benutzt. 2 Ein Objekt ist eine konkrete Ausprägung einer Klasse. Ein Objekt der Klasse Auto wäre z.B. der blaue BMW 320d von Julia Kern mit dem amtlichen Kennzeichen S-JULY1, einem Kilometerstand von 67.845 km und einem aktuellen Tankinhalt von 23,7 Litern Diesel. Die folgende Grafik veranschaulicht den Zusammenhang zwischen Klasse und Objekt. 54 Softwareentwicklung Lerneinheit 1: Klassen und Objekte Klasse Auto 2 Objektorientiertes Programmieren Objekt Mercedes Objekt Porsche be Objekt BMW Objekt Audi Objekte sind Instanzen einer Klasse. Abb.: Klasse und Objekt ro Diese Abbildung findest du in der PowerPointPräsentation unter der ID: 1179. Vererbung Eine Klasse wie z.B. Auto kann aus weiteren Klassen, sogenannten Subklassen, bestehen. So könnte es beispielsweise eine Klasse PKW und eine Klasse LKW geben. Die Klasse PKW könnte wiederum in eine Klasse Benzin und eine Klasse Diesel unterteilt werden. Die Subklasse ist die untergeordnete Klasse, die von der Basisklasse erbt. Vererbung ist ein Grundprinzip objektorientierter Programmierung. Dabei erbt eine Subklasse alle Merkmale und Funktionen einer übergeordneten Klasse, der Basisklasse. Mit der Anwendung der Vererbung beschäftigen wir uns in der nächsten Lerneinheit in diesem Kapitel. sep Die Basisklasse ist die übergeordnete Klasse. L 1: Welche Merkmale und Funktionen enthalten die Klassen Auto, PKW und LKW? Mr. What und Ms. Check Le Auto: Kennzeichen, Marke, Verbrauch, Tankinhalt, Kraftstoffvorrat, Fahren, Tanken PKW: alle Merkmale von Auto + Transportkapazität, Sitzplatzanzahl LKW: alle Merkmale von Auto + Transportkapazität, Achszahl Welche Merkmale und Funktionen hat die Klasse Schüler? Bin ich eine Klasse oder ein Objekt? Vorname, Zuname, Klasse, Katalognummer, Erziehungsberechtigte, Fächer, Lernen, Prüfung, Noten usw. Du bist ein Objekt, da du konkrete Merkmale und Funktionen aufweist. Ü 2: Handelt es sich bei den folgenden Aussagen um Angaben zu einer Klasse oder einem Objekt? 1. Ein Auto hat ein Reserverad. 2. Ein PKW mit Anhänger darf auf dem Firmenparkplatz nicht parken. 3. Ein Auto ist blau. 4. Ein PKW kann Personen befördern. 5. Ein Auto hat eine Klimaanlage. Softwareentwicklung 55 Lernen Üben Sichern Wissen Objekte aus einer Klasse instanzieren Im Rahmen der objektorientierten Programmierung verwenden wir Klassen, in denen Eigenschaften und Operationen (Methoden) festgelegt werden. Bei der Erzeugung eines Objektes wird der Inhalt einer Klasse auf das Objekt übertragen, ein Objekt wird daher auch als Instanz einer Klasse bezeichnet. Klasse Auto Datentyp Objekt audi Konstruktor Abb.: Objekt aus einer Klasse instanzieren Instanzierung Konstruktormethode sep Diese Abbildung findest du in der PowerPointPräsentation unter der ID: 1179. ro Jede Klasse erhält automatisch eine StandardKonstruktormethode ohne Parameter, z.B. Auto(). Objektvariable be Der Konstruktor new ruft die Konstruktormethode Auto() auf, die das Objekt erzeugt. 1 Ein Objekt wird in einer Objektvariablen mit dem Datentyp der Klasse gespeichert. Objektvariablen: Auto audi; Auto bmw; Objekte erzeugen: audi = new Auto(); bmw = new Auto(); Hinweis Die Deklaration der Objektvariablen mittels Auto audi erzeugt die Variable audi vom Datentyp der angegebenen Klasse Auto. Bei diesem Vorgang wird noch kein Objekt instanziert! Der Inhalt der Objektvariablen audi ist zu diesem Zeitpunkt null. 2 Ein Objekt wird durch den Aufruf eines Konstruktors mit einer entsprechenden Konstruktormethode instanziert. Le null = kein Wert Die Initialisierung der Objektvariablen audi mit new Auto() erzeugt ein neues Objekt, indem die Konstruktormethode Auto() aus der Klasse Auto aufgerufen wird. Jede Klasse hat eine Standard-Konstruktormethode, die ein neues Objekt aus der Klasse erzeugt. Der Name dieses Standardkonstruktors ist der Klassenname, z.B. Auto(). L 2: Klasse mit Objekten Erstelle die Klasse Auto mit den Eigenschaften Kennzeichen und Marke vom Typ String und erzeuge die Objekte Audi und BMW. class Auto // Deklaration der Klasse Auto { string kennzeichen, marke; // Deklaration der Eigenschaften } Die Variablen audi und bmw sind vom Typ der Klasse Auto und beinhalten die Objekte. 56 Auto audi = new Auto(); // Objekt Audi aus Auto instanzieren Auto bmw = new Auto(); // Objekt BMW aus Auto instanzieren Softwareentwicklung Lerneinheit 1: Klassen und Objekte 2 Eigenschaften und Methoden Inhalte von Klassen und Objekten Wir haben gesehen, dass eine Klasse abstrakte Inhalte hat, während ein Objekt über sehr konkrete Inhalte verfügt. Doch welche Inhalte können das sein? besteht aus 2 Objektorientiertes Programmieren Sehen wir uns anhand der folgenden Abbildung die Inhalte der Klasse Auto genauer an: besteht aus Klasse Auto Methoden be Eigenschaften Marke Fahren() Kennzeichen Verbrauch Tanken() Kraftstoffvorrat Diese Abbildung findest du in der PowerPointPräsentation unter der ID: 1179. Fahrtkosten ro Tankinhalt Abb.: Eigenschaften und Methoden der Klasse Auto Eigenschaften der Klasse Marke Kennzeichen Eigenschaften Objekt 1 Eigenschaften Objekt 2 Eigenschaften Objekt 3 Audi A6 BMW 525d Porsche 911 W-702AB KS-545AM S-911C 8,9 l/100 km 7,5 l/100 km 16,2 l/100 km Tankinhalt 80 l 75 l 70 l Kraftstoffvorrat 45,6 l 12,9 l 58,4 l Fahrtkosten 195,80 EUR 157,50 EUR 356,40 EUR Le Verbrauch sep Die Klasse Auto beschreibt Eigenschaften und Methoden für alle Autos. Jedes Auto-Objekt besitzt die gleichen Eigenschaften und Methoden wie die gesamte Klasse. Aber jedes Objekt kann für eine Eigenschaft einen eigenen konkreten Wert enthalten. So könnte es in einem Programm beispielsweise folgende Auto-Objekte geben: Eigenschaften und Methoden Eigenschaften sind Variablen einer Klasse. Methoden sind Prozeduren bzw. Funktionen einer Klasse. 1 Eine Eigenschaft beschreibt den Zustand eines Objekts. Jedes Auto-Objekt aus der Klasse Auto hat einen Verbrauch und einen Benzinvorrat. Die Eigenschaften verändern sich, wenn ein Auto fährt oder aufgetankt wird. 2 Eine Methode beschreibt das Verhalten eines Objekts. Die Methode Fahren() erhöht für jedes Auto-Objekt die Eigenschaften Fahrtkosten und vermindert den Kraftstoffvorrat. Die Methode Tanken() erhöht den Kraftstoffvorrat. Eigenschaften können durch Methoden beeinflusst und verändert werden. Die Methode Tanken() hat z.B. die Aufgabe, den Kraftstoffvorrat zu erhöhen. Jedes Objekt ist in der Lage, seine eigenen Eigenschaften zu speichern und durch seine Methoden zu verändern. Die Eigenschaften von Objekten können jederzeit abgefragt werden. Softwareentwicklung 57 Lernen Üben Sichern Wissen Ü 3: Bei welchen dieser Inhalte handelt es sich um Eigenschaften, bei welchen um Methoden? 1. Tanken 2. Tankinhalt 3. Maut entrichten 4. Anzahl der Sitzplätze 5. Durchschnittlicher Kraftstoffverbrauch 6. Serviceintervall 7. Service durchführen 8. Farbe Ist die Farbe Rot eines VW-Käfers die Eigenschaft einer Klasse oder eines Objekts? Für die Klasse Schüler wären z.B. folgende Methoden denkbar: Lernen(), Prüfung(), Zeugnis(), Aufsteigen(), Wiederholen(), Schulabschluss() usw. ro Welche Methoden könnte es bei einer Klasse Schüler geben? Eine Eigenschaft der Klasse Auto ist die Farbe. Rot ist eine konkrete Farbe und gehört daher zu einem bestimmten Auto-Objekt. be Mr. What und Ms. Check Wir haben nun die wichtigsten Bestandteile einer Klasse kennengelernt. Im Rahmen eines immer komplexer werdenden Lehrbeispiels wenden wir unsere Kenntnisse an. sep Das Lehrbeispiel „Frachtkostenoptimierung“ Le In diesem Kapitel erstellen wir Schritt für Schritt eine Anwendung in der Programmiersprache C#. In dieser Lerneinheit beginnen wir mit der Klasse Auto sowie der Erstellung von AutoObjekten. Unsere Anwendung soll das günstigste Transportmittel aus einem vorhandenen Fuhrpark ermitteln. Im Fuhrpark gibt es Autos mit unterschiedlichen Transportkapazitäten und Kosten pro gefahrenem Kilometer. Nachdem der Anwender das Transportgewicht sowie die Fahrtstrecke eingegeben hat, soll das Programm die Kosten für jedes Auto berechnen und jenes mit den geringsten Gesamtkosten ermitteln. L 3: Klasse Wir beginnen in Visual Studio 2005 eine neue C#-Windows-Anwendung und erstellen eine neue Klasse mit dem Namen „Auto“. namespace cs_Frachtkostenoptimierung { class Auto { // Eigenschaften string kennzeichen, marke; double verbrauch, tankinhalt, kraftstoffvorrat, fahrtkosten; Beim Tanken wird der Kraftstoffvorrat um die getankte Menge erhöht. 58 // Methode Tanken void Tanken(double liter) { kraftstoffvorrat += liter; } } } Softwareentwicklung Lerneinheit 1: Klassen und Objekte Das wichtigste Grundprinzip objektorientierter Programmierung ist die Eigenständigkeit der Klassen bzw. Objekte. Ein Objekt darf nur gültige Werte in seinen Eigenschaften speichern und nur dann eine Methode ausführen, wenn dies auch zulässig ist. Über definierte Schnittstellen kann jeder beliebige Programmierer Klassen und Objekte benutzen. Dieses Prinzip nennt sich Kapselung. Objekte sind eigenverantwortlich. be Ein wichtiges Ziel objektorientierter Programmierung ist die Modularisierung von Programmkomponenten, damit auch andere Programmierer mit diesen Komponenten arbeiten können. Dazu ist es notwendig, dass Klassen möglichst selbständig funktionieren und fehlerhafte Aufrufe oder Daten nicht zulassen. Die Umsetzung dieses Grundsatzes wird als Kapselung bezeichnet. Kapselung Kapselung bedeutet Zusammenfassen von Daten und Methoden. bedeutet, dass 1 Methoden für ihre korrekte Funktion selbst verantwortlich sind und dass ro 2 Eigenschaften nur durch zulässige Methodenaufrufe verändert werden dürfen. Eigenschaften sind private Diese Grafik findest du in der PowerPointPräsentation unter der ID: 1179. Beachte Auto Kein Zugriff Zugriff kraftstoffvorrat fahrtkosten Fahren(km) Tanken(liter) Ein direkter Zugriff auf Eigenschaften von außen würde die Kontrollfunktion der Methoden unterlaufen. Aus diesem Grund dürfen Eigenschaften nicht von außen veränderbar sein. Sie sind in der Klasse eingekapselt und können nur mit Hilfe von Methoden (Zugriffsmethoden) gelesen oder verändert werden. Le Methoden sind public sep Die Fahrtkosten dürfen nur durch das Fahren des Autos verändert werden. Außerdem dürfen sich die Fahrtkosten nur erhöhen und niemals verringern, z.B. wenn ein Auto rückwärts fährt. Gleichzeitig vermindert die Mehode Fahren() den Kraftstoffvorrat. Die Kontrolle darüber hat die Methode Fahren(). Das Tanken bewirkt die Erhöhung des Kraftstoffvorrates. Die Methode Tanken() muss prüfen, dass der Tank nicht überläuft. Abb.: Kapselung der Klasse Auto Eigenschaften dürfen von außen nur mittels Methoden gelesen oder verändert werden. Daher sind Eigenschaften innerhalb einer Klasse gekapselt und niemals als public deklariert. Aus dem Prinzip der Kapselung ergibt sich zwangsläufig, dass nur über genau festgelegte Schnittstellen, nämlich die Methoden, auf eine Klasse bzw. ein Objekt zugegriffen werden kann. Würde ein Programmierer Eigenschaften von außen zugänglich machen, so könnten diese Eigenschaften ohne die Kontrolle der Methoden verändert werden. In unserem Beispiel wäre es dann z.B. denkbar, die Transportkosten eines Auto-Objektes auf betrügerische Art und Weise zu ändern, um einem bestimmten Auto den Vorzug zu geben. Softwareentwicklung 59 2 Objektorientiertes Programmieren 3 Kapselung Lernen Üben Sichern Wissen Grundsätze der Kapselung private Eigenschaft: ist nur ihrer Klasse bekannt protected Eigenschaft: ist der Klasse und ihren Subklassen bekannt 1 Eigenschaften sind nur Objekten der eigenen Klasse (private) oder einer Subklasse (protected) bekannt. Die Eigenschaft Verbrauch ist für die Klasse Auto und für jedes Auto-Objekt eine als private festgelegte Variable. Wenn Verbrauch auch bei der Klasse LKW, die alle Eigenschaften von Auto erbt, bekannt ist, wird die Variable als protected deklariert. 2 Eigenschaften dürfen nur durch Methoden verändert werden. Die Eigenschaft Kraftstoffvorrat darf nur durch die Methode Tanken() erhöht werden. Eine Reduzierung des Kraftstoffvorrates ist nur durch die Methode Fahren() möglich. be 3 Methoden bedienen sich der Eigenschaften einer Klasse. Öffentliche Methoden dienen als Schnittstelle der Klassen. Private Methoden dienen innerhalb der Klasse als Hilfsmethoden. Die öffentliche Methode Fahren() reduziert z.B. die Eigenschaft Kraftstoffvorrat um den verbrauchten Kraftstoff, egal ob das Auto vorwärts oder rückwärts fährt. Welchen Vorteil haben Kapseln bei Veränderungen von Objekteigenschaften? Kapseln kontrollieren vor einer Veränderung, ob diese zulässig ist. Klassen können als Komponenten auch von anderen Entwicklern verwendet werden, bieten genau definierte Schnittstellen nach außen und sorgen selbst für ihre korrekte Verwendung. sep Welchen Vorteil haben Kapseln bei der objektorientierten Programmierung? ro Mr. What und Ms. Check L 4: Kapselung Die Klasse Auto soll dem Prinzip der Kapselung entsprechen. Eigenschaften dürfen von außen nicht direkt beeinflusst werden, sondern werden über die Methoden verändert. Methoden sind public, da sie von außerhalb der Klasse bzw. des Objektes aufgerufen werden. Die Methoden sorgen für gültige Werte in den Eigenschaften. 60 Le Eigenschaften dürfen nicht public sein. namespace cs_Frachtkostenoptimierung { class Auto { // Eigenschaften private string kennzeichen, marke; private double verbrauch, tankinhalt, kraftstoffvorrat, fahrtkosten; // Methode Tanken public void Tanken(double liter) { if (kraftstoffvorrat + liter > tankinhalt) throw new Exception("So viel passt nicht in den Tank."); else kraftstoffvorrat += liter; } } } Softwareentwicklung Lerneinheit 1: Klassen und Objekte Wir haben gelernt, dass wir Klassen kapseln müssen und Eigenschaften niemals von außen zugänglich machen dürfen. Um dennoch mit Eigenschaften effizient arbeiten zu können, benötigen wir Zugriffsmethoden als Schnittstelle nach außen. Nun besprechen wir, welche Zugriffsmethoden es gibt und welche Aufgaben sie erfüllen. 4 Zugriffsmethoden Beachte Die Kapselung bewirkt, dass Eigenschaften nur noch der Klasse bzw. dem aus der Klasse erzeugten Objekt bekannt sind. Für jeden Zugriff auf eine Eigenschaft benötigen wir daher eine Methode – eine Zugriffsmethode. be Property bedeutet Zugriffsmethode. Eine Methode, deren Aufgabe darin besteht, eine Eigenschaft zu lesen (Get-Property) oder zu verändern (Set-Property), nennt man eine Zugriffsmethode. Eine Zugriffsmethode kann entweder nur ein Get-, nur ein Set- oder ein Get- und ein SetProperty implementieren. ro Ein Auto-Objekt hat die Eigenschaften Kraftstoffvorrat und Fahrtkosten sowie die Methoden Fahren() und Tanken(), welche diese beiden Eigenschaften verändern. Beim Fahren werden die Fahrtkosten erhöht und der Kraftstoffvorrat verringert. Beim Tanken wird der Kraftstoffvorrat erhöht. Über Zugriffsmethoden können wir die berechneten Fahrtkosten und den Kraftstoffvorrat eines Auto-Objekts abfragen. Ein Get-Property dient zum Lesen einer Eigenschaft. Die Veränderung einer Eigenschaft erfolgt mittels Set-Property. sep Auto kraftstoffvorrat fahrtkosten In C# haben Eigenschaften einen kleinen und Properties einen großen Anfangsbuchstaben. Methode Zugriff von außen Diese Abbildung findest du in der PowerPointPräsentation unter der ID: 1179. Fahrtkosten Kraftstoffvorrat Le Property Fahren() Tanken() Abb.: Zugriffsmethoden (Properties) für Kraftstoffvorrat und Fahrtkosten Anwendung von Properties 1 Ein Property ist eine Methode zum Lesen oder Schreiben von Eigenschaften. Es ist immer public und dient als Schnittstelle für eine Eigenschaft. Das Property Fahrtkosten ist als public deklariert. Es liest die private Eigenschaft Fahrtkosten und liefert das Ergebnis nach außen. Ein Get-Property dient zum Lesen von Eigenschaften. Ein Set-Property dient dem Setzen von Eigenschaften. Softwareentwicklung 2 Get-Properties ermöglichen das Lesen von Eigenschaften. Die Eigenschaften Fahrtkosten und Kraftstoffvorrat haben Get-Properties. 3 Set-Properties ermöglichen das Ändern von Eigenschaften. Anstelle der Methode Tanken() könnte auch die Eigenschaft Kraftstoffvorrat ein Set-Property haben. Von dieser Methode müsste dann geprüft werden, ob eine gültige Kraftstoffmenge getankt wird und dieser neue Kraftstoffvorrat auch wirklich in den Tank passt. 61 2 Objektorientiertes Programmieren „Property“ ist nicht gleich „Eigenschaft“. Lernen Üben Mr. What und Ms. Check Sichern Wissen Was unterscheidet ein Property von einer Eigenschaft? Eigenschaften sind nur der Klasse bekannt. Ein Property ermöglicht den Zugriff auf eine Eigenschaft von außen. Worin unterscheiden sich Properties von anderen Methoden? Properties haben die Funktion, eine bestimmte Eigenschaft zu lesen oder zu ändern. Eine Methode ist in der Lage, mehrere Eigenschaften gleichzeitig zu ändern. Was sind Get- und Set-Properties? Muss es immer beide für eine Eigenschaft geben? Ein Get-Property liest und ein Set-Property verändert eine Eigenschaft. Je nach Situation kann es kein Property, ein Set-, ein Get- oder beide Properties geben. Warum muss ich überhaupt Properties verwenden? Wäre es nicht einfacher, die Eigenschaften public zu deklarieren? ro be Ja, aber dann würden wir die Kontrolle über die Eigenschaften verlieren und jedes Programm könnte Objekteigenschaften beliebig verändern. Da der Grundsatz der Kapselung genau dies vermeiden soll, sind Properties eine sinnvolle Möglichkeit, die Kapselung umzusetzen. L 5: Properties Für die Eigenschaften Kraftstoffvorrat und Fahrtkosten der Klasse Auto sollen Zugriffsmethoden (Properties) erstellt werden, die nur das Lesen der Eigenschaften erlauben. sep namespace cs_Frachtkostenoptimierung { class Auto { // Eigenschaften private string kennzeichen, marke; private double verbrauch, tankinhalt, kraftstoffvorrat, fahrtkosten; Le // Methode Tanken public void Tanken(double liter) { if (kraftstoffvorrat + liter > tankinhalt) throw new Exception("So viel passt nicht in den Tank."); else kraftstoffvorrat += liter; } Properties ermöglichen den Zugriff auf die gekapselten Eigenschaften. Der Wert der Eigenschaft wird mit return an das aufrufende Programm zurückgegeben. 62 // Get-Properties public double Kraftstoffvorrat { get { return kraftstoffvorrat; } } public double Fahrtkosten { get { return fahrtkosten; } } } } Softwareentwicklung Lerneinheit 1: Klassen und Objekte Der übergebene Wert wird in value repräsentiert. be Auch ein Set-Property muss die Gültigkeit der Eigenschaft überprüfen. // Get- und Set-Property public double Kraftstoffvorrat { get { return kraftstoffvorrat; } set { if (kraftstoffvorrat + value > tankinhalt) throw new Exception("So viel passt nicht in den Tank."); else kraftstoffvorrat += value; } } ro Die Aufgabe einer Klasse besteht darin, die erforderlichen Methoden und Eigenschaften für ihre Objekte festzulegen. Ein Objekt wird aus einer Klasse erzeugt – instanziert. Das Objekt wird als Instanz einer Klasse bezeichnet. Für die Konstruktion des Objektes gibt es in einer Klasse eine dafür verantwortliche Methode: den Konstruktor. 5 Konstruktor und Destruktor Objekte erzeugen und zerstören Ein Konstruktor ist eine Methode, die beim Erzeugen eines neuen Objektes aufgerufen wird. sep Unsere Klasse Auto besteht nun aus Eigenschaften, Properties und Methoden. Um ein AutoObjekt erzeugen zu können, benötigen wir einen Konstruktor: Jede Klasse enthält einen Standardkonstruktor – es sei denn, wir schreiben selbst eigene Konstruktoren. Der Standardkonstruktor hat keine Parameter und keine Implementierung. Die Konstruktormethode trägt den Namen der Klasse, z.B. Auto(). Es kann viele Konstruktormethoden mit unterschiedlichen Parametern geben. Wir nennen diese überladene Konstruktormethoden. Le Auto objekt = new Auto(); Konstruktor Objekt erzeugen Konstruktormethode Diese Abbildung findest du in der PowerPointPräsentation unter der ID: 1179. Der Garbage-Collector ist die Müllabfuhr in .NET und entsorgt nicht mehr benötigte Objekte, um Ressourcen und Speicher freizugeben. Softwareentwicklung Auto kraftstoffvorrat fahrtkosten Tanken() Fahren() Kraftstoffvorrat Fahrtkosten Auto() Abb.: Konstruktormethode Auto() Die Destruktormethode wird beim Zerstören eines Objektes aufgerufen. Ein Objekt wird durch den Garbage-Collector verworfen, nachdem wir der Objektvariablen null zugewiesen haben. Wann der Garbage-Collector den Destruktor aufruft ist nicht vorhersehbar. Die Destruktormethode erhält den Namen der Klasse mit einer voranstehenden Tilde, z.B. ~Auto(). Wie beim Konstruktor gibt es auch beim Destruktor einen Standarddestruktor, der vom Compiler automatisch generiert wird. Ein Objekt kann auch dann zerstört werden, wenn kein Destruktor explizit implementiert wurde. Destruktoren können nicht überladen werden. 63 2 Objektorientiertes Programmieren L 6: Set-Property Erstelle ein Set-Property zum Tanken und erhöhe mit dem übergebenen Wert die Eigenschaft Kraftstoffvorrat. Lernen Üben Sichern Wissen Konstruktormethoden verwenden 1 Eine Konstruktormethode ist für die korrekte Erzeugung eines Objektes zuständig. Mit einer Konstruktormethode werden die für die Gültigkeit eines Objektes erforderlichen Eigenschaften, wie z.B. der Verbrauch und die Marke eines Autos, mit Werten initialisiert. Um ein Auto-Objekt erzeugen zu können, müssen diese Werte als Parameter der Konstruktormethode angegeben werden. Mr. What und Ms. Check Für die Klasse Auto sind folgende Fälle möglich: be 1. Das zu erzeugende Auto-Objekt ist ein Anhänger. Es gibt demnach keinen Verbrauch. Diese Aufgabe kann ein Konstruktor ohne Parameter lösen: Auto(). 2. Das Auto-Objekt ist ein Neuwagen. Hierzu benötigen wir eine Konstruktormethode, die den Verbrauch als Parameter erhält: Auto(verbrauch). 3. Das Auto-Objekt ist ein angemeldetes KFZ. Hierzu benötigen wir eine Konstruktormethode, die sowohl den Verbrauch als auch das amtliche Kennzeichen als Parameter erhält: Auto(verbrauch, kennzeichen). 3 Überladene Konstruktormethoden müssen anhand der Datentypen ihrer Parameter eindeutig unterscheidbar sein. Die drei oben beschriebenen Konstruktoren unterscheiden sich anhand ihrer Parameter. Der Standardkonstruktor Auto() enthält keinen Parameter. Er kann den Anhänger erzeugen, da dieser keinen Verbrauch benötigt. Die beiden überladenen Konstruktoren Auto(verbrauch) und Auto(verbrauch, kennzeichen) erhalten als Parameter den Verbrauch bzw. zusätzlich das Kennzeichen und können voneinander und vom Standardkonstruktor unterschieden werden. ro Die Konstruktoren public Auto(int km) und public Auto(double verbrauch) unterscheiden sich z.B. anhand der Datentypen ihrer Parameter. 2 Konstruktormethoden können überladen werden. sep Das Überladen ist nicht nur für Konstruktoren, sondern für alle Methoden möglich. Wofür wird ein Konstruktor benötigt? Die Konstruktormethode hat den gleichen Namen wie die Klasse. Konstruktormethoden dürfen keinen Rückgabewert haben. Le Woran erkenne ich eine Konstruktormethode? Ein Konstruktor ist eine Methode, die beim Erzeugen eines Objektes aufgerufen wird. Sie sorgt dafür, dass die Eigenschaften des Objektes sinnvolle Werte annehmen. L 7: Konstruktormethode Erstelle für die Klasse Auto einen Konstruktor, der die Parameter Kennzeichen, Marke, Tankinhalt und Verbrauch zwingend vorschreibt. Der Konstruktor hat keinen Rückgabedatentyp, void fehlt also. Bei gleichen Variablennamen für den Parameter des Konstruktors und die Eigenschaft wird mit this klargestellt, welche Variable gemeint ist. 64 // Konstruktor public Auto(string kennzeichen, string marke, double tankinhalt, double verbrauch) { this verweist auf die this.kennzeichen = kennzeichen; Eigenschaft this.marke = marke; this.tankinhalt = tankinhalt; if (verbrauch > 0) this.verbrauch = verbrauch; else throw new Exception("Der Verbrauch ist ungültig."); fahrtkosten = 0; } Softwareentwicklung Lerneinheit 1: Klassen und Objekte In einer Klasse darf es nur einen Destruktor geben. Überladen und Zugriffsmodifizierer, z.B. public, sind nicht erlaubt. Auf die Verwendung von Destruktormethoden können wir in der Praxis verzichten, wenn wir z.B. mit einer Cleanup-Methode, z.B. Dispose(), für die Freigabe der Ressourcen sorgen. be Statt eines Destruktors wird zum Freigeben von Ressourcen häufig die Methode Dispose() verwendet. // Destruktor ~Auto() { anzahlAutos--; } Andere Konstruktoren mit this aufrufen ro Ein wichtiges Prinzip objektorientierter Programmierung ist die Wiederverwendbarkeit von Programmcode. Ein überladener Konstruktor kann sich der Implementierung eines anderen Konstruktors mit Hilfe von : this(Parameter) bedienen. In den runden Klammern werden die Parameter an die aufzurufende Konstruktormethode übergeben. Um einen besseren Überblick zu erhalten, sehen wir uns nun die gesamte Klasse Auto zusammenfassend an: L 9: Konstruktormethoden mit this wiederverwenden Mit : this() wird zunächst der Standardkonstruktor aufgerufen. Erst danach wird der Codeblock des überladenen Konstruktors ausgeführt. Der Gebrauchtwagenkonstruktor verwendet den Neuwagenkonstruktor und ergänzt ihn danach. Softwareentwicklung sep Der Konstruktor sorgt für gültige Werte der Eigenschaften, wenn ein Objekt erzeugt wird. // Standardkonstruktor public Auto() { fahrtkosten = 0; } Le Aufgrund der Kapselung sind die Eigenschaften private. namespace cs_Frachtkostenoptimierung { class Auto { // Eigenschaften private string kennzeichen, marke; private double verbrauch, tankinhalt, kraftstoffvorrat, fahrtkosten; // Konstruktor für einen Neuwagen public Auto(string marke, double verbrauch) : this() // Standardkonstruktor aufrufen { this.kennzeichen = "Neu"; this.marke = marke; this.tankinhalt = 0; if (verbrauch > 0) this.verbrauch = verbrauch; else throw new Exception("Der Verbrauch ist ungültig."); } // Konstruktor für einen Gebrauchtwagen public Auto(string kennzeichen, string marke, double tankinhalt, double verbrauch) : this(marke, verbrauch) // Neuwagenkonstruktor aufrufen { this.kennzeichen = kennzeichen; this.tankinhalt = tankinhalt; } 65 2 Objektorientiertes Programmieren L 8: Destruktormethode Erstelle für die Klasse Auto einen Destruktor, der die globale Variable anzahlAutos um eins reduziert, wenn ein Auto-Objekt zerstört wird. Üben Die Methode Tanken erlaubt die Veränderung der Eigenschaft Kraftstoffvorrat und überprüft vor der Wertzuweisung deren Gültigkeit. Mit Hilfe der Properties kann auf die gekapselten Eigenschaften zugegriffen werden. Sichern Wissen // Methode Tanken public void Tanken(double liter) { if (kraftstoffvorrat + liter > tankinhalt) throw new Exception("So viel passt nicht in den Tank."); else kraftstoffvorrat += liter; } // Properties public double Kraftstoffvorrat { get { return kraftstoffvorrat; } set { if (kraftstoffvorrat + value > tankinhalt) throw new Exception("Der Tank ist dafür zu klein."); else kraftstoffvorrat += value; } } Le sep ro public double Fahrtkosten { get { return fahrtkosten; } } } } be Lernen Die Klasse Auto besteht aus Eigenschaften, einer Konstruktormethode, der Methode Tanken sowie den Properties Kraftstoffvorrat und Fahrtkosten. Nun sehen wir uns an, wie wir eine Klasse dazu verwenden, Objekte zu erzeugen. Instanzierung von Objekten Wir haben bereits den Unterschied zwischen einer Klasse und einem Objekt kennengelernt: Die Klasse Auto legt abstrakte Eigenschaften und Methoden fest, ein Objekt der Klasse Auto kann konkrete Werte für jede Eigenschaft beinhalten. Klasse Auto Diese Abbildung findest du in der PowerPointPräsentation unter der ID: 1179. Beachte 66 Instanz von kennzeichen marke verbrauch tankinhalt kraftstoffvorrat fahrtkosten Tanken() Kraftstoffvorrat Fahrtkosten Objekt Porsche "W4711A" "Porsche" 14.7 70 34.2 0 Tanken() Kraftstoffvorrat Fahrtkosten Abb.: Instanzierung eines Objektes von einer Klasse Das Objekt Porsche ist eine Instanz der Klasse Auto. Ein Objekt kennt die Klasse, von der es instanziert wurde. Wir können somit die folgende Aussage über ein Porsche-Objekt treffen: „Ein Porsche ist ein Auto.“ Softwareentwicklung Lerneinheit 1: Klassen und Objekte Natürlich ist das wenig überraschend. Entscheidend ist aber, dass jedes von einer Klasse instanzierte Objekt die Klasse kennt, von der es abgeleitet wurde. In der Lerneinheit 2 wird das bei der Nutzung der Vererbung von Bedeutung sein. Das folgende Lehrbeispiel zeigt, wie wir Objekte instanzieren und verwenden. 2 Objektorientiertes Programmieren ro Objekte bieten Methoden und Properties zur Verwendung an. // Formularkonstruktor public Form1() { InitializeComponent(); porsche.Tanken(70); golf.Tanken(10); golf.Kraftstoffvorrat = 5; } } be Objekte werden mittels eines Konstruktors von der Klasse instanziert. L 10: Objekte instanzieren public partial class Form1 : Form { Auto porsche = new Auto("W4711A", "Porsche", 70, 14.7); Auto golf = new Auto("S457GA", "VW", 50, 6.4); Die Objektvariablen porsche und golf werden als Klassenvariablen erzeugt. Beiden Objektvariablen wird ein neues Auto-Objekt über die Konstruktormethode der Klasse Auto zugewiesen. Le sep Im Formularkonstruktor wird für jedes Auto-Objekt die Methode Tanken aufgerufen. Alternativ kann auch das Set-Property Kraftstoffvorrat zum Tanken verwendet werden. Zur Berechnung der Fahrtkosten jedes Auto-Objektes benötigen wir neben dem Verbrauch und der Fahrstrecke den Kraftstoffpreis. Warum wir diesen nicht einfach als Eigenschaft der Klasse Auto erstellen können, sehen wir uns nun genauer an. 6 Statische Elemente Eigenschaften für mehrere Objekte Eigenschaften, wie z.B. der Benzin- bzw. Dieselpreis, sind für alle Auto-Objekte mit einem Benzin- bzw. Dieselmotor gleich. Eine Preisänderung der Kraftstoffe wirkt sich auf alle Autos aus. Hingegen sind der Verbrauch oder der Kraftstoffvorrat bei jedem Objekt individuell. Statische Eigenschaften gelten auf Klassenebene und sind für alle Objekte gleich. Sie können nur durch statische Methoden geändert werden. Auto Die Preisänderung erfolgt über eine statische Methode. Diese Abbildung findest du in der PowerPointPräsentation unter der ID: 1179. Softwareentwicklung kraftstoffvorrat fahrtkosten benzinpreis dieselpreis Tanken() Fahren() Kraftstoffvorrat Fahrtkosten Preisänderung() Abb.: Statische Elemente betreffen alle Objekte einer Klasse 67 Lernen Üben Sichern Wissen 1 Der Wert einer statischen Eigenschaft gilt für alle Objekte einer Klasse. Benzinpreis und Dieselpreis sind statische Eigenschaften. Deren Werte gelten für alle Objekte der Klasse Auto. Der Wert einer statischen Eigenschaft kann nur über die Klasse zugewiesen werden, nicht über das Objekt wie bei den Objekt-Eigenschaften. 3 Statische Eigenschaften können nur durch statische Methoden oder die Referenzierung über den Klassennamen verändert werden. Die statische Methode Preisänderung() ändert die statischen Eigenschaften Benzinpreis und Dieselpreis. Preisänderung() wird über die Klasse aufgerufen – nicht über das Objekt, wie eine nicht-statische Methode. 4 Statische Elemente erhalten das Schlüsselwort static. Um eine statische Eigenschaft oder eine statische Methode zu erstellen, wird das Schlüsselwort static bei der Deklaration verwendet, z.B. private static double benzinpreis oder public static void Preisänderung(double benzin, double diesel) { ... }. ro Das Verändern einer statischen Eigenschaft durch eine nichtstatische Methode ist durch die Angabe des Klassennamens möglich, z.B. public void ChangePreis() { Auto. benzinpreis = 1.15; } 2 Nicht-statische Methoden können statische Eigenschaften lesen, aber nicht verändern. Zum Ändern muss die statische Eigenschaft über die Klasse aufgerufen werden. Die nicht-statische Methode Tanken() kann den Benzin- bzw. Dieselpreis zwar lesen, aber nicht verändern. Dafür ist eine statische Methode oder die Angabe der Klasse erforderlich. be Wird eine statische Eigenschaft über die Klasse angesprochen, kann diese gelesen und geändert werden, auch von einem Objekt aus. sep 5 Wertzuweisungen an statische Eigenschaften erfolgen über einen statischen Konstruktor. Wertzuweisungen an Objekt-Eigenschaften erfolgen über einen Konstruktor, wenn das Objekt erzeugt wird. Da statische Eigenschaften selbst dann einen Wert erhalten könnten, wenn es keine Objekte gibt, muss die Wertzuweisung über die Klasse, also einen statischen Konstruktor, erfolgen. Das folgende Lehrbeispiel zeigt die Verwendung der statischen Elemente. L 11: Statische Elemente in einer Klasse Erstelle für die Klasse Auto die statischen Eigenschaften Benzinpreis und Dieselpreis mit einem statischen Konstruktor sowie eine Methode zur Änderung beider Kraftstoffpreise. Der statische Konstruktor weist den statischen Eigenschaften gültige Werte zu. Ein statischer Konstruktor kann nicht überladen werden. Die Wertänderung statischer Eigenschaften ist über die statische Methode Preisänderung möglich. Falls die Methode Preisänderung nicht statisch wäre, müssten die statischen Eigenschaften über die Klasse Auto angesprochen werden, z.B. Auto.benzinpreis. 68 Le // Statische Eigenschaften private static double benzinpreis, dieselpreis; // Statischer public static { benzinpreis dieselpreis } Konstruktor Auto() = 1.1; = 1.05; // Statische Methode als nicht-statische Methode: public static void Preisänderung (double benzin, double diesel) public void Preisänderung(..) { { .. if (benzin > 0 && diesel > 0) Auto.benzinpreis = benzin; { Auto.dieselpreis = diesel; benzinpreis = benzin; .. } dieselpreis = diesel; } else throw new Exception("Ungültiger Kraftstoffpreis."); } Softwareentwicklung Lerneinheit 1: Klassen und Objekte Wir können uns den Einsatz eines statischen Konstruktors ersparen, wenn wir die Wertzuweisung bei der Deklaration der statischen Eigenschaften vornehmen. Außerdem kann statt einer statischen Methode zur Preisänderung auch ein statisches Property verwendet werden. 2 Objektorientiertes Programmieren // Statische Eigenschaften mit Wertzuweisung private static double benzinpreis = 1.1; private static double dieselpreis = 1.05; be // Statische Set-Properties public static double Benzinpreis { set { if (value > 0) benzinpreis = value; else throw new Exception("Ungültiger Benzinpreis."); } } Mr. What und Ms. Check sep ro public static double Dieselpreis { set { if (value > 0) dieselpreis = value; else throw new Exception("Ungültiger Dieselpreis."); } } Wodurch unterscheiden sich statische von nicht-statischen Eigenschaften? Nein, aber eine nicht-statische Methode kann den Wert einer statischen Eigenschaft ansprechen. Le Kann eine statische Methode auf nicht-statische Eigenschaften zugreifen? Die Werte statischer Eigenschaften gelten auf Klassenebene und sind somit für alle Objekte lesbar, nicht-statische hingegen gelten nur für das jeweilige Objekt. Du hast nun alle erforderlichen Grundlagen für die Erstellung der Klasse Auto kennengelernt. Die folgenden Aufgaben helfen dir dabei, deine Kenntnisse anzuwenden und zu vertiefen. Üben Übungsbeispiele Softwareentwicklung Ü 4: Erweitere die Klasse Auto um die Eigenschaft Diesel vom Datentyp Bool. Berücksichtige dies auch bei den Konstruktoren, denn die Kraftstoffart muss zur Erzeugung von Auto-Objekten mittels True oder False angegeben werden. 69 Lernen Üben Sichern Wissen Ü 5: Erweitere die Klasse Auto um die Methode Fahren(km) und berücksichtige dabei folgende Anforderungen: 1. Der Parameter km hat den Datentyp Double. 2. Autos dürfen nur vorwärts fahren. 3. Autos dürfen nur fahren, wenn genug Kraftstoffvorrat für die Fahrstrecke vorhanden ist. 4. Vermindere den Kraftstoffvorrat um den Kraftstoffverbrauch für die zurückgelegte Fahrstrecke. Ü 6: Erweitere die Klasse Auto um die Eigenschaft Tankwert vom Datentyp Double. Diese Eigenschaft repräsentiert den Gesamtwert des Kraftstoffvorrats, der sich aktuell im Tank eines Autos befindet. be a) Erweitere die Methode Tanken so, dass Tankwert um das Produkt aus Liter und Kraftstoffpreis erhöht wird. Berücksichtige, ob das Auto einen Benzin- oder Dieselmotor hat. Dafür benötigst du die Eigenschaft Diesel aus Ü 4. b) Ergänze die Methode Fahren aus Ü 5 um die Erhöhung der Fahrtkosten. Die Formel dafür lautet Fahrtkosten + Tankwert / Kraftstoffvorrat * Verbrauch / 100 * Fahrstrecke. In diesem komplexen Übungsbeispiel verwendest du alle bisher gelernten Basiskonzepte der objektorientierten Programmierung. Ü 7: Pedro’s Pizzeria Erstelle eine neue C#-Projektdatei „cs_Pizzeria“ mit Visual Studio 2005 und darin die Klasse Pizza wie folgt: ro sep a) Jede Pizza hat die Eigenschaften Tischnummer vom Typ Integer, Zutaten vom Typ String sowie Pizzapreis vom Typ Double. b) Jede Pizza besteht mindestens aus den Zutaten „Teig“, „Tomatensauce“ und „Käse“ und hat einen Grundpreis von 4,50 EUR. Füge diese Zutaten im Standardkonstruktor als Text an die Eigenschaft Zutaten an und setze die Eigenschaft Pizzapreis auf den Grundpreis. c) Erstelle ein Set-Property für die Eigenschaft Zutaten, das diese um den als Wert übergebenen Text erweitert. Bereits bestellte Zutaten dürfen dadurch nicht überschrieben werden. Erhöhe die Zutatenanzahl um 1. d) Erstelle ein Get-Property für die Eigenschaft Zutaten. Le e) Erstelle einen überladenen Konstruktor ähnlich dem Standardkonstrukor, wobei zusätzlich als Parameter die Tischnummer übergeben wird. Im Restaurant gibt es 14 Tische. Weise die Tischnummer der Eigenschaft Tischnummer zu. f) Erstelle die statische Eigenschaft Zutatenpreis, die für jeden zusätzlich gewählten Pizzabelag einen einheitlichen Preis von 0,50 EUR festlegt. Das Set-Property für die Eigenschaft Zutaten soll den Pizzapreis um den Zutatenpreis erhöhen, wenn der Belag einer Pizza um eine zusätzliche Zutat ergänzt wird. g) Erstelle die Methode Preiserhöhung ohne Parameter, die den Zutatenpreis bei jedem Aufruf um 2 % erhöht. h) Erstelle ein Set-Property für die Eigenschaft Zutatenpreis. Zusätzlich zu diesen Übungen findest du in SbX eine Internetaufgabe. ID: 1180 70 Softwareentwicklung Lerneinheit 1: Klassen und Objekte Sichern Eine Klasse beschreibt allgemeine abstrakte Merkmale, die für alle der Klasse zugehörigen Objekte gültig sind. Eine Klasse beinhaltet Eigenschaften und Methoden. Objekt Ein Objekt ist eine Instanz einer Klasse. Es hat konkrete Ausprägungen in Form von Eigenschaften und Methoden. Ein Objekt erbt alle Eigenschaften und Methoden von einer Klasse. Vererbung Eine Klasse kann ein Abbild einer Basisklasse sein. Als Subklasse erbt sie alle Eigenschaften und Methoden der Basisklasse. Kapselung Eigenschaften einer Klasse werden als private Variablen festgelegt. Dadurch wird ein unkontrollierter Zugriff auf Eigenschaften verhindert. Nur Methoden erhalten Zugang zu den Eigenschaften ihrer Klasse. Durch Kapselung wird erreicht, dass eine Klasse bzw. ein Objekt möglichst eigenverantwortlich und losgelöst vom aufrufenden Programm agieren kann. Zugriffsmethode Ein Property ist eine Zugriffsmethode für eine Eigenschaft. Properties sind nötig, da Eigenschaften aufgrund der Kapselung nur innerhalb der Klasse verfügbar sind. Nach der Zugriffsberechtigung werden Get- und Set-Properties unterschieden. ro be Klasse Ein Get-Property darf Eigenschaften einer Klasse lesen. Set-Property Ein Set-Property darf Eigenschaften einer Klasse verändern. Konstruktor Eine Konstruktormethode wird bei der Erzeugung eines Objektes aufgerufen. Konstruktoren können mit Hilfe sich unterscheidender Datentypen bei den Parametern überladen werden. Eine Konstruktormethode legt die Art und Weise fest, unter welchen Bedingungen ein Objekt erzeugt werden darf. Destruktor sep Get-Property Die Destruktormethode einer Klasse wird bei der Zerstörung eines Objektes aufgerufen. Der Wert einer statischen Eigenschaft wird entweder direkt bei der Deklaration oder über die Klasse in Form eines statischen Konstruktors, einer statischen Methode oder eines statischen Propertys festgelegt. Objekte können statische Eigenschaften lesen, jedoch nicht verändern. Statische Methode Eine statische Methode dient der Änderung eines Wertes einer statischen Eigenschaft. Auch statische Properties können dafür verwendet werden. Le Statische Eigenschaft Zusätzlich zu dieser Zusammenfassung findest du in SbX eine Bildschirmpräsentation. ID: 1181 Wissen Wiederholungsfragen und -aufgaben 1.Erkläre den Unterschied zwischen Klasse und Objekt! 2.Welche Bestandteile hat eine Klasse? 3.Warum ist die Kapselung ein wichtiger Grundsatz objektorientierter Programmierung? Softwareentwicklung 71 2 Objektorientiertes Programmieren In dieser Lerneinheit haben wir die wichtigsten Bestandteile von Klassen und Objekten kennengelernt: Lernen Üben Sichern Wissen 4.Erkläre das Prinzip der Vererbung sowie die Begriffe Basis- und Subklasse! 5.Wofür werden Properties verwendet? 6.Erkläre die verschiedenen Erscheinungsformen von Properties! 7.Beschreibe mögliche Gefahren bei Missachtung des Grundsatzes der Kapselung! 8.Erkläre den Unterschied zwischen private und protected anhand einer Eigenschaft in einer Subklasse! 9.Erkläre den Unterschied zwischen einer statischen und einer nicht-statischen Eigenschaft! be 10.Kann eine nicht-statische Methode den Wert einer statischen Eigenschaft lesen bzw. verändern? 11.Kann eine statische Methode den Wert einer nicht-statischen Eigenschaft lesen bzw. verändern? Zusätzlich zu diesen Aufgaben findest du in SbX ein Quiz. Lerncheck Ich kann jetzt … ro ID: 1182 w ... den Unterschied und die Verwendung von Klassen und Objekten erklären. sep w ... Eigenschaften und Methoden als wichtige Bestandteile von Klassen und Objekten beschreiben. w ... Kapselung und Vererbung als Grundprinzipien objektorientierter Programmierung nennen. w ... die Verwendung von Properties im Zusammenhang mit Eigenschaften einer Klasse erklären. w ... Konstruktoren überladen und zur Erzeugung von Objekten verwenden. w ... statische Eigenschaften und statische Methoden richtig einsetzen. Le In der nächsten Lerneinheit beschäftigen wir uns mit dem Einsatz der Vererbung von Klassen sowie dem Überschreiben von virtuellen Methoden und der Anwendung der Polymorphie. 72 Softwareentwicklung Lerneinheit 2: Vererbung Lerneinheit 2 Vererbung In dieser Lerneinheit beschäftigen wir uns mit den Konzepten der Vererbung und der Polymorphie sowie dem Einsatz von Interfaces. Wir erweitern das bereits in der Lerneinheit 1 begonnene Lehrbeispiel zur Frachtkostenoptimierung um 2 Objektorientiertes Programmieren Alle SbX-Inhalte zu dieser Lerneinheit findest du unter der ID: 1183. be ● die Vererbung von Klassen und Konstruktoren, ● das Überschreiben virtueller Methoden (Polymorphie), ● die Verwendung abstrakter und versiegelter Klassen sowie ● die Verwendung der Mehrfachvererbung mit Hilfe von Interfaces. ro Lernen 1 Subklassen Klassen vererben L 1: Der Fuhrpark eines Unternehmens besteht aus LKWs und PKWs. Für beide Fahrzeugkategorien sollen unterschiedliche Klassen erstellt werden, da PKWs und LKWs unterschiedliche Eigenschaften haben. Für einen LKW muss zur Berechnung der Autobahnmaut die Achszahl bekannt sein, PKWs benötigen eine Vignette. Eine einfache Lösung besteht darin, eine gemeinsame Basisklasse Auto zu erstellen und die beiden Subklassen PKW und LKW davon abzuleiten. Le Das komplexe Fallbeispiel Fuhrpark findest du unter der ID: 1184. sep Wir haben in der ersten Lerneinheit anhand der Klasse Auto die Bestandteile einer Klasse besprochen. Diese Klasse dient als Basis für die weitere Entwicklung unseres Fallbeispieles für die Frachtkostenoptimierung eines Fuhrparks. Auto Die Basisklasse Auto enthält allgemein gültige Eigenschaften und Methoden. marke verbrauch ... Tanken() Fahren() PKW Diese Abbildung findest du in der PowerPointPräsentation unter der ID: 1184. Softwareentwicklung vignette LKW achszahl Die Subklassen PKW und LKW erweitern oder verändern die Eigenschaften und Methoden der Basisklasse. Abb.: Vererbung von Basisklassen an Subklassen 73 Lernen Üben Sichern Wissen Wiederverwendbarkeit durch Vererbung Die Abbildung zeigt, dass sich PKWs von LKWs nur durch die Eigenschaften Vignette und Achszahl unterscheiden. Alle anderen Bestandteile sind gleich. Ein wichtiges Prinzip beim objektorientierten Programmieren ist die Wiederverwendbarkeit von Programmcode. Wir erstellen daher die Klasse Auto mit den gemeinsamen Merkmalen beider Subklassen. Die speziellen Eigenschaften Vignette und Achszahl werden in den Subklassen ergänzt. Eine spätere Erweiterung, z.B. das Hinzufügen der Frachtkapazität, wird in der Basisklasse vorgenommen. Die Frachtkapazität steht durch die Vererbung in allen Subklassen zur Verfügung. Vererbung verwenden be 1 Eine Subklasse greift auf die Elemente ihrer Basisklasse zu. Die Subklassen PKW und LKW kennen die Eigenschaften und Methoden der Basisklasse Auto. Alle Elemente der Basisklasse werden an die Subklassen vererbt – sofern der Zugriffsmodifizierer dies erlaubt. 2 Der Zugriffsmodifizierer legt fest, ob auf eine Eigenschaft einer Klasse zugegriffen werden darf. ro Der Zugriffsmodifizierer private legt den Zugriff auf die eigene Klasse fest. Aus einer Subklasse ist der Zugriff nicht möglich. Private Eigenschaften werden nicht vererbt! Der Zugriffsmodifizierer protected erlaubt den Zugriff für die eigene Klasse und für alle von dieser Klasse abgeleiteten Subklassen. Sollen die Eigenschaften der Basisklasse an die Subklassen vererbt werden, müssen wir protected verwenden. Der Zugriffsmodifizierer public erlaubt den Zugriff für alle Klassen. sep 3 Von einer abstrakten Klasse dürfen keine Objekte instanziert werden. Wird für eine Klasse das Schlüsselwort abstract benutzt, muss es mindestens eine Subklasse geben, wenn Objekte erzeugt werden sollen. Eine abstrakte Klasse verbietet das Instanzieren von Objekten. 4 Von einer versiegelten Klasse können keine Subklassen abgeleitet werden. Le Wenn von einer Klasse keine weiteren Subklassen mehr abgeleitet werden dürfen, muss die Klasse mit dem Schlüsselwort sealed versiegelt werden. Auto marke verbrauch ... protected Tanken() Fahren() Eigenschaften werden vererbt PKW Diese Abbildung findest du in der PowerPointPräsentation unter der ID: 1184. 74 LKW marke verbrauch ... vignette marke verbrauch ... achszahl Tanken() Fahren() Tanken() Fahren() zusätzliche Eigenschaften Abb.: Eigenschaften und Methoden in Subklassen Softwareentwicklung Lerneinheit 2: Vererbung Der Zugriffsmodifizierer protected kapselt die Eigenschaften innerhalb der Klasse Auto und ihrer Subklassen PKW und LKW. Der Zugriff für andere Klassen ist nicht zulässig. Die Methoden Tanken() und Fahren() sind hingegen weiterhin öffentlich (public) und damit auch für andere Klassen verwendbar. Eine Subklasse erbt alle nicht privaten Eigenschaften, Methoden und Zugriffsmethoden von ihrer Basisklasse. Konstruktormethoden werden nicht vererbt! 2 Objektorientiertes Programmieren Beachte // Basisklasse Auto abstract class Auto { // Eigenschaften protected string marke; protected double verbrauch, tankinhalt, fahrtkosten, kraftstoffvorrat; ro Da nur von den Subklassen PKW und LKW Objekte erzeugt werden dürfen, ist Auto eine abstrakte Klasse. be L 2: Vererbung anwenden Erstelle die Basisklasse Auto sowie die beiden Subklassen PKW und LKW gemäß der Abbildung auf Seite 74. // Methoden public void Tanken() { kraftstoffvorrat = tankinhalt; } Die Vererbung erfolgt mit einem Doppelpunkt und der Angabe der Basisklasse. public void Fahren() { // do something } } sep Die Implementierung der Methode Fahren() erfolgt später. // Subklasse für PKWs class Pkw : Auto { private bool vignette; } Mr. What und Ms. Check Le // Subklasse für LKWs class Lkw : Auto { private byte achszahl; } Wie kann ich ein Objekt von einer Subklasse instanzieren, wenn die Konstruktormethoden nicht vererbt werden? Können statische Eigenschaften und Methoden auch vererbt werden? Jede Klasse hat einen Standardkonstruktor, wenn du keine eigenen Konstruktoren implementierst. Das gilt auch für Subklassen – aber nur, wenn es in der Basisklasse auch einen Standardkonstruktor gibt! Ja, alle statischen und nicht-statischen Elemente werden vererbt. Die Konstruktoren werden nicht vererbt. Wie wir bereits erfahren haben, werden Konstruktormethoden nicht vererbt. Sehen wir uns an, wie wir dennoch die vorhandenen Konstruktormethoden einer Basisklasse verwenden können. Softwareentwicklung 75 Lernen Üben Sichern Wissen 2 Konstruktoren in Subklassen Konstruktormethoden wiederverwenden Das Lehrbeispiel L 2 funktioniert, da wir keine speziellen Konstruktormethoden verwendet haben. Aus allen Klassen können Objekte instanziert werden. Was passiert aber, wenn in einer Basisklasse überladene Konstruktormethoden vorhanden sind? Sehen wir uns dazu das folgende Lehrbeispiel an! L 3: Vererbung und Konstruktoren Die Klasse Auto aus dem Lehrbeispiel L 2 soll um eine Konstruktormethode mit den Parametern Marke und Kraftstoffvorrat erweitert werden. sep ro be // Basisklasse Auto class Auto { // Eigenschaften protected string marke; protected double verbrauch, tankinhalt, fahrtkosten, kraftstoffvorrat; // Konstruktor public Auto(string marke, double verbrauch, double tank) { this.marke = marke; this.verbrauch = verbrauch; this.tankinhalt = tank; this.kraftstoffvorrat = 0; this.fahrtkosten = 0; } // Methoden public void Tanken() { kraftstoffvorrat = tankinhalt; } public void Fahren() { // do something } } Fehler! –> Le // Subklasse PKW class Pkw : Auto { private bool vignette; } // Objekt instanzieren Pkw audi = new Pkw(); Diese Konstruktormethode fehlt in der Basisklasse. Das Objekt audi wird bottom-up erzeugt, d.h. es werden der Reihe nach alle Konstruktoren der Subklassen bis hin zur Basisklasse aufgerufen. Wenn es in einer Klasse keine überladenen Konstruktormethoden gibt, wird vom Compiler automatisch ein Standardkonstruktor erzeugt. Das gilt auch für die Subklasse PKW. Da das Audi-Objekt bottom-up erzeugt wird, verlangt der Standardkonstruktor in der Subklasse PKW auch nach einem Standardkonstruktor in der Basisklasse Auto. Doch diesen gibt es nicht und wir erhalten in der Folge einen Compilierfehler. Beachte 76 In einer Basisklasse muss es Konstruktormethoden geben, deren sich die SubklassenKonstruktormethoden bedienen können. Softwareentwicklung Lerneinheit 2: Vererbung L 4: Basisklassen-Konstruktor verwenden Die Subklasse PKW aus dem Lehrbeispiel L 3 soll so verändert werden, dass PKWObjekte instanziert werden können. be public Pkw(string marke, double verbrauch, double tank) : base(marke, verbrauch, tank) { this.vignette = false; } public Pkw(string marke, double verbrauch, double tank, bool vignette) : base(marke, verbrauch, tank) { this.vignette = vignette; } } ro Beide Subklassenkonstruktoren rufen denselben Basisklassenkonstruktor auf. 2 Objektorientiertes Programmieren // Subklasse PKW class Pkw : Auto { private bool vignette; Konstruktoren wiederverwenden 1 Konstruktormethoden werden nicht vererbt und müssen daher in der Subklasse neu deklariert werden. sep Die Konstruktormethode Auto(marke, kraftstoffvorrat) muss auch in der Subklasse PKW implementiert werden. Jede Klasse, egal ob Basisklasse oder Subklasse, muss alle benötigten Konstruktormethoden implementieren. 2 Mit base wird die Basisklasse angesprochen. Mit Hilfe von base ruft die Subklassen-Konstruktormethode PKW(marke, kraftstoffvorrat) die Basisklassen-Konstruktormethode Auto(marke, kraftstoffvorrat) auf. Alle dort implementierten Anweisungen werden zunächst ausgeführt, danach die im Subklassenkonstruktor zusätzlich festgelegten. Le 3 Eine Subklasse kann zusätzliche Konstruktormethoden implementieren, die sich eines gemeinsamen Basisklassenkonstruktors bedienen. Die überladene Subklassen-Konstruktormethode PKW(marke, kraftstoffvorrat, vignette) ruft denselben Basisklassenkonstruktor auf, wie der Konstruktor PKW(marke, kraftstoffvorrat). Ü 1: Ergänze zum Lehrbeispiel L 4 die Subklasse Lkw mit dem Konstruktor Lkw(marke, verbrauch, tankinhalt, achszahl). Ein LKW ohne Angabe der Achszahl darf nicht instanziert werden. Mr. What und Ms. Check Softwareentwicklung Kann ich mit base auch Methoden, wie z.B. Fahren(), aus der Basisklasse aufrufen? Ja, das ist z.B. sinnvoll, wenn du die Methode der Basisklasse um zusätzliche Anweisungen erweitern möchtest. 77 Lernen Üben Sichern Wissen 3 Methoden überschreiben Unterschiedliches Verhalten von Objekten Sehen wir uns die beiden Methoden der Basisklasse Auto genauer an. Die Methode Tanken() kann für PKWs und LKWs in der gleichen Art und Weise eingesetzt werden. Sie erhöht den Kraftstoffvorrat um die getankte Menge und sorgt dafür, dass der Tank nicht überfüllt wird. Für Autobahnfahrten benötigt ein PKW eine Vignette, ein LKW bezahlt hingegen einen von der Achszahl abhängigen Mautsatz pro Autobahnkilometer. In der Basisklasse wird die folgende Methode Fahren() hinzugefügt: be sep Da die Basisklasse für PKWs und LKWs gilt, können nur die allgemeinen Fahrtkosten berechnet werden, die zusätzlichen Fahrtkosten für Autobahnfahrten jedoch nicht. ro Hinweis: Wir gehen vereinfacht davon aus, dass Benzin und Diesel gleich viel kosten. // Fahren-Methode der Basisklasse Auto public void Fahren(int km, bool autobahn) { // Kraftstoffpreis double preis = 1.15; if (km <= 0) throw new Exception("Das Auto darf nicht rückwärts fahren!"); else { if (kraftstoffvorrat < km * verbrauch / 100) throw new Exception("Zu wenig Kraftstoff im Tank!"); else { kraftstoffvorrat –= km * verbrauch / 100; fahrtkosten += km * verbrauch / 100 * preis; } } } Die Fahren-Methode der Basisklasse aktualisiert den Kraftstoffvorrat und erhöht die Fahrtkosten um die Kosten für den verbrauchten Treibstoff. Da ein PKW für eine Autobahnfahrt eine Vignette haben muss und ein LKW pro Kilometer bezahlt, können diese Kosten in der Basisklasse noch nicht berechnet werden. Diese Ergänzung müssen wir in den Subklassen vornehmen: Ein PKW benötigt für Autobahnfahrten eine Vignette, die Fahrtkosten erhöhen sich um deren Anschaffungspreis. Ein LKW bezahlt einen Mautsatz pro Kilometer, der von seiner Achszahl abhängig ist. 78 Le Aufruf der FahrenMethode in der Basisklasse. // Fahren-Methode der Subklasse PKW public void Fahren(int km, bool autobahn) { base.Fahren(km, autobahn); if (autobahn == true && vignette == false) { fahrtkosten += vignettenpreis; // Vignette kaufen vignette = true; // Vignette aufkleben } } // Fahren-Methode der Subklasse LKW public void Fahren(int km, bool autobahn) { base.Fahren(km, autobahn); if (autobahn == true) { switch (achszahl) { case 2: fahrtkosten += km * 0.13; break; case 3: fahrtkosten += km * 0.182; break; default: fahrtkosten += km * 0.273; break; } } } Softwareentwicklung Lerneinheit 2: Vererbung Die Fahren-Methoden der Subklassen überschreiben die Fahren-Methode der Basisklasse. Wir können PKW- und LKW-Objekte erzeugen, die bei Autobahnfahrten ein unterschiedliches Verhalten haben: L 5: Objekte aus Subklassen instanzieren Wir erzeugen ein PKW- und ein LKW-Objekt und lassen beide Objekte 200 km auf der Autobahn fahren. 2 Objektorientiertes Programmieren // Ausgabe der Fahrtkosten beider Objekte MessageBox.Show("Fahrtkosten von " + auto1.Marke + ": " + Convert.ToString(auto1.Fahrtkosten)); MessageBox.Show("Fahrtkosten von " + auto2.Marke + ": " + Convert.ToString(auto2.Fahrtkosten)); Kann ich die Autos des Fuhrparks auch in einem Array verwalten? Ein Array verlangt nach der Angabe eines Datentyps, aber wir haben zwei – PKW und LKW. Du kannst trotzdem ein Array erstellen, wenn du dafür als Datentyp Auto verwendest. sep Mr. What und Ms. Check // 200 km Autobahnfahrt (true) be Hinweis: Marke und Fahrtkosten müssen als Properties in der Basisklasse implementiert werden. // Objekte fahren auto1.Fahren(200, true); auto2.Fahren(200, true); ro Zur Objektinstanzierung werden die Subklassen-Konstruktormethoden aufgerufen. // Formularklasse: Klassenvariablen und Objekte instanzieren // Subklassen-Konstruktoraufruf Pkw auto1 = new Pkw("Mercedes", 7.5, 70, true); // mit Vignette Lkw auto2 = new Lkw("Actros", 15.3, 250, 5); // mit fünf Achsen 4 Polymorphie Spätes Binden von Objekten Beachte Le Im Lehrbeispiel L 5 wurde deutlich, dass für die Verwaltung mehrerer PKWs und LKWs in einem Fuhrpark ein Array sinnvoll eingesetzt werden kann. Allerdings stoßen wir hier auf ein Problem, denn bei der Deklaration des Arrays müssen wir einen Datentyp angeben – aber welchen? Jedes Objekt einer Subklasse lässt sich in den Typ der Basisklasse casten. L 6: Objekte in den Basisklassentyp casten Für die Verwaltung des Fuhrparks wird ein Array vom Typ der Basisklasse Auto verwendet. Jedes PKW- und LKWObjekt erscheint im Array als Auto. Die Subklassen PKW und LKW bieten verschiedene Konstruktormethoden an. // Fuhrpark-Array mit PKW- und LKW-Objekten Auto[] fuhrpark = { new Pkw("Mercedes", 7.5, 70, true), Die PKW- und LKW-Objekte new Lkw("Actros", 15.3, 250, 5), werden implizit in den Typ new Pkw("VW Sprinter", 9.2, 80) Auto gecastet. }; Das Fuhrpark-Array enthält PKWs und LKWs, die nach außen als Autos erscheinen. Dennoch weiß jedes Objekt, dass es entweder ein PKW oder ein LKW ist. Softwareentwicklung 79 Lernen Üben Sichern Wissen Damit das Fuhrpark-Array mit den Objekten aus dem Lehrbeispiel L 6 richtig funktioniert, nehmen wir an den Fahren-Methoden der Basis- und Subklassen eine kleine Ergänzung vor. e e. ein d st etho i en M hr elle a F tu vir Auto marke kraftstoffvorrat fahrtkosten Tanken() virtual Fahren() ird n w ben. e r h Fa schrie r übe vignette achszahl override Fahren() override Fahren() Abb.: Polymorphie durch spätes Binden ro Diese Abbildung findest du in der PowerPointPräsentation unter der ID: 1184. ? LKW be PKW ? Virtuelle Methoden überschreiben 1 Wenn eine Methode der Basisklasse in den Subklassen überschrieben werden soll, wird die Basisklassenmethode als virtuelle Methode gekennzeichnet. Die Fahren-Methode wird in der Basisklasse Auto mit dem Schlüsselwort virtual versehen. sep 2 Die virtuelle Basisklassenmethode muss in jeder Subklasse als überschreibende Methode neu implementiert werden. Die Fahren-Methoden in den Subklassen PKW und LKW müssen mit dem Schlüsselwort override versehen werden, da die virtuelle Basisklassenmethode dies zwingend vorschreibt. Late Binding = spätes Binden 3 Der Aufruf der virtuellen Basisklassenmethode bewirkt den automatischen Aufruf der überschreibenden Subklassenmethode. Welche Subklassenmethode aufgerufen wird, bestimmt das Objekt. Dies wird als spätes Binden bezeichnet. Le Im Fuhrpark-Array erscheint jedes Objekt als Auto, nicht als PKW oder LKW. Daher ruft die Methode Fahren() zunächst die virtuelle Methode der Klasse Auto auf. Je nachdem, welches Objekt angesprochen wird, PKW oder LKW, wird die entsprechende FahrenMethode der Subklasse ausgeführt. Das folgende Lehrbeispiel veranschaulicht, wie mit Hilfe einer Schleife alle Objekte des Fuhrparks 200 km auf der Autobahn fahren. Die Fahrtkosten werden dabei für PKWs und LKWs durch das späte Binden unterschiedlich berechnet. L 7: Polymorphie Die virtuelle Methode Fahren() wird in den Subklassen überschrieben. Das Fallbeispiel Fuhrpark_Polymorphie findest du unter der ID: 1184. Die Fahren-Implementierung der Basisklasse wird mit base aufgerufen. 80 // Fahren-Methode in der Klasse Auto public virtual void Fahren(int km, bool autobahn) { // do something } // Fahren-Methode in den Subklassen PKW und LKW public override void Fahren(int km, bool autobahn) { base.Fahren(km, autobahn); // do something else } Softwareentwicklung Lerneinheit 2: Vererbung Die polymorphe Methode Fahren() funktioniert mit unterschiedlichen Objekten – egal ob PKW oder LKW, und ruft die entsprechenden überschriebenen Methoden der Klassen PKW und LKW auf. be Kann ich statt eines Arrays auch eine ArrayList für den Fuhrpark verwenden? Ja, eine ArrayList-Collection ist sogar eine noch bessere Lösung, da du Objekte jederzeit hinzufügen und entfernen kannst. sep Mr. What und Ms. Check foreach(Auto auto in fuhrpark) { auto.Fahren(200, true); // 200 km Autobahnfahrt (true) } ro Die foreach-Schleife iteriert durch das Array fuhrpark und repräsentiert in jedem Schleifendurchlauf ein anderes Objekt in der Variable auto. Auto[] fuhrpark = { new Pkw("Mercedes", 7.5, 70, true), new Lkw("Actros", 15.3, 250, 5), new Pkw("VW Sprinter", 9.2, 80) }; Ü 2: Verändere das Lehrbeispiel L 8 so, dass statt einem Array eine ArrayList für den Fuhrpark verwendet wird. Hinweis: Du musst dafür den Collection-Namensraum einbinden. 5 Abstrakte und versiegelte Klassen Instanzen und Vererbung verbieten Le Um die Klassen PKW und LKW besser abzusichern, sollten wir das Erzeugen von Objekten aus der Klasse Auto verbieten. Nur Instanzen von PKW und LKW dürfen möglich sein, wenn wir die Polymorphie nutzen wollen. Die Klasse Auto ist eine abstrakte Klasse. 1 Das Instanzieren von Objekten aus einer abstrakten Klasse ist nicht zulässig. Wenn wir die Klasse Auto als abstrakte Klasse festlegen, dürfen nur noch PKW- und LKW-Objekte erzeugt werden. Das Schlüsselwort abstract definiert eine abstrakte Klasse. abstract class Auto { } Auto auto; auto = new Auto(); –> Konstruktoraufruf ist nicht mehr zulässig! Auto auto ist zulässig, da hier kein Objekt instanziert, sondern lediglich eine Objektvariable deklariert wird. Der Konstruktoraufruf von Auto ist nicht erlaubt. 2 Eine versiegelte Klasse erlaubt keine weitere Vererbung mehr. Wird die Klasse PKW versiegelt, darf keine Subklasse von PKW, wie z.B. Sportwagen, abgeleitet werden. Zum Versiegeln wird das Schlüsselwort sealed verwendet. sealed class Pkw { } class Sportwagen : Pkw { } –> Vererbung ist nicht mehr zulässig! Die Vererbung ist nicht zulässig, da die Klasse PKW versiegelt ist. Softwareentwicklung 81 2 Objektorientiertes Programmieren L 8: Polymorphie mit Objekten Alle Objekte des Fuhrparks fahren 200 km auf der Autobahn. Lernen Üben Mr. What und Ms. Check Sichern Wissen Warum sollte ich abstrakte und versiegelte Klassen einsetzen? Klassen sollten möglichst sicher und gegen Fehler resistent sein. Mit abstrakten und versiegelten Klassen schränkst du mögliche Fehlerquellen ein. Wie wir bereits wissen, verbietet eine abstrakte Klasse das Instanzieren von Objekten. Wir haben uns das bereits anhand des Beispiels der Basisklasse Auto und der Subklassen PKW und LKW angesehen. Da es nur PKW- oder LKW-Objekte geben kann, wäre das Instanzieren von AutoObjekten sinnlos. be Abstrakte Klassen können noch einen weiteren Zweck erfüllen: das Vorschreiben von abstrakten Methoden, die in einer Subklassen zwingend zu implementieren sind. Abstrakte Methoden Beachte ro Das Schlüsselwort abstract ist nicht nur für Klassen, sondern auch für Methoden und Properties zulässig. Abstrakte Methoden und abstrakte Properties haben keine Implementierung, sind nur in abstrakten Klassen erlaubt und dürfen nicht private sein. sep L 9: Abstrakte Methoden vererben Die Klasse Fahrzeug schreibt für alle Subklassen die Methode Transportieren vor, ohne sie selbst zu implementieren. // abstrakte Basisklasse Fahrzeug abstract class Fahrzeug { protected double kostenProKm; public abstract void Transportieren(int km); } In einer nicht abstrakten Klasse müssen alle geerbten abstrakten Methoden mit Hilfe von override implementiert werden. Abstrakte Methode // Subklasse Auto class Auto : Fahrzeug { public override void Transportieren(int km) { // do something } } Erbt eine abstrakte Klasse abstrakte Methoden, müssen diese nicht implementiert werden. // abstrakte Subklasse Zweirad abstract class Zweirad : Fahrzeug { // erbt die abstrakte Methode Transportieren(int km) } Die Klasse Fahrrad erbt die abstrakte Methode durch die Vererbungshierarchie und muss sie implementieren. 82 Le Eine abstrakte Methode muss sich in einer abstrakten Klasse befinden. // Subklasse Fahrrad class Fahrrad : Zweirad { public override void Transportieren(int km) { // do something } } Softwareentwicklung Lerneinheit 2: Vererbung Wofür setze ich abstrakte Methoden ein? Eine abstrakte Methode schreibt einer Subklasse die Implementierung der Methode vor. Eine nicht abstrakte Subklasse muss alle geerbten abstrakten Methoden implementieren. Im Gegensatz zu C++ unterstützt C# keine direkte Mehrfachvererbung. Eine Subklasse hat genau eine Basisklasse. Interfaces stellen aber eine Alternative zur Mehrfachvererbung dar. 6 Interfaces be Vorschriften für Klassen ro Wir haben erfahren, dass wir mit Hilfe von Vererbung viel Zeit und Arbeit einsparen können, indem wir Klassen in Subklassen erweitern bzw. verändern. Um die Notwendigkeit von Mehrfachvererbung zu verstehen, erweitern wir unser Fallbeispiel um die Klasse Bahn. Das Programm soll berechnen, mit welchem Transportmittel die günstigsten Fahrtkosten erzielt werden. Wir erstellen zunächst die Klasse Bahn in gewohnter Art und Weise. L 10: Klasse Bahn Die Klasse Bahn beinhaltet die Eigenschaften preisProKm und transportkosten sowie die Methode Transportieren. // Klasse Bahn class Bahn { private double transportkosten; private double kostenProKm = 0.35; sep Das Fallbeispiel Fuhrpark_Interface findest du unter der ID: 1184. Le public void Transportieren(int km) { transportkosten += km * kostenProKm; } } Wenn wir nun das Fuhrpark-Array im Hauptprogramm um ein Bahn-Objekt erweitern, stoßen wir unweigerlich auf ein Problem: Das Bahn-Objekt hat mit der Klasse Auto nichts zu tun und lässt sich daher auch nicht in den Typ Auto casten! Die Lösung unseres Problems ist das Interface IFahrzeug, das wir in alle Klassen vererben, die wir zur Transportkostenberechnung verwenden, z.B. die Klassen Bahn, PKW und LKW. L 11: Interface Das Interface IFahrzeug implementiert die Methode Transportieren sowie die Properties Kosten und Bezeichnung. Das Fallbeispiel Fuhrpark_Interface findest du unter der ID: 1184. Ein Interface enthält ausschließlich abstrakte Methoden und Properties. Softwareentwicklung // Interface IFahrzeug interface IFahrzeug { // Methode Transportieren void Transportieren(int km); // Properties double Kosten { get; } string Bezeichnung { get; } } Abstrakte Methode Abstrakte Properties 83 2 Objektorientiertes Programmieren Mr. What und Ms. Check Lernen Üben Sichern Wissen IFahrzeug Auto marke kraftstoffvorrat fahrtkosten Transportieren() Tanken() Fahren() PKW Bahn Diese Abbildung findest du in der PowerPointPräsentation unter der ID: 1184. Transportieren() vignette achszahl Fahren() Transportieren() Fahren() Transportieren() be transportkosten kostenProKm LKW Abb.: Interface zur Mehrfachvererbung ro Interfaces verwenden 1 Ein Interface legt Methoden und Properties ohne Implementierung fest. Die Methode Transportieren() und die Get-Properties im Interface IFahrzeug aus dem Lehrbeispiel L 10 enthalten keine Implementierungen – sie sind abstrakt. 2 Eine Klasse darf aus maximal einer Basisklasse und zusätzlich aus mehreren Interfaces abgeleitet werden. sep Die Klasse Bahn erbt z.B. nur vom Interface IFahrzeug. Die Klassen PKW und LKW erben von der Klasse Auto und vom Inferface IFahrzeug. 3 Wenn eine Klasse von einem Interface erbt, muss sie alle Methoden und Properties aus dem Interface implementieren oder selbst abstrakt sein. Beachte Le Die Klassen Bahn, PKW und LKW müssen die im Interface IFahrzeug festgelegte Methode Transportieren() implementieren, falls nicht, müssten sie abstrakte Klassen sein. Ein Objekt lässt sich in den Typ eines Interfaces casten, wenn es von einer Klasse instanziert wird, die das Interface implementiert. Für das Array, das alle Transportmittelobjekte enthält, verwenden wir als Datentyp statt einer Klasse nun das Interface IFahrzeug. Da alle Objekte des Arrays vom Interface ableiten, können wir jedes Objekt in den Typ des Interfaces casten. IFahrzeug Transportieren() PKW Das Array ist vom Typ des Interfaces IFahrzeug und enthält PKW-, LKWund Bahn-Objekte. LKW Bahn Diese Abbildung findest du in der PowerPointPräsentation unter der ID: 1184. 84 Array Abb.: Interface verwenden Softwareentwicklung Lerneinheit 2: Vererbung L 12: Array vom Typ eines Interfaces Das Array mit den Transportmitteln ist vom Typ des Interfaces IFahrzeug. IFahrzeug[] transportmittel = { new Pkw("Mercedes", 7.5, 70, true), new Lkw("Actros", 15.3, 250, 5), new Pkw("VW Sprinter", 9.2, 80), new Bahn() }; 2 Objektorientiertes Programmieren Das Fallbeispiel Fuhrpark_Interface findest du unter der ID: 1184. ID: 1184 Was bedeutet gegen eine Klasse bzw. gegen ein Interface programmieren? Wenn du als Datentyp eine Klasse verwendest, programmierst du gegen eine Klasse. Gegen ein Interface programmierst du, wenn du ein Interface als Datentyp benutzt. Mit der zweiten Methode bist du wesentlich flexibler, wenn du mehrere verschiedene Klassen verwendest, die nicht vererbbar sind. In SbX findest du das Lehrbeispiel Fuhrpark unter Verwendung von Vererbung, Polymorphie und Interfaces. Le Üben Übungsbeispiele ro Mr. What und Ms. Check Damit dieses Beispiel funktioniert, müssen auch die Klassen PKW und LKW die Methode Transportieren() und die beiden Properties des Interfaces IFahrzeug implementieren. sep Beachte be foreach(IFahrzeug fahrzeug in transportmittel) { fahrzeug.Transportieren(200); } Ü 3: Erstelle die Basisklasse Flugzeug mit den Eigenschaften Flugnummer, Reiseziel, Sitzplatzanzahl und der Anzahl der Reservierungen. Die Parameter für den Konstruktor sind Flugnummer, Reiseziel und Sitzplatzanzahl. Ein Standardkonstruktor ist nicht zulässig. Erstelle die Methode Reservieren(), die die Anzahl der Reservierungen erhöht. Die Methode darf keine Überbuchung eines Flugzeuges zulassen. a) Erstelle die Subklassen Hubschrauber und Transportflugzeug. b) Die Klasse Transportflugzeug erhält zusätzlich die Eigenschaft Transportgewicht. c) Erstelle die Konstruktormethoden für die Subklassen. Das Transportgewicht ist bei einem Transportflugzeug verpflichtend anzugeben. Ü 4: Erstelle zur nachfolgend abgebildeten Klasse Artikel die Subklassen Speise und Getränk. a) Die Subklassen erben alle Eigenschaften der Basisklasse. b) Ändere die Methode Verkaufen() in der Basisklasse in eine virtuelle Methode und berücksichtige das in den Subklassen. Softwareentwicklung 85 Lernen Üben Sichern Wissen c) Die Methode Verkaufen() in den Subklassen berechnet den Nettoumsatz. Für Speisen beträgt der Umsatzsteuersatz 10 %, für Getränke 20 %. d) Im Hauptprogramm sollen die Speisen und die Getränke in einem Array abgelegt werden. Erstelle ein Array für eine Pizza Diabolo und ein Coca Cola. Wähle die Preise selbst aus. e) Erstelle das Interface IArtikel mit der Methode Verkaufen() und den Get-Properties Bezeichnung und NettoUmsatz. Vererbe das Interface in die Klasse Artikel. be class Artikel { // Eigenschaften private string bezeichnung; private double bruttoPreis, nettoUmsatz; private static byte[] ust = { 10, 20 }; ro // Konstruktor public Artikel(string bezeichnung, double bruttoPreis) { this.bezeichnung = bezeichnung; this.bruttoPreis = bruttoPreis; this.nettoUmsatz = 0; } sep // Methode Verkaufen() public void Verkaufen(int stk) { if (stk <= 0) throw new Exception("Die Verkaufsmenge ist ungültig."); else { // Nettoumsatz berechnen, aber // Speise = 10 % bzw. Getränk = 20 % USt // nettoUmsatz += bruttoPreis * stk / (1 + ust[?] / 100) } } Le // Properties public Bezeichnung { get { return bezeichnung; } } public NettoUmsatz { get { return nettoUmsatz; } } } Ü 5: a) Erstelle das Interface IArtikel mit der Methode Verkaufen() und den Get-Properties Bezeichnung und NettoUmsatz. Vererbe das Interface in die Klasse Hotel. b)Erstelle die neue Klasse Hotel und vererbe ihr das Interface IArtikel. Implementiere alle dadurch erforderlichen Methoden und Properties. c) Erstelle die Eigenschaft Nächtigungspauschale in der Klasse Hotel. d)Implementiere die Methode Nächtigung() mit dem Parameter AnzahlNächte und erhöhe den Nettoumsatz um AnzahlNächte x Nächtigungspauschale. Das Nächtigungspauschale ist bereits Netto. e) Das Hotel bietet Zimmer mit Halbpension bzw. Zimmer/Frühstück an. Erstelle die Subklassen Halbpension und Frühstück und ändere die Methode Nächtigung() in der Basisklasse in eine virtuelle Methode. Die Methoden in den Subklassen erhöhen den Nettoumsatz zusätzlich um die Kosten für das Essen (Halbpension 18 EUR, Frühstück 6 EUR). Zusätzlich zu diesen Übungen findest du in SbX eine Internetaufgabe. ID: 1185 86 Softwareentwicklung Lerneinheit 2: Vererbung Sichern Eine Basisklasse vererbt ihre Eigenschaften, Methoden und Properties an die Subklasse, sofern die Zugriffsmodifizierer protected oder public verwendet werden. Konstruktoren wiederverwenden Konstruktormethoden werden nicht vererbt, sondern müssen in der Subklasse neu implementiert werden. Die Konstruktormethode der Subklasse kann aber mit dem Schlüsselwort base einen Basisklassenkonstruktor aufrufen. Methoden überschreiben Eine Subklasse kann geerbte Methoden überschreiben und damit neu implementieren, z.B. verändern oder erweitern. Die Methode der Basisklasse wird mit base.Methode() aufgerufen. Polymorphie Wird eine Methode in der Basisklasse als virtual deklariert, muss jede Subklasse diese Methode mit override neu implementieren. Spätes Binden Durch die Verwendung virtueller Methoden (Polymorphie) ist es möglich, Objekte vom Typ einer Subklasse in den Typ der Basisklasse zu casten, z.B. in einem Array. Erst beim Ausführen der virtuellen Methode steht fest, welches Objekt die Methode aufgerufen hat und welche Methode daher auszuführen ist. Interface Eine Klasse kann nur von einer Klasse erben, aber dafür von mehreren Interfaces. Ein Interface enthält die Deklaration von Methoden und Properties, jedoch keine Implementierung. Wird ein Interface in eine Klasse vererbt, so muss die Klasse alle Methoden und Properties des Interfaces implementieren. sep ro be Vererbung Zusätzlich zu dieser Zusammenfassung findest du in SbX eine Bildschirmpräsentation. ID: 1186 Le Wissen Wiederholungsfragen und -aufgaben 1.Erkläre die Begriffe Basis- und Subklasse! 2.Welche Bestandteile einer Klasse werden vererbt? 3.Wie kann eine Konstruktormethode der Basisklasse in einer Subklasse wiederverwendet werden? 4.Welche Problematik tritt auf, wenn die Subklasse keinen Konstruktor implementiert, die Basisklasse aber schon? 5.Erkläre die Unterschiede zwischen private, protected und public! 6.Welche Auswirkung hat das Schlüsselwort virtual auf die Verwendung von Methoden? 7.Erkläre das späte Binden (late Binding) im Rahmen der Polymorphie! 8.Beschreibe Unterschiede zwischen einem Interface und einer Klasse! Softwareentwicklung 87 2 Objektorientiertes Programmieren In dieser Lerneinheit haben wir die Grundlagen der Vererbung sowie die Verwendung von Polymorphie und Mehrfachvererbung kennengelernt: Lernen Üben Sichern Wissen 9.Was bedeutet gegen eine Klasse bzw. gegen ein Interface programmieren? 10.Welche Konsequenzen hat das Einbinden eines Interfaces in eine Klasse? 11.Welche Vorteile bietet die Verwendung von Interfaces gegenüber Klassen? Zusätzlich zu diesen Aufgaben findest du in SbX ein Quiz. ID: 1187 Lerncheck Ich kann jetzt … be w ... die Bestandteile einer Klasse in Subklassen vererben und Konstruktormethoden der Basisklasse wiederverwenden. w ... virtuelle Methoden erstellen und in den Subklassen überschreiben. w ... Polymorphie und spätes Binden verwenden. w ... gegen Klassen und gegen Interfaces programmieren und meine Klassen durch Mehrfachvererbung flexibler gestalten. Le sep ro In der nächsten Lerneinheit beschäftigen wir uns mit speziellen Konzepten von .NET, wie z.B. den generischen Typparametern (Generics), Indexern und Methodenzeigern (Delegates). 88 Softwareentwicklung Lerneinheit 3: Weitere OOP-Konzepte Lerneinheit 3 Weitere OOP-Konzepte In dieser Lerneinheit lernen wir den Einsatz von generischen Typen für Collections sowie das Konzept der Delegates kennen. Wir beschäftigen uns mit 2 Objektorientiertes Programmieren Alle SbX-Inhalte zu dieser Lerneinheit findest du unter der ID: 1188. be ● der Verwendung von Generics, Indexern und Interfaces sowie ● dem Aufruf von Funktionen mit Hilfe von Delegates. Lernen 1 Generics ro Typsicherheit für Klassen Generics sind ein neues Konzept in .NET 2.0 und werden als Basistechnologie sehr intensiv im .NET-Framework eingesetzt. Wenn wir in Visual Studio 2005 ein neues Projekt erzeugen, egal ob Konsolen- oder Windowsanwendung, erhalten wir automatisch den Namensraum System. Collections.Generic eingebunden. Der Typparameter T wird als Stellvertreter für den Datentyp verwendet. Das Get-Property liefert das ganze Array zurück. Das Set-Property fügt dem Array ein neues Objekt hinzu. Das Lehrbeispiel GenericFlotte findest du unter der ID: 1189. Softwareentwicklung L 1: Generische Klasse Die generische Klasse Flotte speichert beliebige Datentypen in einem Array. class Flotte<T> { private T[] fahrzeuge; private int index; Le <T> ergänzt als generischer Typparameter den Namen der Klasse. sep Im folgenden Lehrbeispiel erstellen wir die Klasse Flotte. Diese soll unterschiedliche Datentypen in einem Array speichern, z.B. Strings oder Fahrrad-Objekte. Der Vorteil generischer Klassen ist ihre universelle Einsatzfähigkeit. public Flotte(int anzahl) { fahrzeuge = new T[anzahl]; index = -1; } public T[] Fahrzeuge { get { return fahrzeuge; } } public T Neu { set { index++; if (index < fahrzeuge.Length) fahrzeuge[index] = value; else throw new Exception("Die Flotte ist voll."); } } } 89 Lernen Üben Sichern Wissen class Fahrrad { private string marke, farbe; public Fahrrad(string marke, string farbe) { this.marke = marke; this.farbe = farbe; } public string Marke { get { return marke; } } public string Farbe { get { return farbe; } } } be // Hauptprogramm Flotte<string> wien = new Flotte<string>(10); wien.Neu = "BMW 320d"; wien.Neu = "Audi A4"; wien.Neu = "Opel Astra"; Flotte<Fahrrad> linz = new Flotte<Fahrrad>(5); linz.Neu = new Fahrrad("KTM", "Silber"); linz.Neu = new Fahrrad("KTM", "Blau"); ro Zur Verwendung der generischen Klasse Flotte wird bei der Objektinstanzierung der Typparameter übergeben, z.B. ein String oder eine andere Klasse. sep Console.WriteLine("Flotte Wien:"); foreach (string f in wien.Fahrzeuge) { if (f != null) Console.WriteLine(f); } Console.WriteLine("\nFlotte Linz:"); foreach (Fahrrad f in linz.Fahrzeuge) { if (f != null) Console.WriteLine(f.Marke + ", " + f.Farbe); } Le Anstatt eines generischen Typparameters hätten wir in der Klasse Flotte auch mit dem allgemeinen Datentyp Object arbeiten können. Jedes Objekt, z.B. ein String oder ein Fahrrad-Objekt leiten implizit von der Klasse Object ab und lassen sich daher nach Object casten. Ein solches Object-Array würde jede Art von Datentyp erlauben, z.B. Autos, Motorräder und Fahrräder innerhalb nur einer Flotte. Durch die Verwendung von Generics gelingen hingegen typsichere Klassen, z.B. generische Collections. Unsere generischen Flotten-Arrays erlauben nur Objekte des gleichen Typs, z.B. entweder nur Autos oder nur Fahrräder. 1 Ein Generic ist ein generischer Typparameter, der für eine Klasse die Verwendung eines bestimmten Datentyps vorschreibt. Zur Verwendung der Klasse Flotte muss bei der Instanzierung eines Objektes ein Typparameter in spitzen Klammern angegeben werden, z.B. Flotte<Auto> berlin = new Flotte<Auto>(100); 2 Der Datentyp für ein generisches Objekt wird bei der Instanzierung angegeben. Ein Flotten-Objekt kann einen beliebigen Datentyp als Typparameter erhalten. Dadurch erhalten wir universell einsetzbare sowie typsichere Flotten-Objekte, die z.B. nur Autos oder nur Fahrräder speichern können. 90 Softwareentwicklung Lerneinheit 3: Weitere OOP-Konzepte Flotte<Auto> bregenz = new Flotte<Auto>(4); T = Auto Flotte<T> Flotte<Fahrrad> graz = new Flotte<Fahrrad>(2); 2 Objektorientiertes Programmieren T = Fahrrad Abb.: Generische Klassen verwenden Generische Collections ro Diese Abbildung findest du in der PowerPointPräsentation unter der ID: 1189. be Flotte<T> sep Statt eines Arrays soll eine Collection verwendet werden, wodurch wir beliebig viele Flottenobjekte speichern können. Im Kapitel 1 haben wir die ArrayList kennengelernt, die das Speichern beliebiger Typen erlaubt, denn in einer ArrayList werden Objekte vom Typ Object gespeichert. Eine ArrayList bietet daher keine Typsicherheit. Seit .NET 2.0 gibt es zusätzlich die generische Klasse List<T> im Namensraum System. Collections.Generic. Diese Klasse erlaubt die Angabe eines gewünschten Datentyps für die Listenelemente. List<T> ist das generische Pendant zur nicht generischen ArrayList. Das Lehrbeispiel zeigt die Realisierung des Hauptprogrammes aus L 1 mit einer genersichen Liste. Die Klasse Flotte wird nicht mehr benötigt. Die generische Liste ersetzt die Klasse Flotte<T> und bietet die gleiche Funktionalität. Le L 2: Generische Liste Die generische Liste List<T> speichert nur Objekte des angegebenen Typs. // Hauptprogramm List<string> wien = new List<string>(); wien.Add("BMW 320d"); wien.Add("Audi A4"); wien.Add("Opel Astra"); List<Fahrrad> linz = new List<Fahrrad>(); linz.Add(new Fahrrad("KTM", "Silber")); linz.Add(new Fahrrad("KTM", "Blau")); Console.WriteLine("Flotte Wien:"); foreach (string f in wien) { Console.WriteLine(f); } Console.WriteLine("\nFlotte Linz:"); foreach (Fahrrad f in linz) { Console.WriteLine(f.Marke + ", " + f.Farbe); } Softwareentwicklung 91 Lernen Üben Mr. What und Ms. Check Sichern Wissen Kann ich eine generische Liste auch mit Wertetypen verwenden? Ja, eine generische Liste ermöglicht die Verwendung aller Datentypen, z.B. int, bool, string, object usw. 2 Innere Klassen Klassen kapseln Lösung: Die Klassen Motor und Sitz sind innere Klassen von Auto. class Motor {..} class Sitz {..} class Auto { private Motor motor; private List<Sitz> sitze; } ro Verstoß gegen das Prinzip der Kapselung: Die Klassen Motor und Sitz sind eine Komposition der Klasse Auto. be Objekte bestehen häufig aus anderen Objekten, z.B. besteht eine Mietwagenflotte aus Autos. Ein Mietwagen-Objekt besteht wiederum aus einem Motor und Sitzgelegenheiten, um Personen zu transportieren. Eine Klasse Auto, das aus einem Motorobjekt und einer Liste aus Sitzobjekten besteht, könnten wir wie folgt erstellen: sep class Mietwagen : Auto {..} Diese Lösung hat einen entscheidenden Nachteil – sie verstößt gegen das Prinzip der Kapselung! Die Klassen Motor und Sitz dürfen außerhalb der Klasse Auto nicht verwendbar sein, da ein Motor oder ein Sitz ohne Auto nicht vermietet werden kann. Motor- und Sitzobjekte sollten also ohne ein Auto nicht für sich alleine existieren dürfen. Wenden wir das Prinzip der Kapselung auf die Klasse Auto an, müssen wir die Klassen Motor und Sitz als private innere Klassen von Auto deklarieren. Komposition = Aggregation, bei der die Teile vom Objekt existenzabhängig sind. Le Aggregation = Zusammensetzung eines Objektes aus einer Menge an Einzelteilen. class Auto { private Motor motor; private List<Sitz> sitze; private class Motor {..} private class Sitz {..} } Auto Näheres zur Aggregation und zur Komposition erfährst du im Kapitel zur UML. Motor hat hat Sitz Komposition Diese Abbildung findest du in der PowerPointPräsentation unter der ID: 1189. 92 Abb.: Innere Klassen Softwareentwicklung Lerneinheit 3: Weitere OOP-Konzepte 3 Indexer Zugriff auf Listenelemente in einem Objekt Die Klasse Flotte soll eine Liste von Auto-Objekten kapseln und über einen Indexer direkten Zugriff auf die Listenelemente ermöglichen. Sehen wir uns den Programmcode dafür an: Das Lehrbeispiel Indexer findest du unter der ID: 1189. 2 Objektorientiertes Programmieren L 3: Indexer Die Klasse Flotte enthält eine Liste von Autos und ermöglicht über den Objektnamen den direkten Zugriff auf Listenelemente. class Auto {..} public Auto this[int index] Auto-Objekt { get { return flotte[index]; } set { flotte.Add(value); } } ro Der Indexer wird als Property mit dem Namen this[] deklariert. be class Flotte { private List<Auto> flotte = new List<Auto>(); public int Count { get { return flotte.Count; } } } // Hauptprogramm Flotte wien = new Flotte(); // Indexer verwenden wien[0] = new Auto("Porsche", "Schwarz"); wien[1] = new Auto("Mercedes", "Rot"); sep Indexer aufrufen Beim Aufruf des Indexers mit wien[0] wird das erzeugte Auto-Objekt (z.B. Porsche oder Mercedes) an das Property this[int index] vom Typ Auto übergeben. Die Eigenschaft value des Set-Properties enthält das Auto-Objekt, das der Liste flotte hinzugefügt wird. Das GetProperty Count liefert die Anzahl der Auto-Objekte in einer Flotte. Le Der Indexer: this[] Das Schlüsselwort this haben wir bereits kennengelernt, es referenziert das aktuelle Objekt. Im Lehrbeispiel verweist this auf das Flottenobjekt wien. Der Aufruf von wien[0] ruft das Property this[int index] auf und übergibt das erzeugte Auto-Objekt als Property-Value. Der Parameter index speichert den angegebenen Index (z.B. 0) und verweist auf das erste Listenelement. Über das Get-Property von this[int index] wird ein Listen-Objekt ausgelesen. Beachte Mr. What und Ms. Check Softwareentwicklung Der Aufruf eines Indexers sieht zwar wie der Aufruf eines Arrays aus, tatsächlich ist der Indexer aber ein Property, das auf ein Array oder eine Collection zugreift. Wofür kann ich einen Indexer sinnvoll einsetzen? Mit einem Indexer kannst du direkt auf Arrays oder Collections zugreifen, die in einem Objekt gekapselt sind. Das Objekt selbst scheint die Liste zu sein und kann wie die Liste verwendet werden, indem der Index des gewünschten Elements angegeben wird. 93 Lernen Üben Sichern Wissen Die Interfaces IEnumerable und IEnumerator Damit die Klasse Flotte wie eine Liste verwendet werden kann, z.B. um damit durch eine foreach-Schleife zu iterieren, muss sie das Interface IEnumerable implementieren. Wie wir bereits wissen, schreibt die Implementierung eines Interfaces die Implementierung der abstrakten Interface-Methoden vor. In unserem Fall ist das die Methode GetEnumerator(). Interface-Smarttag Der Smarttag des Interfaces ermöglicht die automatische Implementierung aller vorgeschriebenen Methoden. Das Interface IEnumerable schreibt die Methode GetEnumerator() zur Implementierung vor. class Auto {..} class Flotte : IEnumerable { private List<Auto> flotte = new List<Auto>(); public Auto this[int index] { get { return flotte[index]; } set { flotte.Add(value); } } ro Das Lehrbeispiel Indexer findest du unter der ID: 1189. be L 4: Implementierung des Interfaces IEnumerable Die Klasse Flotte enthält eine Liste von Autos und ermöglicht über den Objektnamen den direkten Zugriff auf Listenelemente. public int Count { get { return flotte.Count; } } Das Interface IEnumerator schreibt die Methoden für die Klasse FlottenEnumerator vor. sep Die Implementierung der Klasse FlottenEnumerator erfolgt als innere Klasse. public IEnumerator GetEnumerator() { return (IEnumerator)new FlottenEnumerator(this); } private class FlottenEnumerator : IEnumerator { private int index = -1; private Flotte flotte; public FlottenEnumerator(Flotte flotte) { this.flotte = flotte; } Le Die Methode GetEnumerator() liefert ein FlottenEnumeratorObjekt vom Typ des Interfaces IEnumerator zurück. public object Current { get { return flotte[index]; } } public bool MoveNext() { index++; if (index < flotte.Count) return true; // Ende noch nicht überschritten! else return false; // Ende überschritten! } public void Reset() { index = -1; } } } 94 Softwareentwicklung Lerneinheit 3: Weitere OOP-Konzepte 4 Delegates und Events Callbacks Ein Delegate-Objekt kapselt den Verweis auf eine oder mehrere Methoden einer Klasse. Mit Delegates können Callbacks (Rückrufe) und Events (Ereignisse) programmiert werden. Die Methoden Fahren und Fliegen in der Klasse Transport werden durch den Callback aufgerufen. Delegate deklarieren class Fahrzeug { public delegate void Transport(int distanz); public void Transportieren(Transport trans) { if (trans != null) Methodenparameter ist trans(200); das Delegate-Objekt } } Callback class Transport { public static void Fahren(int km) { Console.WriteLine("Fahre " + km + " km."); } public static void Fliegen(int flugmeilen) { Console.WriteLine("Fliege " + flugmeilen + " km."); } } Le // Hauptprogramm Fahrzeug auto = new Fahrzeug(); Fahrzeug flugzeug = new Fahrzeug(); Das Delegate-Objekt kapselt den Zeiger auf die aufzurufende Methode. 2 Objektorientiertes Programmieren Die Methode Transportieren führt den Callback mit Hilfe des Delegate-Objektes durch. be Mit dem Schlüsselwort delegate wird ein Delegate deklariert. L 5: Callbacks verwenden Die Klasse Fahrzeug holt sich über einen Callback die benötigte Methode aus der Klasse Transport. ro Das Lehrbeispiel Delegate findest du unter der ID: 1189. Ein Delegate ist ein Objekt, das einen Zeiger auf eine Objektmethode beschreibt. sep Beachte Delegate-Objekt instanzieren Fahrzeug.Transport autoTrans = new Fahrzeug.Transport(Transport.Fahren); Fahrzeug.Transport flugzeugTrans = new Fahrzeug.Transport(Transport.Fliegen); auto.Transportieren(autoTrans); flugzeug.Transportieren(flugzeugTrans); Methodenaufruf mit DelegateObjekt als Parameter 1 Bei der Instanzierung des Delegate-Objektes wird die Zielmethode festgelegt. Methodensignatur = Rückgabetyp und Typen der Parameter. Das Delegate-Objekt beschreibt einen Zeiger auf die aufzurufende Methode. Die Methodensignaturen des Delegates sowie der Zielmethode müssen übereinstimmen. 2 Der Methodenaufruf erfolgt durch Übergabe des Delegate-Objektes. Die Methode Transportieren der Klasse Fahrzeug weiß nicht, um welches Fahrzeug es sich handelt. Die aufzurufende Zielmethode ist im Delegate-Objekt gekapselt. Wenn ein Delegate-Objekt vorhanden ist, erfolgt der Callback unter Ausführung des Delegates. Softwareentwicklung 95 Lernen Üben Sichern Wissen Ereignisse (Events) Die Ereignissteuerung, z.B. von Windowsprogrammen, basiert in C# auf Delegates. Das Event verwendet ein Delegate zum Callback-Aufruf der Ereignisprozedur. Im folgenden Lehrbeispiel reagiert die Ereignisprozedur reagiereAufKlingeln auf das Ereignis Klingeln, wenn die Weckzeit erreicht ist. L 6: Events verwenden Die Klassen Wecker und Wecken kommunizieren über das Ereignis Klingeln. delegate void WeckerEventHandler(DateTime uhrzeit); // Klasse, die das Event auslöst Delegate und Event class Wecker deklarieren { public event WeckerEventHandler Klingeln; private DateTime weckzeit, uhrzeit; // Eigenschaften public Wecker(DateTime weckzeit) // Konstruktor { this.weckzeit = weckzeit; } be Der Delegate kapselt den Aufruf des Eventhandlers reagiereAufKlingeln in der Klasse Wecken. Das Wecker-Objekt registriert den Eventhandler. Das Lehrbeispiel Events findest du unter der ID: 1189. 96 // Klasse, die auf das Event reagiert class Wecken { public Wecken(Wecker wecker) // Konstruktor { Eventhandler registrieren wecker.Klingeln += new WeckerEventHandler(reagiereAufKlingeln); } private void reagiereAufKlingeln(DateTime uhrzeit) { Eventhandler Console.WriteLine("Aufstehen!!!"); } } Le Das Event Klingeln ruft über den Delegate die Ereignisprozedur auf. sep ro public DateTime Uhrzeit // Property { get { return uhrzeit; } set { uhrzeit = value; if (uhrzeit.Hour == weckzeit.Hour && uhrzeit.Minute == weckzeit.Minute && uhrzeit.Second == weckzeit.Second) { Klingeln(uhrzeit); Event auslösen } } } } // Hauptprogramm Wecker wecker = new Wecker(Convert.ToDateTime("6:30:00")); Wecken wecken = new Wecken(wecker); wecker.Uhrzeit = Convert.ToDateTime("6:29:55"); for (int i = 0; i <= 10; i++) { wecker.Uhrzeit = wecker.Uhrzeit.AddSeconds(1); Console.WriteLine("Es ist jetzt: " + wecker.Uhrzeit); } Softwareentwicklung Lerneinheit 3: Weitere OOP-Konzepte 1 Das Weckerobjekt wecker registriert den Eventhandler reagiereAufKlingeln für das Event Klingeln. Der Aufruf von Klingeln führt über den Delegate WeckerEventHandler zur Ereignisprozedur. Die Klasse Wecker kommuniziert über das Ereignis Klingeln mit der Klasse Wecken. Das Prinzip der Kapselung wird auch für Nachrichten zwischen Objekten angewendet. Wie funktionieren Ereignisse in der Sprache Java? In Java gibt es keine Delegates wie in C#, daher werden Ereignisse dort als Objekte dargestellt. Im Anhang findest du eine Darstellung einiger Unterschiede zwischen C# und Java. ro be Mr. What und Ms. Check Üben Ü 1: Erstelle zu den folgenden Aufgaben die Deklarationen in C#: sep Übungsbeispiele a) Erstelle eine generische Collection (Liste) für bool-Werte. b) Erstelle eine Klasse mit den Eigenschaften km und reichweite, wobei die Datentypen beider Eigenschaften generisch sind. Ü 2: Die Klasse Unterricht soll eine typsichere generische Liste enthalten. Der Unterricht darf nur für Schüler oder für SeminarTeilnehmer stattfinden. Erstelle die inneren Klassen. Le class Unterricht { public ArrayList = new ArrayList(); } class Lernender { public int jahrgang; public string name; } class Schüler : Lernender { } class SeminarTeilnehmer : Lernender { } Softwareentwicklung 97 2 Objektorientiertes Programmieren 2 Da die Ereignisprozedur in der Klasse Wecken gekapselt ist (private), reagiert sie ausschließlich auf das Auftreten des Ereignisses. Lernen Üben Sichern Wissen Ü 3: Erstelle die Klasse Orchester mit einer generischen Liste zum Speichern der verschiedenen Musikinstrumente, z.B. Flöte, Chello, Posaune usw. Alle Musikinstrumente implementieren das Interface IInstrument. Erstelle in der Klasse Orchester einen Indexer für den direkten Zugriff auf die Instrumentenliste. Ü 4: Verwende die Klassen aus Ü 3 und ergänze die Klasse Konzert. Erstelle ein Delegate für das Spielen eines Konzertes. Am Konzert sind alle Instrumente beteiligt. Verwalte die Instrumente in einer generischen Liste vom Typ des Interfaces. Zusätzlich zu diesen Übungen findest du in SbX eine Internetaufgabe. be ID: 1190 Sichern ro In dieser Lerneinheit haben wir weitere objektorientierte Konzepte, wie generische Typparameter, innere Klassen, Indexer und Delegates, kennengelernt: Ein Generic ist ein Typparameter für eine Klasse oder eine Methode um typsichere Klassen und Methodenaufrufe programmieren zu können. Ein Beispiel für einen häufig verwendeten Generic ist die genersiche Collection List<T> aus dem Namensraum System.Collections. Generic. Innere Klasse Mit Hilfe von inneren Klassen lassen sich die Bestandteile von Klassen in weiteren Klassen integrieren, ohne gegen das Prinzip der Kapselung zu verstoßen. Indexer Der Indexer this[] ermöglicht den Zugriff auf Listenelemente einer Eigenschaft in einer Klasse über ein Property. Die Instanz der Klasse wird dabei wie eine Liste, z.B. ein Array oder eine Collection, behandelt. Die Werte der Liste werden über einen Index angesprochen. IEnumerable Durch die Implementierung des Interfaces IEnumerable kann eine Klasse wie eine Liste verwendet werden, z.B. mit einer foreach-Schleife. Delegate Ein Delegate ist ein Zeiger auf eine Methode. Delegates ermöglichen Callback-Aufrufe von Methoden in anderen Klassen und stellen die Grundlage für die Eventprogrammierung in C# dar. Callback Als Callback wird der Aufruf einer Methode in einer anderen Klasse bezeichnet. Callbacks ermöglichen die Übermittlung von Nachrichten zwischen verschiedenen Objekten. Event Die Programmierung von Ereignissen funktioniert in C# über Delegates. Wenn ein Objekt ein bestimmtes Ereignis auslösen möchte, muss der entsprechende Eventhandler abonniert werden. Le sep Generic Zusätzlich zu dieser Zusammenfassung findest du in SbX eine Bildschirmpräsentation. ID: 1191 98 Softwareentwicklung Lerneinheit 3: Weitere OOP-Konzepte Wissen Wiederholungsfragen und -aufgaben 2.Welche Bestandteile einer Klasse werden vererbt? 3.Wie kann eine Konstruktormethode der Basisklasse in einer Subklasse wiederverwendet werden? be 4.Welche Problematik tritt auf, wenn die Subklasse keinen Konstruktor implementiert, die Basisklasse aber schon? 5.Erkläre die Unterschiede zwischen private, protected und public! 6.Welche Auswirkung hat das Schlüsselwort virtual auf die Verwendung von Methoden? 7.Erkläre das späte Binden (late Binding) im Rahmen der Polymorphie! ro 8.Beschreibe Unterschiede zwischen einem Interface und einer Klasse! 9.Was bedeutet gegen eine Klasse bzw. gegen ein Interface programmieren? 10.Welche Konsequenzen hat das Einbinden eines Interfaces in eine Klasse? sep 11.Welche Vorteile bietet die Verwendung von Interfaces gegenüber Klassen? Zusätzlich zu diesen Aufgaben findest du in SbX ein Quiz. ID: 1192 Lerncheck Ich kann jetzt … Le w ... die Bestandteile einer Klasse in Subklassen vererben und Konstruktormethoden der Basisklasse wiederverwenden. w ... virtuelle Methoden erstellen und in den Subklassen überschreiben. w ... Polymorphie und spätes Binden verwenden. w ... gegen Klassen und gegen Interfaces programmieren und meine Klassen durch Mehrfachvererbung flexibler gestalten. In der nächsten Lerneinheit beschäftigen wir uns mit ADO.NET und der Verwendung von Dateien und Datenbanken zum Lesen und Speichern von Daten. Softwareentwicklung 99 2 Objektorientiertes Programmieren 1.Erkläre die Begriffe Basis- und Subklasse! Lernen Üben Sichern Wissen Lerneinheit 4 Datenspeicherung Alle SbX-Inhalte zu dieser Lerneinheit findest du unter der ID: 1193. In dieser Lerneinheit lernen wir, wie wir Objektdaten in einer Datei abspeichern und aus einer Datei einlesen können. Für den Dateizugriff verwenden wir Textdateien im CSV-Format, XML-Dateien und relationale Datenbanken. In einem immer komplexer werdenden Lehrbeispiel beschäftigen wir uns mit Lernen Tier = Schicht; häufig wird auch der Begriff Layer verwendet. Three-Tier-Architecture ro 1 Drei-Schichten-Architektur be ● dem Dateizugriff auf Textdateien mittels StreamReader und StreamWriter, ● der XML-Serialisierung von Objekten sowie ● dem Zugriff auf eine Access- bzw. SQL-Server-Datenbank. In den Beispielen des ersten Kapitels befand sich die Programmlogik, wie z.B. eine Berechnung, direkt im Formular der Windows-Anwendung. sep Im diesem Kapitel haben wir die Programmlogik in verschiedene Klassen eingekapselt und dadurch die Windows-Formulare entlastet. Die aus den Logikklassen abgeleiteten Objekte achten auf die Korrektheit der enthaltenen Daten und erlauben keine unzulässigen Operationen (Methoden). In vielen Programmen werden die Präsentations- und die Logikschicht durch eine Datenschicht erweitert. Diese ist für die dauerhafte Speicherung der Objektdaten verantwortlich und besteht im einfachsten Fall aus einer Textdatei. Middle Tier: Business-Logik, z.B. Klassen für die Objektinstanzierung. Data Tier: Datenspeicherung, z.B. Textdatei oder Datenbank. Drei-Schichten-Architektur Le Presentation Tier: Benutzerschnittstelle, z.B. WinForms oder WebForms. Präsentationsschicht Logikschicht Diese Abbildung findest du in der PowerPointPräsentation unter der ID: 1194. Beachte 100 Abb.: Drei-Schichten-Architektur Datenschicht Zur Modellierung der Logikschicht werden in der Praxis verschiedene Diagramme der Unified Modelling Language (UML) verwendet, z.B. ein Use-Case-Diagramm und ein Klassendiagramm. Mit UML beschäftigst du dich im Kapitel 3. Softwareentwicklung Lerneinheit 4: Datenspeicherung Dreischichtige Anwendungen 1 Die Präsentationsschicht (Presentation Tier) umfasst die Benutzerschnittstelle. Als Benutzerschnittstellen kommen Windows-Formulare, WebForms und Konsolenanwendungen in Frage. Zur Benutzerschnittstelle gehören die Navigation, die Darstellung von Eingabemasken, die Eingabeüberprüfung (Validitätsprüfung) und die Ausgabe von Berichten. Bei der Geschäftslogik gilt der Grundsatz der Datenkapselung. Daten und Programmcodes werden in Klassen bzw. davon abgeleiteten Objekten verwaltet. Klassen und Objekte sind für ihre korrekte Funktion und ihre korrekten Daten eigenverantwortlich. persistent = dauerhaft 3 Die Datenschicht (Data Tier) speichert die Daten der Objekte persistent ab. Welchen Vorteil hat die Verwendung einer Drei-Schichten-Architektur? Alle drei Schichten sind voneinander völlig unabhängig. So kannst du die Geschäftslogik und die Datenschicht für Windows- und WebAnwendungen verwenden, ohne daran etwas verändern zu müssen. ro Mr. What und Ms. Check be Damit die Daten der Objekte auch nach dem Schließen des Anwenderprogrammes bereitgestellt werden können, müssen diese dauerhaft gespeichert werden, z.B. in einer Textdatei, in einer XML-Datei oder in einer relationalen Datenbank. sep Die Datenschicht besteht im einfachsten Fall aus einer Textdatei, die mit Hilfe entsprechender .NET-Klassen erstellt und gelesen werden kann. Bei der Verwendung dieser Methoden achten wir darauf, dass wir die Geschäftslogik von der Datenzugriffslogik abgrenzen. Wenn wir später z.B. statt auf Text- auf XML-Dateien oder eine Datenbank zugreifen möchten, ist diese Veränderung einfach und rasch durchführbar. 2 Textdateien Die Klassen StreamReader und StreamWriter Le Das .NET-Framework bietet im Namensraum System.IO die Klassen StreamReader und StreamWriter für den Zugriff auf Textdateien an. CSV = comma separated value Diese Abbildung findest du in der PowerPointPräsentation unter der ID: 1194. Softwareentwicklung CSV-Datei Abb.: Dateizugriff mit der Klasse StreamReader 101 2 Objektorientiertes Programmieren 2 Die Geschäftslogik (Business Logic) wird in Klassen eingekapselt. Die Collection fz dient vorübergehend als Container für die Fahrzeugobjekte. Bei Pfadangaben stellen wir einem String einen Klammeraffen vor, z.B. string pfad = @".\"; Das StreamReader-Objekt r öffnet die Datei Fuhrpark.csv für den Lesezugriff. Die private Methode GetFahrzeug() liefert ein fertiges Objekt vom Interfacetyp IFahrzeug. Um die Polymorphie nutzen zu können, werden alle Fahrzeugobjekte von der Collection in das Array fahrzeug kopiert. Die Methode Split() erzeugt ein Array mit den Feldern der Zeile. Als Trennzeichen wird ein Strichpunkt festgelegt. Die Hilfsmethode GetFahrzeug() erhält die aus der Textdatei eingelesene Zeile als Parameter übergeben und erzeugt daraus ein PKW- bzw. LKW-Objekt, das an die Methode Fahrzeugdatei_Lesen() zurückgegeben wird. 102 Das folgende Lehrbeispiel zeigt die Erweiterung des Fuhrparkbeispieles um die Klasse Datenzugriff mit der Methode Fahrzeugdatei_Lesen(). L 1: Die Klasse Datenzugriff kapselt die statische Methode Fahrzeugdatei_Lesen(). using System.Collections; using System.IO; Die Methode verweist auf das Array fahrzeug aus dem aufrufenden Code. class Datenzugriff { public static void Fahrzeugdatei_Lesen(ref IFahrzeug[] fahrzeug) { ArrayList fz = new ArrayList(); fz.Add(new Bahn()); Referenztypparameter string pfad = @".\"; string dateiname = "Fuhrpark.csv"; string zeile; be Zur Verwendung der Klasse StreamReader wird der Namensraum System.IO eingebunden. Wissen StreamReader r = new StreamReader(pfad + dateiname); while ((zeile = r.ReadLine()) != null) { fz.Add(GetFahrzeug(zeile)); } r.Close(); ro Das Fallbeispiel Fuhrpark_CSV findest du unter der ID: 1194. Sichern // Arraylist in das Array fahrzeug kopieren fahrzeug = new IFahrzeug[fz.Count]; fz.CopyTo(fahrzeug); } sep Üben // Hilfsmethode erzeugt ein Pkw-/Lkw-Objekt // aus dem übergebenen CSV-String private static IFahrzeug GetFahrzeug(string zeile) { IFahrzeug auto; Motor motor; string[] felder = zeile.Split(';'); if (felder[0] == "Pkw") { if (felder[5] == "Benzin") motor = Motor.Benzin; else motor = Motor.Diesel; Le Lernen auto = new Pkw(felder[1], felder[2], Convert.ToDouble(felder[3]), Convert.ToDouble(felder[4]), motor, Convert.ToBoolean(felder[6])); } else { auto = new Lkw(felder[1], felder[2], Convert.ToDouble(felder[3]), Convert.ToDouble(felder[4]), Convert.ToByte(felder[5])); } return auto; } } Softwareentwicklung Lerneinheit 4: Datenspeicherung Die Klasse Datenzugriff verbindet die Geschäftslogik, das Array fahrzeug, mit der Datenschicht, der Textdatei Fuhrpark.csv. Abb.: Dateizugriff mit der Klasse StreamReader sep Diese Abbildung findest du in der PowerPointPräsentation unter der ID: 1194. ro be 2 Objektorientiertes Programmieren Die statische Methode Fahrzeugdatei_Lesen() erhält als Parameter einen Verweis auf das Array fahrzeug des aufrufenden Codes, der Geschäftslogik. Durch den Verweistypparameter greift die Methode auf das Array der Geschäftslogik durch und erstellt darin ein neues Array mit den aus der Textdatei eingelesenen Fahrzeugobjekten. Ü 1: Erstelle die statische Methode GetFahrzeuge() in der Klasse Datenzugriff. Die Methode hat keine Parameter und der Rückgabewert ist ein Array vom Typ IFahrzeug. Der Methodenaufruf von GetFahrzeuge() lautet im Hauptprogramm wie folgt: Le IFahrzeug[] fahrzeug; ... fahrzeug = Datenzugriff.GetFahrzeuge(); Das folgende Lehrbeispiel L 2 demonstriert das Speichern von Textdateien sowie das Öffnen der geschriebenen Datei mit einer Applikation, z.B. Excel für CSV-Dateien. L 2: Die Klasse Datenzugriff wird um die Methode Transportkostendatei_Schreiben() erweitert. Das Fallbeispiel Fuhrpark_CSV findest du unter der ID: 1194. Mit dem StreamWriter-Objekt w wird eine neue Datei erstellt. false = Neue Datei true = Datei ergänzen public static void Transportkostendatei_Schreiben(ref IFahrzeug[] fahrzeug) { string pfad = @".\"; string dateiname = "Transportkosten.csv"; string zeile; StreamWriter w = new StreamWriter(pfad + dateiname, false, Encoding.Default); Das Encoding legt den Zeichensatz für die Textdatei fest. Softwareentwicklung 103 In der Variablen zeile wird der Ausgabestring für die Textdatei zusammengebaut. Die Methode WriteLine() schreibt die Zeile in die Textdatei. Startet ein Programm zur Bearbeitung von CSV-Dateien, z.B. Excel. Hinweis Wissen foreach (IFahrzeug fz in fahrzeug) { if (fz is Pkw) zeile = "Pkw;"; else if (fz is Lkw) zeile = "Lkw;"; else zeile = "Bahn;"; zeile += fz.Bezeichnung + ";" + fz.Kosten.ToString("C"); w.WriteLine(zeile); } w.Close(); // Datei mit verknüpftem Programm öffnen, z.B. Excel System.Diagnostics.Process.Start(pfad + dateiname); } Der Aufruf der Textdatei sollte idealerweise in der Präsentationsschicht erfolgen, um die Datenschicht unabhängig zu machen. Kann ich mit StreamReader auch Bilder einlesen? Nein, zum Einlesen von Binärdateien gibt es die Klasse BinaryReader. Mit der Methode PeekChar() kannst du Zeichen für Zeichen aus einer Binärdatei, z.B. einem Bitmap, einlesen. Wenn das Dateiende erreicht ist, liefert PeekChar() den Wert –1. sep Mr. What und Ms. Check Sichern be Üben ro Lernen Le Neben dem Zugriff auf Textdateien ist es häufig erforderlich, den Inhalt von Verzeichnissen zu verarbeiten, neue Verzeichnisse anzulegen oder bestehende zu löschen. Auch für die Verarbeitung von Dateien werden oft bestimmte Informationen benötigt. Für diese Operationen bietet das Framework einige Klassen an. 3 Dateisystem-Operationen Arbeiten mit Verzeichnissen und Dateien Das .NET-Framework stellt zur Bearbeitung von Verzeichnissen und Dateien folgende Klassen zur Verfügung: Klasse Aufruf über Methode Beschreibung Directory Klasse CreateDirectory(pfad) Erstellt ein Verzeichnis im angegebenen Pfad. Delete(pfad, true) Löscht ein Verzeichnis mit allen Unterverzeichnissen und Dateien im angegebenen Pfad. Move(pfad1, pfad2) Verschiebt das Verzeichnis pfad1 an die Stelle von pfad2 und ändert ggf. den Namen. SetCurrentDirectory(pfad) Legt das aktuelle Arbeitsverzeichnis fest. GetCurrentDirectory() 104 Ermittelt das aktuelle Arbeitsverzeichnis. Softwareentwicklung Aufruf über Methode Beschreibung DirectoryInfo Objekt GetDirectories() Liefert alle Unterverzeichnisse in einem Array zurück. GetFiles() Liefert alle Dateien in einem Array zurück. Copy(file1, file2) Kopiert die Datei file1 nach file2. Move(file1, file2) Verschiebt die Datei file1 nach file2; wird auch zum Umbenennen verwendet. GetCreationTime(file) Liefert Datum und Uhrzeit der Erstellung. GetLastAccessTime(file) Liefert Datum und Uhrzeit des letzten Zugriffs. File Klasse GetAttributes(file) Exists(file) Liefert die Dateiattribute, z.B. Archive, Hidden, ReadOnly, Compressed, Directory etc., als Wert zurück. Liefert true, falls die Datei existiert. Die Klassen Directory und File enthalten statische Methoden, die Klasse DirectoryInfo hingegen nicht-statische, d.h. dass der Methodenaufruf über ein instanziertes Objekt erfolgt. ro Beachte be Klasse sep Das folgende Lehrbeispiel demonstriert die Verwendung der in der Tabelle angeführten Operationen. L 3: Verschiedene Operationen für die Bearbeitung von Verzeichnissen und Dateien: using System.IO; // Verzeichnis erstellen Directory.CreateDirectory(@"c:\meinOrdner"); Le // Verzeichnis verschieben und umbenennen Directory.Move(@"c:\meinOrdner", @"d:\deinOrdner"); // Verzeichnis mit allen Inhalten löschen Directory.Delete(@"c:\oldstuff"); // Arbeitsverzeichnis festlegen Directory.SetCurrentDirectory(@"d:\deinOrdner"); // Arbeitsverzeichnis ermitteln string pfad = Directory.GetCurrentDirectory(); // DirectoryInfo verwendet nicht-statische Methoden // –> Objekt dirObj instanzieren DirectoryInfo dirObj = new DirectoryInfo(@"c:\windows"); Die Eigenschaft Name enthält den Namen des Verzeichnisses bzw. der Datei, z.B. subdirs[i].Name oder files[i].Name. Softwareentwicklung // Unterverzeichnisse ermitteln DirectoryInfo[] subdirs = dirObj.GetDirectories(); // Dateien eines Verzeichnisses ermitteln FileInfo[] files = dirObj.GetFiles(); 105 2 Objektorientiertes Programmieren Lerneinheit 4: Datenspeicherung Lernen Üben Sichern Wissen // Datei kopieren File.Copy(@"c:\daten.txt", @"d:\deinOrdner\daten.txt"); // Datei umbenennen (beide Pfade sind ident) File.Move(@"d:\deinOrdner\daten.txt", @"d:\deinOrdner\autos.csv"); // Datei verschieben und umbenennen File.Move(@"d:\deinOrdner\autos.csv", @"e:\sicherung.csv"); be Gibt es auch Klassen, um auf Dateien und Ordner zugreifen zu können? Mit der Klasse DriveInfo kannst du die Laufwerke, mit DirectoryInfo Ordner und mit FileInfo Dateien anzeigen lassen. Die Sicherheitseinstellungen von Ordnern und Dateien (ACL) kannst du über den Namespace System.Security.AccessControl verändern bzw. auslesen. sep Mr. What und Ms. Check ro Zur Ermittlung des Dateiattributes ReadOnly wird eine bitweise Oder-Verknüpfung durchgeführt. // Dateiattribute ermitteln Directory.SetCurrentDirectory(@"d:\deinOrdner"); if (File.Exists("autos.csv")) { label1.Text = "Erstellungsdatum: " + File.GetCreationTime("autos.csv").ToString(); FileAttributes a = File.GetAttributes("autos.csv"); if (a == (a | FileAttributes.ReadOnly)) MessageBox.Show("Die Datei ist schreibgeschützt."); } else label1.Text = "File existiert nicht."; Ein großer Nachteil bei der Verwendung von Textdateien zur Datenspeicherung besteht darin, dass der Aufbau der Daten in der Textdatei fest vorgegeben ist. Mit Hilfe der Extensible Markup Language (XML) lassen sich Textdateien erstellen, die ihre Datenstruktur selbst beschreiben und gleichzeitig die Daten enthalten. Le 4 XML-Serialisierung Objekte speichern Serialisierung ist das Abspeichern bzw. Laden von Objekten auf ein Speichermedium, wie z.B. eine Festplatte. Wie wir be<?xml version="1.0"?> reits gesehen haben, ist die Se<ArrayOfAnyType rialisierung mittels Textdateien xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" keine ideale Lösung, denn die xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <anyType xsi:type="XmlAuto"> Inhalte der Textdatei sind starr. <Bezeichnung>Bahn</Bezeichnung> Der Aufbau der Datei muss <Kosten>350</Kosten> immer gleich sein. </anyType> Der Aufbau der Daten ist Bestandteil einer XML-Datei. <anyType xsi:type="XmlAuto"> <Bezeichnung>E 230, W-1001</Bezeichnung> <Kosten>822.6</Kosten> </anyType> <anyType xsi:type="XmlAuto"> <Bezeichnung>VW Sprinter, W-1003</Bezeichnung> <Kosten>920</Kosten> </anyType> </ArrayOfAnyType> Eine XML-Datei enthält die Objektdaten in Form einer Baumstruktur und beschreibt den Typ der Daten, z.B. die Klasse. Abb.: XML-Darstellung von Fahrzeug-Objekten 106 Softwareentwicklung Lerneinheit 4: Datenspeicherung Extensible Markup Language Die Extensible Markup Language (XML) dient zur strukturierten Speicherung und zum Austausch von Daten. XML-Dateien sind logisch als Baumstruktur aufgebaut. Die Hierarchie für den Aufbau der Daten ist in der XML-Datei enthalten (Schema). Aufbau von XML-Dateien XML = Extensible Markup Language 1 Eine XML-Datei speichert Daten in Form einer Baumstruktur. Die Felder der Tabelle T_Schueler werden als Tags dargestellt und beinhalten die Daten. XSD = XML-SchemaDefinition XSL = XML-Style-Sheet be 2 Das XML-Schema legt die Struktur der XML-Datei fest. Die XML-Schema-Definition (XSD) kann Bestandteil der XML-Datei sein oder importiert werden. In der Beispieldatei wird ein Standard-XSD des W3C verwendet. 3 Ein XML-Style-Sheet legt das Layout der XML-Datei fest. ro XML-Style-Sheet (XSL) ist ein Sprachstandard zur Formatierung von XML-Seiten. So könnte z.B. eine XML-Datei auf einem Ausdruck anders aussehen als bei der Anzeige am PC. XML ist ein wesentlicher Bestandteil des .NET-Frameworks. Es bietet nicht nur mächtige Klassen zur Verarbeitung von XML-Dateien an, sondern nutzt auch selbst XML intensiv als Datenformat. Auch Visual Studio 2005 verwendet XML innerhalb von Projekten, z.B. liegen alle Dateien mit den Endungen .csproj, .resx und .settings im XML-Format vor. sep Objekte serialisieren Um Objekte in eine XML-Datei mittels XML-Serialisierung abspeichern zu können, müssen diese bestimmte Voraussetzungen erfüllen. 1 Die Namensräume System.Xml und System.Xml.Serialization werden eingebunden. Le Damit wir die XML-Klassen verwenden können, binden wir mit dem Befehl using die erforderlichen Namensräume ein. 2 Die zu serialisierenden Objekte müssen einen Standardkonstruktor sowie öffentliche Eigenschaften haben. Da diese Anforderungen dem Prinzip der Kapselung widersprechen, erstellen wir dafür eine Hilfsklasse. Die Objekte werden in den Typ der Hilfsklasse übertragen. 3 Die Collection mit den zu serialisierenden Objekten muss die XML-Serialisierung unterstützen. Wir erzeugen dafür eine von ArrayList abgeleitete Collection, die mit Hilfe von XmlInclude den Typ der Hilfsklasse unterstützt. Das Fallbeispiel Fuhrpark_XML findest du unter der ID: 1194. Einbindung der Namensräume für die XML-Serialisierung Softwareentwicklung L 4: Zur Serialisierung aller Fahrzeug-Objekte wird die Hilfsklasse XmlAuto mit einem Standardkonstruktor und öffentlichen Eigenschaften erstellt. Die Klasse FahrzeugList ist eine ArrayList für die XML-Serialisierung. // Für XML-Serialisierung erforderlich using System.Xml; using System.Xml.Serialization; 107 2 Objektorientiertes Programmieren XML steht in allen Office-Anwendungen von Microsoft als Dateityp zum Speichern von Daten zur Verfügung. Lernen Üben Die Hilfsklasse benötigt öffentliche Eigenschaften sowie einen Konstruktor ohne Parameter. Dieser wird automatisch erzeugt, wenn kein Konstruktor implementiert wird. Wissen // Hilfsklasse für XML-Serialisierung public class XmlAuto { public string Bezeichnung; public double Kosten; } // Die Klasse FahrzeugList leitet von ArrayList ab und // ermöglicht die XML-Serialisierung [XmlInclude(typeof(XmlAuto))] public class FahrzeugList : ArrayList { } be Die Collection FahrzeugList ist eine ArrayList und unterstützt XML. Sie wird später die Objekte vom Typ XmlAuto enthalten. Sichern Um den Inhalt einer Collection serialisieren zu können, binden wir den Datentyp XmlAuto mittels XmlInclude in die Collection ein. Die neue Collection heißt FahrzeugList und erbt von ArrayList. Die XML-Datei wird erstellt. Mittels Serialize werden alle Objekte der Collection in die XML-Datei geschrieben. ro Die Collection wird vom Typ FahrzeugList übergeben. // XML-Datei speichern in der Klasse Datenzugriff public static void Fahrzeug_XML_Export(FahrzeugList fz) { XmlSerializer xs = new XmlSerializer(typeof(FahrzeugList)); string pfad = @".\"; string dateiname = "Transportkosten.xml"; XmlTextWriter tw = new XmlTextWriter(pfad + dateiname, null); tw.Formatting = Formatting.Indented; xs.Serialize(tw, fz); tw.Flush(); tw.Close(); } sep Das Fallbeispiel Fuhrpark_XML findest du unter der ID: 1194. L 5: In der Klasse Datenzugriff wird die statische Methode Fahrzeug_XML_Export() ergänzt, der als Parameter eine Collection vom Typ FahrzeugList übergeben wird. Le Der Aufruf der statischen Methode Fahrzeug_XML_Export() erfolgt in einem Eventhandler. Zuvor muss das Array vom Typ IFahrzeug in die Collection FahrzeugList kopiert werden, da das Interface IFahrzeug bzw. die darin enthaltenen Objekte der Typen Bahn, PKW und LKW die Voraussetzungen für die XML-Serialisierung nicht erfüllen, z.B. öffentliche Eigenschaften. L 6: Für den XML-Export wird ein Button mit einem Eventhandler erstellt, der die Methode Fahrzeug_XML_Export() aufruft. Das Fallbeispiel Fuhrpark_XML findest du unter der ID: 1194. Die Bahn-, PKW- und LKW-Objekte werden in Objekte vom Typ XmlAuto kopiert und in einer Collection vom Typ FahrzeugList abgelegt. 108 // Eventhandler private void btXMLExport_Click(object sender, EventArgs e) { FahrzeugList fzlist = new FahrzeugList(); foreach(IFahrzeug fz in fahrzeug) { XmlAuto auto = new XmlAuto(); auto.Bezeichnung = fz.Bezeichnung; auto.Kosten = fz.Kosten; fzlist.Add(auto); } Datenzugriff.Fahrzeug_XML_Export(fzlist); } Softwareentwicklung Lerneinheit 4: Datenspeicherung Wie kann ich mir den Inhalt von XML-Dateien ansehen? Wenn du auf eine XML-Datei doppelklickst, öffnet sich automatisch der Internet Explorer bzw. ein anderer XML-fähiger Editor. Auch in Visual Studio 2005 findest du einen XML-Editor, mit dem du die Inhalte der XML-Dateien anzeigen kannst. Da wir bereits eine XML-Datei abgespeichert haben, soll nun eine Methode zum Einlesen und Darstellen der gespeicherten Daten erstellt werden. Das Ergebnis des XMLImports wird in der Listbox angezeigt. Mr. What und Ms. Check be ro sep Die Methode liefert eine Collection mit den eingelesenen Fahrzeugobjekten zurück. // XML-Datei einlesen in der Klasse Datenzugriff public static FahrzeugList Fahrzeug_XML_Import() { XmlSerializer xs = new XmlSerializer(typeof(FahrzeugList)); string pfad = @".\"; string dateiname = "Transportkosten.xml"; XmlTextReader tr = new XmlTextReader(pfad + dateiname); FahrzeugList fz = (FahrzeugList) xs.Deserialize(tr); tr.Close(); return fz; } // Eventhandler private void btXMLImport_Click(object sender, EventArgs e) { listFuhrparkFahrtkosten.Items.Clear(); foreach (XmlAuto auto in Datenzugriff.Fahrzeug_XML_Import()) { listFuhrparkFahrtkosten.Items.Add( auto.Bezeichnung + ": " + auto.Kosten.ToString("C")); } } Le Das Fallbeispiel Fuhrpark_XML findest du unter der ID: 1194. L 7: In der Klasse Datenzugriff wird die statische Methode Fahrzeug_XML_Import() ergänzt, die als Rückgabewert eine Collection vom Typ FahrzeugList liefert. Wofür werden XML-Dateien in der Praxis verwendet? Das XML-Format ist ein ideales Datenaustauschformat, z.B. wenn du Objektdaten über das Internet übertragen möchtest. Zum Abspeichern und Verwalten großer Datenmengen eignen sich relationale Datenbanken besser. Nachdem wir das Abspeichern von Text- und XML-Dateien kennengelernt haben, beschäftigen wir uns nun mit dem Zugriff auf relationale Datenbanken, wie z.B. Microsoft Access und Microsoft SQL-Server. Diese Form der Datenspeicherung stellt für wirtschaftliche Anwendungen, z.B. eine Kundenverwaltung, ein Auftragssystem oder einen Webshop, die ideale Lösung dar. Softwareentwicklung 109 2 Objektorientiertes Programmieren Mr. What und Ms. Check Lernen Üben Sichern Wissen 5 Datenbankzugriff mit ADO.NET Verwendung relationaler Datenbanken Das .NET-Framework liefert mit ADO.NET eine umfassende Technologie für die Arbeit mit verschiedenen Datenquellen. Am häufigsten wird der Zugriff auf relationale Datenbanken verwendet, wie z.B. Microsoft Access und Microsoft SQL-Server. Im Rahmen des Faches Wirtschaftsinformatik wurden relationale Datenbanken anhand von Microsoft Access sowie die Verwendung von SQL-Befehlen bereits behandelt. Wir konzentrieren uns daher zunächst auf die Verwendung von Tabellen und Abfragen aus einer Access-Datenbank. Später werden wir auch auf SQL-Server-Datenbanken zugreifen. Die folgende Abbildung gibt einen Überblick über den Aufbau der ADO.NET-Klassen. be ADO.NET-Datenbankzugriff DataSet Read DataView DataTable ro DataRelation Write Read-only DataProvider DataReader sep DataAdapter Command Connection Diese Abbildung findest du in der PowerPointPräsentation unter der ID: 1194. Abb.: Aufbau von ADO.NET Le Datenbankzugriff 1 Die Verbindung zu einer Access-Datenbank erfolgt über ein Connection-Objekt, das aus der Klasse OleDbConnection instanziert wird. Bei der Instanzierung des Connection-Objektes wird im Konstruktor ein ConnectionString angegeben, der sich aus folgenden Bestandteilen zusammensetzt: Provider=Microsoft.Jet.OLEDB.4.0 für Microsoft Access 2003 Data Source=Datenbankname.mdb User=Admin Password=pw Die Angabe von User und Passwort ist nur erforderlich, wenn die Datenbank geschützt ist. Die Verbindung zur Datenbank wird über die Methode Open() geöffnet und über die Methode Close() geschlossen. 2 Über ein Command-Objekt wird ein SQL-Befehl ausgeführt. Der SQL-Befehl sowie das bereits erzeugte Connection-Objekt werden im Konstruktor des Command-Objektes angegeben. Das Command-Objekt bietet über die Methode ExecuteReader() die Ausführung von Select und über ExecuteNonQuery() die Ausführung einer Aktionsabfrage an, z.B. Insert, Update, Delete. 110 Softwareentwicklung Lerneinheit 4: Datenspeicherung 3 Ein DataReader-Objekt repräsentiert das Ergebnis einer Select-Abfrage. Es ermöglicht nur Lesezugriffe. 4 Eine Alternative zum DataReader stellt der DataAdapter dar. Er dient dem Befüllen eines DataSets und unterstützt Lese- und Schreibzugriffe. Mit Hilfe der Fill()-Methode des DataAdapter-Objektes wird der Inhalt einer Select-Abfrage in ein DataSet-Objekt kopiert. Die Veränderungen im DataSet werden über die Methode Update() des DataAdapters in die Datenbank zurückgeschrieben. Klasse Beschreibung OleDbConnection Stellt eine Verbindung zur Datenbank her OleDbCommand Führt einen SQL-Befehl aus OleDbDataReader Direkter Lesezugriff auf die Datenbank OleDbDataAdapter Füllt ein DataSet mit dem Ergebnis eines SQL-Befehls DataSet Enthält eine oder mehrere Tabellen mit Beziehungen ro Die Klassen für den Zugriff auf einen SQL-Server heißen SQLConnection, SQLCommand, SQLDataReader und SQLDataAdapter. be Die Tabelle fasst nochmals die wichtigsten Klassen für den Datenbankzugriff zusammen: Auswahlabfragen mit DataReader sep Das folgende Lehrbeispiel zeigt die Verwendung des DataReader-Providers zum Einlesen der Fahrzeuge aus einer Access-Datenbank. Die Methoden für den Datenbankzugriff erstellen wir in der bereits vorhandenen Klasse Datenzugriff. L 8: Einlesen der Tabelle T_Auto aus der Access-Datenbank Fuhrpark.mdb. Die statische Methode FahrzeugDB_Lesen() befüllt das Fahrzeug-Array im Hauptprogramm. Erzeugen des Connection-Objektes mit dem ConnectionString für eine AccessDatenbank SQL-Befehl an das Command-Objekt übergeben Das DataReaderObjekt dr enthält die Datensätze der Tabelle T_Auto. Softwareentwicklung using System.Data.OleDb; ... class Datenzugriff { ... // Access-Datenbank einlesen public static void FahrzeugDB_Lesen(ref IFahrzeug[] fahrzeug) { // Fahrzeug-Collection ArrayList fz = new ArrayList(); fz.Add(new Bahn()); Le Das Fallbeispiel Fuhrpark_DB findest du unter der ID: 1194. // Connection-Objekt string dbname = "Fuhrpark.mdb"; OleDbConnection conn = new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;" + "Data Source=" + dbname + ";"); conn.Open(); // Command-Objekt OleDbCommand cmd = new OleDbCommand("SELECT * FROM T_Auto", conn); // DataReader-Objekt OleDbDataReader dr = cmd.ExecuteReader(); Auswahlabfrage 111 2 Objektorientiertes Programmieren Das Ausführen der ExecuteReader()-Methode des Command-Objektes liefert als Ergebnis das fertige DataReader-Objekt zurück. Ein Konstruktoraufruf ist nicht erforderlich. Mit Hilfe einer while-Schleife iterieren wir durch die Datensätze. Der PKW- bzw. LKWKonstruktor erzeugt die Objekte. Die Close()-Methoden geben die reservierten Ressourcen wieder frei. Bereitstellen der Objekte im FahrzeugArray. Wissen while (dr.Read()) { string kennzeichen = dr["Kennzeichen"].ToString(); string marke = dr["Marke"].ToString(); double verbrauch = Convert.ToDouble(dr["Verbrauch"]); double tankinhalt = Convert.ToDouble(dr["Tankinhalt"]); if (dr["Fahrzeugtyp"].ToString() == "Pkw") { Motor motor; if (dr["Motor"].ToString() == "B") motor = Motor.Benzin; else motor = Motor.Diesel; bool vignette = Convert.ToBoolean(dr["Vignette"]); fz.Add( new Pkw(kennzeichen, marke, verbrauch, tankinhalt, motor, vignette) ); } else { byte achszahl = Convert.ToByte(dr["Achszahl"]); fz.Add( new Lkw(kennzeichen, marke, verbrauch, tankinhalt, achszahl) ); } } be Mit jedem Schleifendurchlauf wird in dr ein anderer Datensatz repräsentiert. Sichern // Alles schließen dr.Close(); conn.Close(); ro Üben // Arraylist in das Array fahrzeug umkopieren fahrzeug = new IFahrzeug[fz.Count]; fz.CopyTo(fahrzeug); } } sep Lernen Beachte Le Die Methode FahrzeugDB_Lesen() ersetzt die Methode Fahrzeugdatei_Lesen(). Das Ergebnis beider Methoden ist das Befüllen des Fahrzeug-Arrays mit den PKW- und LKW-Objekten. Die Daten werden aus der Access-Tabelle T_Auto der Datenbank Fuhrpark.mdb eingelesen. Die Datentypen der Datenbank sind mit den .NET-Datentypen nicht kompatibel und müssen konvertiert werden. Aktionsabfragen mit ExecuteNonQuery() Der Zugriff über DataReader erlaubt nur die Ausführung von Select-Befehlen. Mit ExecuteNonQuery können auch Aktionsabfragen, z.B. Insert, Update und Delete, durchgeführt werden. Häufig werden zur Ausführung von Abfragen Parameter benötigt. Die sichere Übergabe von Parametern erfolgt über Parameter-Objekte, die aus der Klasse OleDbParameter instanziert werden. Durch die Verwendung von Parametern erreichen wir zwei wichtige Ziele: Das sichere Übertragen von .NET-Datentypen in die Datentypen der Datenbank. Das Verhindern von benutzerseitig eingefügten bösartigen SQL-Befehlen, sogenannten SQL-Injections. Auf diesen Punkt werden wir im Rahmen von ASP.NET genauer eingehen. Das folgende Lehrbeispiel zeigt die Verwendung einer Insert-Aktionsabfrage mit Parametern zur Übergabe der Objekt-Eigenschaften Bezeichnung und Kosten. 112 Softwareentwicklung Lerneinheit 4: Datenspeicherung L 8: Anfügen der Transportkosten an die Tabelle T_Transportkosten in der Datenbank. SQL-Befehl mit Parameternamen an das Command-Objekt übergeben // Transportkosten in der Datenbank speichern public static void TransportkostenDB_Schreiben(ref IFahrzeug[] fahrzeug) { // Connection-Objekt string dbname = "Fuhrpark.mdb"; OleDbConnection conn = new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;" + "Data Source=" + dbname + ";"); conn.Open(); foreach (IFahrzeug fz in fahrzeug) { // Command-Objekt OleDbCommand cmd = new OleDbCommand("INSERT INTO T_Transportkosten " + "([Bezeichnung], [Kosten]) " + "VALUES (@bez, @kost)", conn); 2 Objektorientiertes Programmieren Erzeugen des Connection-Objektes mit dem ConnectionString für eine AccessDatenbank. using System.Data.OleDb; be Das Fallbeispiel Fuhrpark_DB findest du unter der ID: 1194. OleDbParameter kost = cmd.Parameters.Add("@kost", OleDbType.Double); kost.Value = fz.Kosten; sep Das DataReaderObjekt dr enthält die Datensätze der Tabelle T_Auto. ro // Parameter Parameter OleDbParameter bez = cmd.Parameters.Add("@bez", OleDbType.VarChar, 50); bez.Value = fz.Bezeichnung; // SQL-Befehl ausführen cmd.ExecuteNonQuery(); } Aktionsabfrage // Alles schließen conn.Close(); } Mr. What und Ms. Check Le Die beiden Parameterobjekte bez und kost sorgen für eine sichere Übergabe der .NET-Datentypen String und Double an die Datenbank. Was bedeutet VarChar beim Bezeichnung-Parameter? In einer Datenbank gibt es andere Datentypen als im .NET-Framework. Dem .NET-Datentyp String entsprechen die DatenbankDatentypen Char bzw. VarChar. Bei Char wird der übergebene Text auf die definierte Länge mit Leerzeichen aufgefüllt, VarChar belegt nur den tatsächlich benötigten Speicherplatz. n-Tier-Architektur Wie wir bereits wissen, gibt es für den Datenbankzugriff über ADO.NET zwei Techniken: Die Verwendung eines Command-Objektes mit der Methode ExecuteNonQuery() bzw. das Einlesen in ein DataReader-Objekt sowie die Verwendung eines Command-Objektes im Zusammenspiel mit einem DataAdapter und einem DataSet. Die Three-Tier-Architektur wird zur n-Tier-Architektur erweitert. Softwareentwicklung 113 Lernen Üben Sichern Wissen Sowohl das DataReader- als auch das DataSet-Objekt repräsentieren das Ergebnis der Datenabfrage, losgelöst von der Datenbank im Hauptspeicher, in Form des Data Access Tiers. Die Business-Logik der Anwendung ist im Fahrzeug-Array gekapselt. Das Einlesen und Abspeichern der Fahrzeug-Objekte erfolgt über den Data Access Tier, die Interaktion mit dem Benutzer über den Presentation Tier, z.B. ein Windows-Formular. Im Data Tier werden die Daten persistent abgelegt (Datenbank). Presentation Tier DataReader DataAdapter Command Connection Connection Diese Abbildung findest du in der PowerPointPräsentation unter der ID: 1194. Data Tier sep Datenbank Data Access Tier ro Command Business Logic Tier be DataSet Abb.: n-Tier-Architektur Das DataReader-Objekt verwendet zum Einlesen der Daten einen serverseitigen Cursor, der nur unidirektionale Lesezugriffe erlaubt (nur vorwärts). Die Veränderung der Daten ist nicht möglich. Datenbankzugriff mit DataAdapter und DataSet Le Ein DataSet-Objekt kann eine Tabelle enthalten oder mehrere, die miteinander in Beziehung stehen. Das DataSet ist von den Daten in der Datenbank entkoppelt. Die Verbindung zur Datenbank wird über ein DataAdapter-Objekt hergestellt. Die folgende Tabelle fasst die wichtigsten Klassen im Zusammenhang mit DataSets zusammen: Klasse Beschreibung DataSet Container für Tabellen und Beziehungen DataView Sicht auf eine DataTable, z.B. zum Sortieren und Suchen DataTable Datentabelle, die aus Zeilen und Spalten besteht DataRow Zeile einer DataTable (Datensatz) DataColumn Spalte einer DataTable (Feld) DataRelation Beziehung zwischen DataTables Constraint Einschränkung innerhalb einer DataTable Die Methode Fahrzeug_DataSetLesen() in der Klasse Datenzugriff im Lehrbeispiel 10 implementiert das Einlesen der Fahrzeuge aus der Access-Datenbank mit Hilfe eines DataSets. 114 Softwareentwicklung Lerneinheit 4: Datenspeicherung Das Fallbeispiel Fuhrpark_DB findest du unter der ID: 1194. using System.Data.OleDb; using System.Data; ... // Access-Datenbank in DataSet einlesen public static void Fahrzeug_DataSetLesen(ref IFahrzeug[] fahrzeug) { // Fahrzeug-Collection ArrayList fz = new ArrayList(); fz.Add(new Bahn()); be // Connection-Objekt string dbname = "Fuhrpark.mdb"; Connection OleDbConnection conn = new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;" + "Data Source=" + dbname + ";"); conn.Open(); Das DataSet steht nach dem Schließen der Connection weiterhin zur Verfügung. Der Zugriff auf das DataSet erfolgt über die DataTable Fahrzeug und die darin enthaltenen DataRows. // Connection schließen conn.Close(); DataAdapter DataSet sep Die Fill-Methode des DataAdapters befüllt das DataSet mit den eingelesenen Daten. // DataSet befüllen OleDbDataAdapter da = new OleDbDataAdapter(cmd); DataSet ds = new DataSet(); da.Fill(ds, "Fahrzeug"); // DataTable aus DataSet erzeugen DataTable dt = ds.Tables["Fahrzeug"]; foreach(DataRow dr in dt.Rows) { string kennzeichen = dr["Kennzeichen"].ToString(); string marke = dr["Marke"].ToString(); double verbrauch = Convert.ToDouble(dr["Verbrauch"]); double tankinhalt = Convert.ToDouble(dr["Tankinhalt"]); if (dr["Fahrzeugtyp"].ToString() == "Pkw") { Motor motor; if (dr["Motor"].ToString() == "B") motor = Motor.Benzin; else motor = Motor.Diesel; bool vignette = Convert.ToBoolean(dr["Vignette"]); fz.Add(new Pkw(kennzeichen, marke, verbrauch, tankinhalt, motor, vignette)); } else { byte achszahl = Convert.ToByte(dr["Achszahl"]); fz.Add(new Lkw(kennzeichen, marke, verbrauch, tankinhalt, achszahl)); } } Le Der DataAdapter stellt die Verbindung zum Command-Objekt her. ro // Command-Objekt Command OleDbCommand cmd = new OleDbCommand("SELECT * FROM T_Auto", conn); // Arraylist in das Array fahrzeug umkopieren fahrzeug = new IFahrzeug[fz.Count]; fz.CopyTo(fahrzeug); } Softwareentwicklung 115 2 Objektorientiertes Programmieren L 10: Verwendung eines DataSets zum Einlesen der Fahrzeuge aus der Access-Datenbank. Lernen Üben Beachte Sichern Wissen Die Fill-Methode des DataAdapters ermöglicht das Lesen aus der Datenbank, die Update-Methode das Aktualisieren (Zurückschreiben) der Daten. L 11: Der folgende Programmcode demonstriert die Aktualisierung des DataSets mit Hilfe der Update-Methode des DataAdapters. be public static void DataSetDemo() { // Connection-Objekt string dbname = "Fuhrpark.mdb"; OleDbConnection conn = new OleDbConnection("Provider=Microsoft.Jet.OLEDB.4.0;" + "Data Source=" + dbname + ";"); conn.Open(); // Command-Objekt zum Einlesen OleDbCommand cmd = new OleDbCommand("SELECT * FROM T_AutoKopie", conn); Die Werte in der DataTable werden einfach überschrieben. Die Delete-Methode löscht den aktuellen Datensatz aus der DataTable. Das CommandBuilderObjekt erstellt die erforderlichen SQL-Befehle für die Aktualisierung, die Update-Methode führt sie aus. 116 // Neues Auto anfügen DataRow new_dr = dt.NewRow(); new_dr["Kennzeichen"] = "KS-123A"; new_dr["Fahrzeugtyp"] = "Pkw"; new_dr["Marke"] = "Volvo V50"; new_dr["Verbrauch"] = 6.5; new_dr["Tankinhalt"] = 65; new_dr["Motor"] = "D"; new_dr["Vignette"] = false; dt.Rows.Add(new_dr); sep Neuer Datensatz // Den Verbrauch aller Dieselfahrzeuge um 10 % reduzieren foreach (DataRow dr in dt.Rows) { double verbrauch = Convert.ToDouble(dr["Verbrauch"]); if (dr["Motor"].ToString() == "D") Datensatz aktualisieren dr["Verbrauch"] = verbrauch / 1.1; } Le Mit der Methode NewRow() wird ein neuer Datensatz in der DataTable erstellt. ro // DataSet befüllen OleDbDataAdapter da = new OleDbDataAdapter(cmd); DataSet ds = new DataSet(); da.Fill(ds, "Fahrzeug"); DataTable dt = ds.Tables["Fahrzeug"]; // Maserati löschen foreach (DataRow dr in dt.Rows) { if (dr["Marke"].ToString() == "Maserati") dr.Delete(); } Datensatz löschen // Aktualisierungsbefehle automatisch erzeugen OleDbCommandBuilder cb = new OleDbCommandBuilder(da); // Aktualisierung durchführen da.Update(dt); // Connection schließen conn.Close(); } Softwareentwicklung Lerneinheit 4: Datenspeicherung Wie kann ich die Tabelle einer Datenbank ohne Programmierung in einem Windows-Formular darstellen? Über die Toolbox fügst du ein DataGridView-Control in das Windows-Formular ein und konfigurierst dafür einen Datenbankzugriff. Das Control erledigt alle Aufgaben, wie z.B. Datensätze anzeigen, sortieren, anfügen, ändern oder löschen, vollautomatisch und ohne eine einzige Zeile Programmcode. 6 DataGridView-Control be Codeless-Datenbankanbindung DataGridView-Control in der Toolbox Diese Abbildung findest du in der PowerPointPräsentation unter der ID: 1194. sep DataGridView-Control ro Das DataGridView-Control ermöglicht die Datenbankanbindung ohne das Schreiben von Programmcode. Abb.: DataGridView-Control Le DataGridView einrichten Abb.: Datenquelle auswählen 1 Das DataGridView-Control wird aus der Toolbox in ein Formular gezogen. Über Choose Data Source wird eine Datenquelle ausgewählt. Als Datenquelle wählen wir unter Add Project Data Source die Option Database. 2 Über die Schaltfläche New Connection wird die Verbindung zur gewünschten Datenbank hergestellt. Der Connection-String wird automatisch erzeugt und kann im Dialog angezeigt werden. Connection-String Abb.: Verbindung zur Access-Datenbank herstellen Softwareentwicklung 117 2 Objektorientiertes Programmieren Mr. What und Ms. Check Lernen Üben Sichern Wissen 3 Der Connection-String wird in den Project-Settings gespeichert. Der Pfad für die Datenbank kann dort jederzeit geändert werden. be Über das Menü Project | Settings kann der Connection-String im Register Settings angepasst werden. Abb.: Connection-String speichern 4 In einem DataGridView-Control können Tabellen oder Abfragen dargestellt werden. Die Auswahl der Felder erfolgt durch Aktivieren der Checkboxes. sep ro Die Spaltenbreite der Felder kann über das Kontextmenü des Controls eingestellt werden. Abb.: Auswahl der darzustellenden Daten Der Datenbankpfad wird im Connection-String angegeben und kann jederzeit über die Project-Settings verändert werden. Le Beachte Abb.: Project-Settings Durch die Festlegung der Datenbank in den Project-Settings ist eine Änderung der Datenquelle jederzeit problemlos durchführbar, z.B. die Verwendung eines SQL-Servers statt einer Access-Datenbank. 118 Softwareentwicklung 2 Objektorientiertes Programmieren Lerneinheit 4: Datenspeicherung Abb.: DataGridView-Control mit Daten be Kann ich das DataGridViewControl auch für Web-Anwendungen verwenden? Im Kapitel zu ASP.NET wirst du ein ähnliches Control für WebAnwendungen kennenlernen. ro Mr. What und Ms. Check Übungsbeispiele sep Üben Ü 2: Erstelle eine Textdatei mit folgendem Inhalt: OS101;London;2100;80 OS456;Paris;1700;65 OS898;New York;11200;127 OS234;Los Angeles;18900;116 a) Erstelle die Klasse Flugziel mit den Eigenschaften Flugnummer, Zielort, Flugstrecke und Passagieranzahl. Le b) Erstelle eine Datenzugriffsklasse, mit der du die Daten aus der Textdatei in ein Flugziel-Array einlesen kannst. c) Stelle die Daten aus dem Flugziel-Array in einer ListBox dar. d) Berücksichtige den Rückflug und verdopple dafür die Flugstrecke jedes Objektes im Flugziel-Array. e) Serialisiere alle Objektdaten in eine XML-Datei. Ü 3: Erstelle in einer Access-Datenbank die folgende Tabelle T_Flugziel: Flugnummer Zielort Flugstrecke Passagieranzahl OS101 OS456 London 2100 80 Paris 1700 65 PS898 New York 11200 127 OS234 Los Angeles 18900 116 Erstelle eine Windows-Anwendung und zeige die Flugziele mit allen Daten aus der AccessTabelle in einem DataGridView-Control an. Softwareentwicklung 119 Lernen Üben Sichern Wissen Ü 4: Verwende die Datenbank mit der Tabelle T_Flugziel aus dem Übungsbeispiel Ü 3. Erstelle eine Windows-Anwendung und zeige alle Langstreckenflüge (Flugstrecke > 10000) aus der Access-Tabelle in einer ListBox an. Verwende zur Lösung der Aufgabe einen DataReader. Ü 5: Verwende die Datenbank mit der Tabelle T_Flugziel aus dem Übungsbeispiel Ü 3. Erhöhe bei den Flügen mit mehr als 100 Passagieren die Passagieranzahl um zwei. Verwende für diese Aufgabe ein DataSet. Gib die veränderten Daten in einer ListBox aus. Zusätzlich zu diesen Übungen findest du in SbX eine Internetaufgabe. be ID: 1195 Sichern ro In dieser Lerneinheit haben wir den Zugriff auf Textdateien, die Serialisierung von Objekten in XML-Dateien sowie den Datenbankzugriff kennengelernt: Bei der Erstellung von Anwendungen werden die Klassen der Präsentationsschicht (Presentation Tier), der Geschäftslogik (Business Logic Tier, Middle Tier) sowie der Datenschicht (Data Tier) voneinander getrennt. Textdateien Der Zugriff auf Textdateien erfolgt mit den Klassen StreamReader und StreamWriter. sep 3-SchichtenArchitektur Das .NET-Framework bietet die Klassen Directory, DirectoryInfo und File für diverse Dateisystemoperationen an. XML-Serialisierung Unter XML-Serialisierung wird das Speichern bzw. Einlesen von Objekten der Geschäftslogik verstanden, wobei als Datenformat XML verwendet wird. ADO.NET Für einen Datenbankzugriff werden zunächst ein Connection- sowie ein Command-Objekt instanziert. Die Daten werden entweder in einem DataReader-Objekt oder mit Hilfe eines DataAdapters in einem DataSet repräsentiert. n-SchichtenArchitektur Von einer vielschichtigen Architektur spricht man, wenn die Geschäftslogik in eine weitere Klasse für den Datenzugriff (Data Access Tier) aufgeteilt wird. DataGridViewControl Mit dem DataGridView-Control wird der Inhalt einer Tabelle oder Abfrage in einer Windows-Anwendung dargestellt. Le DateisystemOperationen Zusätzlich zu dieser Zusammenfassung findest du in SbX eine Bildschirmpräsentation. ID: 1196 Wissen Wiederholungsfragen und -aufgaben 1.Beschreibe die Schichten der Drei-Schichten-Architektur! 2.Mit welchen Klassen erfolgt das Lesen- bzw. Schreiben von Text- bzw. XML-Dateien? 120 Softwareentwicklung Lerneinheit 4: Datenspeicherung 3.Welche Klassen werden für Dateisystemoperationen verwendet? 4.Was bedeutet XML-Serialisierung? 5.Erkläre den Aufbau von ADO.NET! 7.Worin unterscheiden sich die Drei-Schichten- und die n-Schichten-Architektur? 8.Wofür eignet sich ein DataGridView-Control? be 9.Wie kann bei der Verwendung der Methode ExecuteNonQuery() die Ausführung bösartiger SQL-Befehle, sogenannter SQL-Injections, verhindert werden? 10.Wo kann bei Verwendung eines DataGridView-Controls der Datenbankpfad geändert werden? 11.Welche Informationen sind in einem Connection-String enthalten? Lerncheck Ich kann jetzt … ro Zusätzlich zu diesen Aufgaben findest du in SbX ein Quiz. ID: 1197 sep w ... Objektdaten der Geschäftslogik in der Datenschicht als Text- oder XML-Datei persistent abspeichern. w ... mit Hilfe von ADO.NET auf Datenbanken zugreifen und Tabellen sowie Abfragen verwenden. w ... Dateisystemoperationen in meinen Programmen anwenden. Le w ... mit einem DataGridView-Control Datenobjekte präsentieren. Softwareentwicklung 121 2 Objektorientiertes Programmieren 6.Erkläre die Unterschiede zwischen einem Datenbankzugriff über ein DataReader- bzw. ein DataSet-Objekt! Lernen Üben Sichern Wissen Kapitelrückblick In diesem Kapitel haben wir die Grundlagen der objektorientierten Programmierung sowie die Verwendung der ADO.NET-Klassen zum Speichern von Daten gelernt. Was genau sollte ich nun eigentlich wissen? be Anhand des Lernchecks hier am Kapitelende kannst du leicht überprüfen, ob du wirklich alles verstanden hast! Lerncheck ro Ich kann jetzt … w ... Klassen und deren Bestandteile verwenden, um Objekte zu instanzieren. w ... Konstruktoren überladen sowie statische Eigenschaften und statische Methoden richtig einsetzen. w ... die Bestandteile einer Klasse in Subklassen vererben und Konstruktormethoden der Basisklasse wiederverwenden. sep w ... virtuelle Methoden erstellen, in den Subklassen überschreiben sowie das späte Binden einsetzen. w ... gegen Klassen und gegen Interfaces programmieren und meine Klassen durch Mehrfachvererbung flexibler gestalten. w ... Objektdaten der Geschäftslogik in der Datenschicht als Text- oder XML-Datei persistent abspeichern sowie mit Hilfe von ADO.NET auf Datenbanken zugreifen. w ... Dateisystemoperationen in meinen Programmen anwenden. w ... mit einem DataGridView-Control Datenobjekte präsentieren. Le Zusätzlich findest du in SbX eine Bildschirmpräsentation zu diesem Kapitel. ID: 1198 Im nächsten Kapitel beschäftigen wir uns mit der Planung und Modellierung von Programmen und deren Daten mittels verschiedener UML-Diagramme. 122 Softwareentwicklung