PPM –5AHWII 2010/11 Maturafragen 1. Typen in C# Erkläre Wert und Referenztypen und deren Unterschied: Werttypen sind einfache Typen(bool, int, char, …), Enums, Structs. Referenztypen sind Klassen, Interfaces, Arrays, Delegates. Unterschiede: Werttypen Variable enthält Wert Gespeichert am Stack Initialisierung 0, false, ‚‘\0‘ Zuweisung Kopiert Wert Beispiel int i = 17; int j = i; i j 17 17 Referenztypen Zeiger auf ein Objekt Heap null Kopiert Zeiger string s = „Hello“; string s1 = s; s s1 Hello Erkläre Enumerationen und deren Verwendung: Eine Enumeration ist ein eigener Typ, der aus einer Gruppe von bekannten Konstanten besteht. Deklaration(auf Namespace-Ebene) enum access { personal=1, group=2, all=3, } Verwendung: access acc = access.all; if(acc == access.all) { ... } Man kann die Enumerationen auch nach der Nummer ansprechen: int i = (int)access.personal; Wie werden Arrays und Strings definiert und verwendet: Eindimensionale Arrays o int[] a = new int[3]; o int[] b = new int[]{3,4,5}; o int[] c = {3,4,5}; Mehrdimensionale Arrays(„ausgefranst“) o int[][] a = new int[2][]; a[0] = new int[3]; a[1] = new int[4]; int b = a[1][0]; Mehrdimensionale Blockarrays(rechteckig) o int[,] a = new int[2, 3]; int[,]b = {{1, 2, 3}, {4, 5, 6}}; int[,,] c = new int[2, 4, 2]; o Kompakterer, effizienterer Zugriff // Block-Matrix // Können direkt initialisiert werden Indizierung von Arrays beginnt bei 0. Die Länge eines Arrays kann über die Length-Funktion abgefragt werden. Z.B.: int[] a = new int[3] Console.WriteLine(a.Length); int[,] b = new int[3,4]; Console.WriteLine("{0}, {1}", c.GetLength(0), c.GetLength(1)); // 3, 4 Variabel lange Arrays: ArrayList: o using System; using System.Collections; class Test { static void Main() { ArrayList a = new ArrayList(); a.Add("Caesar"); a.Add("Dora"); a.Add("Anton"); a.Sort(); for (int i = 0; i < a.Count; i++) Console.WriteLine(Convert.ToString(a[i])); } } o In eine ArrayList können alle Typen gespeichert werden, vor der Verwendung müssen Elemente allerdings zum richtigen Datentyp konvertiert werden. Assoziative Arrays: using System; using System.Collections; class Test { static void Main() { Hashtable phone = new Hashtable(); phone["Karin"] = 7131; phone["Peter"] = 7130; phone["Wolfgang"] = 7132; foreach (DictionaryEntry x in phone) Console.WriteLine("{0} = {1}", x.Key, x.Value); } } Ausgabe: Karin = 7131 Peter = 7130 Wolfgang = 7132 Erkläre die Begriffe Boxing und Unboxing! Alle Typen sind zu object kompatibel! Boxing o Bei der Zuweisung object obj = 3; o wird der Wert 3 in ein Heap-Objekt eingepackt: o obj 3 Unboxing o Mit o int a = (int) obj; wird der Wert wieder ausgepackt. 2. Anweisungen in C# Verzweigungen Das Ändern der Ablaufsteuerung in einem Programm als Reaktion auf eine Eingabe oder ein Rechenergebnis ist ein wesentlicher Teil einer Programmiersprache. In C# gibt es die Möglichkeit, die Ablaufsteuerung entweder nicht bedingt zu ändern (durch Springen zu einer anderen Stelle im Code) oder bedingt (durch Prüfen einer Bedingung). If-Anweisung Die einfachste Form der bedingten Verzweigung ist die Verwendung des if-Konstrukts. Mit dem if-Konstrukt können Sie eine else-Klausel verwenden, und if-Konstrukte können ineinander geschachtelt werden. if(Bedingung) { Anweisungsblock; } else { Anweisungsblock; } Switch-Anweisung Eine switch-Anweisung kann je nach Wert eines bestimmten Ausdrucks mehrere Aktionen ausführen. Der Code zwischen der case-Anweisung und dem break-Schlüsselwort wird ausgeführt, wenn die Bedingung erfüllt ist. Wenn die Ablaufsteuerung zu einer weiteren case-Anweisung fortfahren soll, verwenden Sie das goto-Schlüsselwort. switch(Ausdruck) { case Vergleichsausdruck1: Anweisungsblock; break; case Vergleichsausdruck2: Anweisungsblock; break; default: Anweisungsblock; break; } Schleifen Eine Schleife ist eine Anweisung oder eine Gruppe von Anweisungen, die mit einer bestimmten Häufigkeit oder bis zur Erfüllung einer bestimmten Bedingung wiederholt wird. Welchen Schleifentyp Sie auswählen, hängt von der Programmieraufgabe und dem persönlichen Programmierstil ab. Ein wesentlicher Unterschied zwischen C# und anderen Sprachen, z. B. C++, ist die foreach-Schleife. Diese wurde entwickelt, um das Durchlaufen von Arrays und Auflistungen zu vereinfachen. For-Schleife Mit der for-Schleife wird eine Anweisung oder ein Anweisungsblock wiederholt ausgeführt, bis ein bestimmter Ausdruck den Wert false liefert. Die for-Schleife eignet sich zum Durchlaufen von Arrays und für die sequenzielle Verarbeitung. For(Initialisierungsausdruck;Abbruckbedingung;Aktualisierungsausdruck) { Anweisungsblock; } While-Schleife Mit der while-Anweisung wird eine Anweisung oder ein Anweisungsblock ausgeführt, bis ein bestimmter Ausdruck den Wert false liefert. Da der Test des while-Ausdrucks jedes Mal stattfindet, bevor die Schleife durchlaufen wird, wird eine while-Anweisung keinmal, einmal oder häufiger ausgeführt. Hierbei besteht ein Unterschied zur do-Schleife, die mindestens einmal ausgeführt wird. while(Bedingung) { Anweisungsblock; } Do-Schleife Mit der do-Anweisung wird eine Anweisung oder ein Anweisungsblock, eingeschlossen in {}, wiederholt ausgeführt, bis ein bestimmter Ausdruck den Wert false liefert. Im Gegensatz zur while-Anweisung wird eine do-while-Schleife einmal ausgeführt, bevor der bedingte Ausdruck ausgewertet wird. do { Anweisungsblock } while(Bedingung) foreach-Schleife foreach(Datentyp Variablenname in Arrayname) { Anweisungsblock; } Break-Anweisung Der Ausdruck break beendet eine Schleife. Mit der break-Anweisung wird die direkt umschließende Schleife oder die switch-Anweisung, in der sie auftritt, beendet. Die Steuerung wird an die Anweisung übergeben, die auf die beendete Anweisung folgt, falls vorhanden. Continue-Anweisung Durch den Ausdruck continue wird an den Schleifenanfang gesprungen. Mit der continue-Anweisung wird die Steuerung an die nächste Iteration der einschließenden Iterationsanweisung übergeben, in der sie auftritt. 3. Methoden in C# Eine Methode ist ein Codeblock, der eine Reihe von Anweisungen enthält. In C# werden alle Anweisungen im Kontext einer Methode ausgeführt. Wie werden Funktionen/Prozeduren definiert und aufgerufen? Methoden werden innerhalb einer Klasse oder einer Struktur deklariert, indem die Zugriffsebene, der Rückgabewert, der Name der Methode und die Methodenparameter angegeben werden. Methodenparameter sind von runden Klammern umgeben und durch Kommas getrennt. Leere Klammern geben an, dass die Methode keine Parameter benötigt. class Circle { private int rad; public void draw() { //Aufruf der Methode fill, mit Übergabe einer Farbe //als Parameter fill (Color.Red); //Aufruf einer statischen Methode (es muss kein Objekt //anglegt werden, um die Methode zu benutzen) Circle.drawCircle (rad, Color.Red, new Point (10, 10)); } private void fill(Color cl) { //…Code.. } public int getRadius() { return rad; } public static int drawCircle (int rad, Color cl, Point mp) { //…Code… } } Welche Arten von Parametern gibt es? Parameter werden an eine Methode übergeben, indem sie beim Aufruf der Methode einfach in Klammern bereitgestellt werden. Für die aufgerufene Methode werden die eingehenden Argumente als Parameter bezeichnet. Die von einer Methode empfangenen Parameter werden ebenfalls in einem Klammernpaar bereitgestellt, aber für jeden Parameter müssen der Typ und ein Name angegeben werden. Der Name muss nicht mit dem Argument übereinstimmen. Arten von Parameter Einteilung Wertetyp Wenn ein Werttyp an eine Methode übergeben wird, wird anstelle des Objekts standardmäßig eine Kopie übergeben. Da es sich um Kopien handelt, wirken sich Änderungen am Parameter nicht auf die aufrufende Methode aus. Die Bezeichnung Werttyp beruht auf eben dieser Tatsache, dass anstelle des Objekts selbst eine Kopie des Objekts übergeben wird. Der Wert wird übergeben, nicht das Objekt selbst. Verweistyp Wenn ein auf einem Verweistyp basierendes Objekt an eine Methode übergeben wird, wird keine Kopie des Objekts angelegt. Stattdessen wird ein Verweis auf das als Methodenargument verwendete Objekt erstellt und übergeben. Durch diesen Verweis vorgenommene Änderungen wirken sich daher auf die aufrufende Methode aus. Ein Verweistyp wird z.B mit dem class-Schlüsselwort erstellt. Spezielle Parameter „ref“ Schlüsselwort Das ref-Schlüsselwort bewirkt, dass Argumente als Verweis übergeben werden. Dies hat zur Folge, dass alle Änderungen am Methodenparameter in diese Variable übernommen werden, sobald die Steuerung wieder an die aufrufende Methode übergeben wird. Um einen ref-Parameter zu verwenden, müssen sowohl die Methodendefinition als auch die aufrufende Methode explizit das ref-Schlüsselwort verwenden. class RefExample { static void Method(ref int i) { i = 44; } static void Main() { int val = 0; Method(ref val); // val is now 44 } } „out“ Schlüsselwort Das out-Schlüsselwort bewirkt, dass Argumente als Verweis übergeben werden. Damit ähnelt es dem refSchlüsselwort, abgesehen davon, dass für ref die Variable initialisiert werden muss, bevor sie übergeben wird. Um einen out-Parameter zu verwenden, müssen sowohl die Methodendefinition als auch die aufrufende Methode explizit das out-Schlüsselwort verwenden. class OutExample { static void Method(out int i) { i = 44; } static void Main() { int value; Method(out value); // value is now 44 } } „params“ Schlüsselwort Mit dem params-Schlüsselwort kann ein Methodenparameter, der ein Argument enthält, angegeben werden, wobei die Anzahl der Argumente variabel ist. Nach dem params-Schlüsselwort sind keine zusätzlichen Parameter in einer Methodendeklaration zugelassen. Gleichzeitig ist nur ein paramsSchlüsselwort in einer Methodendeklaration zulässig. public class MyClass { public static void UseParams(params object[] list) { for (int i = 0 ; i < list.Length; i++) { Console.WriteLine(list[i]); } Console.WriteLine(); } static void Main() { UseParams(1, 2, 3); UseParams(1, 'a', "test"); } } Erkläre das Überladen von Methoden! Die Signatur eines Members umfasst den Namen und die Parameterliste des Members. Jede Membersignatur muss für den Typ eindeutig sein. Member können über den gleichen Namen verfügen, solange sich ihre Parameterlisten unterscheiden. Wenn es sich bei zwei oder mehr Membern eines Typs um Member derselben Art (Methode, Eigenschaft, Konstruktor usw.) handelt und diese denselben Namen, jedoch unterschiedliche Parameterlisten aufweisen, werden die Member als überladen bezeichnet. Diese Richtlinie weist zwei Einschränkungen auf: Wenn eine Überladung eine Variablenargumentliste akzeptiert, muss die Liste der letzte Parameter sein. Wenn die Überladung out-Parameter akzeptiert, müssen diese konventionsgemäß als letzte Parameter angegeben werden. public void Write(string message, FileStream stream){} public void Write(string message, FileStream stream, bool closeStream){} Alle unären und binären Operatoren verfügen über vordefinierte Implementierungen, die in allen Ausdrücken automatisch verfügbar sind. Zusätzlich zu den vordefinierten Implementierungen können durch Einfügen von operator-Deklarationen in Klassen und Strukturen benutzerdefinierte Implementierungen eingeführt werden. Benutzerdefinierte Operatorimplementierungen haben stets Vorrang gegenüber vordefinierten Operatorimplementierungen. Nur wenn keine benutzerdefinierte Operatorimplementierung vorhanden ist, wird die vordefinierte Operatorimplementierung verwendet. Überladbare unäre Operatoren: + - ! ~ ++ -- true false Überladbare binäre Operatoren: + - * / % & | ^ << >> == != > < >= <= Beim Überladen eines binären Operators wird implizit auch der zugehörige Zuweisungsoperator (falls vorhanden) überladen. Eine Überladung des Operators * ist gleichbedeutend mit einer Überladung des Operators *=. Wenn Sie in einer benutzerdefinierten Klasse einen Operator überladen möchten, müssen Sie in der Klasse eine Methode mit der richtigen Signatur erstellen. Die Methode muss mit "operator X" benannt werden, wobei X der Name oder das Symbol des Operators ist, der überladen wird. Unäre Operatoren verfügen über einen Parameter, binäre Operatoren verfügen über zwei Parameter. In beiden Fällen muss ein Parameter vom Typ der Klasse oder Struktur sein, die den Operator deklariert. public static Complex operator +(Complex c1, Complex c2) Was ist eine rekursive Funktion? Eine rekursive Funktion ist eine Funktion, die sich selber wieder aufruft. Dabei funktioniert die Funktion ähnlich wie eine Schleife und benötigt eine Abbruchbedingung um sich nicht endlos aufzurufen. 4. Klassen und Objekte in C# Wie wird eine Klasse in C# definiert? Aufbau: class Class1 { //Membervariablen //… //Methoden, Eigenschaften //… } Objekte einer Klasse müssen mit „new“ erzeugt werden o Ausnahme: Statische Klassen Werden am Heap angelegt Können erben, vererben und Interfaces implementieren Was sind Membervariablen und –funktionen und wie kann die Sichtbarkeit definiert werden? Membervariablen bzw. funktionen werden in der Klasse deklariert. Die Sichtbarkeit wird durch die 3 folgenden Schlüsselwörter bestimmt: Public o Überall bekannt, wo der zugehörige Namespace bekannt ist Private o Nur in der Klasse selbst bekannt Protected o Protected Variablen sind innerhalb der Klasse sowie davon abgeleiteten Klassen sichtbar Weiters gibt es noch die Unterscheidung zwischen statischen und nicht-statischen Membervariablen: Statische existieren einmal pro Klasse Nicht-statische einmal pro Objekt Erkläre die Definition von Konstrukturen, Eigenschaften und Indexern! Konstruktor(Beispiel): public Class1(int a, string b) { //... } Der Konstruktor wird beim Anlegen einer neuen Instanz einer Klasse aufgerufen. Es ist möglich, mehrere Konstruktoren zu implementieren, die sich in der Anzahl der Parameter unterscheiden(„überladen“). Mittels this kann ein Konstruktor einen anderen aufrufen(im Kopf des Konstruktors). Wird kein Konstruktor deklariert, wird der parameterlose Default-Konstruktor verwendet. Eigenschaften: public int GetSet_Anzahl { get { return i; } set { i = value; } } je nach Aufruf wird der Set bzw. GetTeil verwendet. cs1.GetSet_Anzahl = 12; anz = cs1.GetSet_Anzahl; Man muss nicht beide Teile implementieren; wenn zum Beispiel nur der Get-Teil benötigt wird, kann man den Set-Teil weglassen(Read-Only) und umgekehrt(Write-Only). Indexer: Indexer kann man mit Eigenschaften vergleichen, da sie auch einen Get- bzw. Set-Teil besitzen. Aufbau: public int this[int index] { get { return arr[index]; } set { arr[index] = value; } } Aufruf: Typ des indizierten Ausdrucks Name(immer „this“) Typ und Name des Indexwerts Set-Teil cs1[3] = 12; int tmp = cs1[2]; GetTeil Wie werden Objekte erzeugt und wie erfolgt der Zugriff auf Attribute und Methoden? Der Zugriff auf Attribute bzw. Methoden erfolgt grundsätzlich durch Objektname.Methodenname. Da bei statischen Klassen kein Objekt erzeugt wird, nimmt man hier den Klassennamen. Objekte einer Klasse werden über das Schlüsselwort new erzeugt, z.B. Class1 cs1 = new Class1(1, "text"); Nun kann man über das Objekt cs1 auf die Methoden und Attribute der Klasse zugreifen, allerdings nur wenn deren Sichtbarkeit public ist. 5. Statische Elemente und statische Klassen in C# Gib einen Überblick über statische Methoden, Konstruktoren und Eigenschaften? Erkläre das Überladen von Operatoren! Wie erfolgt der Zugriff auf statische Elemente? Statische Klassen und Klassenmember werden verwendet, um Daten und Funktionen zu erstellen, auf die ohne das Erzeugen einer Instanz der Klasse (wird mittels new gemacht) zugegriffen werden kann. Statische Klassenmember können verwendet werden, um Daten und Verhalten abzutrennen, das von der Objektidentität unabhängig ist: Die Daten und Funktionen ändern sich nicht – unabhängig davon, was mit dem Objekt geschieht. Statische Klassen können verwendet werden, wenn die Klasse weder Daten noch Verhalten enthält, das von der Objektidentität abhängt. Statische Klassen Eine Klasse kann als static deklariert werden. Dadurch wird angegeben, dass sie nur statische Member enthält. Es ist nicht möglich, mit dem new-Schlüsselwort Instanzen einer statischen Klasse zu erstellen. Eine statische Klasse zeichnet sich durch die folgenden Hauptmerkmale aus: · · · · Sie enthält nur statische Member. Sie kann nicht instanziert werden. Sie ist versiegelt (= nicht vererbbar). Sie kann keine Instanzkonstruktoren enthalten. Das Erstellen einer statischen Klasse ähnelt daher stark dem Erstellen einer Klasse, die nur statische Member und einen privaten Konstruktor enthält. Ein privater Konstruktor verhindert, dass die Klasse instanziert wird. Der Vorteil der Verwendung von statischen Klassen besteht darin, dass der Compiler eine Überprüfung ausführen und sicherstellen kann, dass nicht versehentlich Instanzmember hinzugefügt werden. Der Compiler garantiert, dass keine Instanzen dieser Klasse erstellt werden können. Statische Klassen sind versiegelt und deshalb nicht vererbbar. Statische Klassen können keinen Konstruktor enthalten. Es ist jedoch möglich, einen statischen Konstruktor zu deklarieren, um die Anfangswerte zuzuweisen oder einen bestimmten statischen Zustand herzustellen. Anwendungsfälle für statische Klassen Angenommen, Sie verfügen über eine Klasse CompanyInfo, die die folgenden Methoden enthält, um Informationen zu Name und Adresse einer Firma abzurufen. class CompanyInfo { public string GetCompanyName() { return "CompanyName"; } public string GetCompanyAddress() { return "CompanyAddress"; } //... } Diese Methoden müssen nicht an eine bestimmte Instanz der Klasse gebunden werden. Anstatt unnötige Instanzen dieser Klasse zu erstellen, können Sie die Klasse deshalb folgendermaßen als statische Klasse deklarieren: static class CompanyInfo { public static string GetCompanyName() { return "CompanyName"; } public static string GetCompanyAddress() { return "CompanyAddress"; } //... } Verwenden sie statische Klassen als Organisationseinheit für Methoden, die nicht an bestimmte Objekte gebunden sind. Statische Klassen können außerdem die Implementierung vereinfachen und die Ausführung beschleunigen, da sie kein Objekt erstellen müssen, um die Klassenmethode aufzurufen. Es ist nützlich, die Methoden in einer Klasse sinnvoll anzuordnen, wie dies z.B. bei den Methoden der Math-Klasse (static const float Pi, sin, cos) im System Namespace der Fall ist. Statische Eigenschaften: Neben den Mitgliedseigenschaften ermöglicht C# außerdem die Definition statischer Eigenschaften. Diese werden nicht nur auf eine spezifische Klasseninstanz, sondern auf die gesamte Klasse angewendet. class Color { // ... public static Color Red { get { return(new Color(255, 0, 0)); } } public static Color Green { get { return(new Color(0, 255, 0)); } } } Zugriff auf die Farbe Rot: Color rot = Color.Red; // Es wird kein Konstruktor der Klasse Color aufgerufen Überladen von Operatoren Alle Operatorüberladungen sind statische Methoden der Klasse. Beachten Sie, dass Sie beim Überladen des Gleichheitsoperators (==) auch den Ungleichheitsoperator (!=) überladen müssen. Die Operatoren < und > sowie die Operatoren <= und >= sollten auch paarweise überladen werden. Beispiel: public class ComplexNumber { private int real; private int imaginary; public ComplexNumber() : this(0, 0) { } public ComplexNumber(int r, int i) // constructor // constructor { real = r; imaginary = i; } // Override ToString() to display a complex number in the traditional format: public override string ToString() { return(System.String.Format("{0} + {1}i", real, imaginary)); } // Overloading '+' operator: public static ComplexNumber operator+(ComplexNumber a, ComplexNumber b) { return new ComplexNumber(a.real + b.real, a.imaginary + b.imaginary); } // Overloading '-' operator: public static ComplexNumber operator-(ComplexNumber a, ComplexNumber b) { return new ComplexNumber(a.real - b.real, a.imaginary - b.imaginary); } } Innerhalb dieser Klasse können Sie mit dem folgenden Code zwei komplexe Zahlen erstellen und bearbeiten: ComplexNumber a = new ComplexNumber(10, 12); ComplexNumber b = new ComplexNumber(8, 9); System.Console.WriteLine("Complex Number a = {0}", a.ToString()); System.Console.WriteLine("Complex Number b = {0}", b.ToString()); ComplexNumber sum = a + b; System.Console.WriteLine("Complex Number sum = {0}", sum.ToString()); ComplexNumber difference = a - b; System.Console.WriteLine("Complex Number difference = {0}", difference.ToString()); 6. Assoziation Wie erfolgt die Implementierung der Assoziationstypen (1:1, 0..1:0..1, 1..0:k)? Assoziationen entsprechen ungefähr den Beziehungen in Datenbanksystemen. Sie stellen Zusammenhänge zwischen Klassen dar. o Typ 1 : 1 o Typ 0..1 : 0..1 Die jeweils andere Klasse wird als Membervariable aufgenommen. Beim Trennen oder Herstellen einer Beziehung zwischen zwei Instanzen kann damit die Beziehung beiderseits getrennt bzw. hergestellt werden. Beispiel: Mann-Frau Beziehung o Typ 1..0 : k Ähnlich wie 1..0 : 1..0, jedoch wird anstatt einer Membervariable eine Liste verwendet. 7. Vererbung in C# Was versteht man unter Vererbung und wie wird sie realisiert? Vererbung ist, gemeinsam mit Kapselung und Polymorphie, eines der drei primären Merkmale (oder Pfeiler) der objektorientierten Programmierung. Vererbung ermöglicht die Erstellung neuer Klassen, die in anderen Klassen definiertes Verhalten wieder verwenden, erweitern und ändern. Die Klasse, deren Member vererbt werden, wird Basisklasse genannt, und die Klasse, die diese Member erbt, wird abgeleitete Klasse genannt. Eine abgeleitete Klasse kann nur eine direkte Basisklasse haben. Vererbung ist jedoch transitiv. Wenn ClassC von ClassB abgeleitet wird und ClassB von ClassA abgeleitet wird, erbt ClassC die in ClassB und ClassA deklarierten Member. Wenn eine Klasse so definiert wird, dass sie von einer anderen Klasse abgeleitet wird, erhält die abgeleitete Klasse implizit alle Member der Basisklasse, mit Ausnahme ihrer Konstruktoren und Destruktoren. In der abgeleiteten Klasse wird dadurch der Code der Basisklasse wieder verwendet, ohne dass dieser erneut implementiert werden muss. In der abgeleiteten Klasse können zusätzliche Member hinzugefügt werden. Auf diese Weise erweitert die abgeleitete Klasse die Funktionalität der Basisklasse. Vererbt wird in C# durch einen Doppelpunkt nach dem Klassennamen (class B : A => d.h. Klasse B erbt Klasse A) Wie kann eine abgeleitete Klasse erweitert werden, Elemente überschrieben bzw. verdeckt werden? Erweitern: Um eine abgeleitete Klasse zu erweitern müssen lediglich neue Funktionen darin deklariert werden. class A {// Oberklasse int a; public A() {...} public void F() {...} } class B: A {// Unterklasse (erbt von A, erweitert A) int b; public B() {...} public void G() {...} } B erbt a und F(), fügt b und G() hinzu. Konstruktoren werden nicht vererbt. Eine Klasse kann nur von einer Klasse erben, aber mehrere Interfaces implementieren. Eine Klasse kann nur von einer Klasse erben, aber nicht von einem Struct. Structs können nicht erben, sie können aber Interfaces implementieren. Alle Klassen sind direkt oder indirekt von object abgeleitet. Structs sind über Boxing mit object kompatibel. Typumwandlung: A a = new A(); // statischer Typ von a ist immer A, dynamischer Typ ist hier auch A a = new B(); // dynamischer Typ von a = B a = new C(); // dynamischer Typ von a = C Typumwandlung kann mit cast und as erfolgen: A a = new C(); B b = (B) a; C c = (C) a; A a = new C(); B b = a as B; Unterschied: bei casts erhält man einen Laufzeitfehler wenn die Umwandlung nicht möglich ist; bei Konvertierung mit as wird das Objekt null Überschreiben: Wenn in einer Basisklasse eine Methode als virtuell deklariert wird, kann die Methode in einer abgeleiteten Klasse mit einer eigenen Implementierung überschrieben werden. Wenn in einer Basisklasse ein Member als abstrakt deklariert wird, muss diese Methode in jeder nicht abstrakten Klasse, die direkt von dieser Klasse erbt, überschrieben werden. class A { public void F() {...}// nicht überschreibbar public virtual void G() {...}// überschreibbar } class B : A { public void F() {...}// Warnung: verdeckt geerbtes F() => new verwenden public void G() {...}// Warnung: verdeckt geerbtes G() => new verwenden public override void G() { // korrekt: überschreibt geerbtes G... base.G(); // ruft geerbtes G() auf } } Überschreibende Methoden müssen dieselbe Schnittstelle haben wie überschriebene Methoden: Gleiche Parameterzahl und Parametertypen - Gleiches Sicherheitsattribut (public, protected, …) Verdecken: Members können in Unterklassen mit new deklariert werden. Dadurch verdecken sie gleichnamige Member der Oberklasse. class A { public int x; public void F(); } class B : A { public new int x; public new void F(); } B b = new B(); b.x = 10; => spricht die Variable in der Klasse B an Was versteht man unter dynamischer Bindung? Bei der dynamischen Bindung geht es darum welche Methode welchem Objekt zugeordnet wird. Dies geschieht während der Laufzeit. Anhand des folgenden Beispiels erklärbar: class Animal{ public virtual void WhoAreYou() { Console.WriteLine("I am an animal"); } } class Dog: Animal { public override void WhoAreYou() { Console.WriteLine("I am a dog"); } } class Beagle: Dog { public new virtual void WhoAreYou() { Console.WriteLine("I am a beagle"); } } class AmericanBeagle: Beagle { public override void WhoAreYou() { Console.WriteLine("I am an american beagle"); } } Beagle pet = new AmericanBeagle(); pet.WhoAreYou();// "I am an american beagle" Animal pet = new AmericanBeagle(); pet.WhoAreYou();// "I am a dog" !! In der Klasse Beagle wird die Funktion WhoAreYou der Basisklasse verdeckt => der Compiler sucht in der Hierarchie, bis er auf eine verdeckte Funktion trifft (oder er am Ende der Hierarchie angelangt ist) und bricht den Suchalgorithmus ab => im 2ten Beispiel wird daher die WhoAreYou-Funktion der Klasse Dog verwendet. Erkläre Konstrukturen in Ober- und Unterklasse! Wie erfolgt der Zugriff auf Methoden und Attribute der Basisklasse? Generell können sowohl Funktionen als auch Attribute in einer Klasse public sein => man kann im selben Namespace immer darauf zugreifen. protected => sichtbar in deklarierender Klasse und ihren Unterklassen Um auf Funktionen bzw. Attribute der Basisklasse zuzugreifen kann in den Unterklassen die Variable base verwendet werden. z.B. base.Push(); 8. Abstrakte Klassen in C# Was versteht man unter einer abstrakten Klasse und wie wird eine abstrakte Klasse in C# implementiert und verwendet? Eine abstrakte Klasse bezeichnet in der objektorientierten Programmierung eine spezielle Klasse, die mindestens eine, aber auch mehrere abstrakte Methoden enthalten (also Methoden ohne „Rumpf“ (Implementierung) nur mit der Signatur). Per Definition können abstrakte Klassen nicht instanziiert, d.h. keine Objekte von ihnen erzeugt werden. Schnittstellen sind rein abstrakte Klassen, die nur Methodensignaturen deklarieren. Eine Klasse gilt dagegen bereits als abstrakt, sobald eine Methode vorhanden ist, die durch eine erbende Klasse implementiert werden muss. In einer abstrakten Klasse können auch Variablen definiert und Methoden implementiert werden. Als Basisklassen in einer Klassenhierarchie können abstrakte Klassen grundlegende Eigenschaften ihrer Unterklassen festlegen, ohne diese bereits konkret zu implementieren. Leitet eine Klasse von einer abstrakten Klasse ab, müssen alle vererbten abstrakten Methoden überschrieben und implementiert werden, damit die erbende Klasse selbst nicht abstrakt ist. Abstrakte Klassen können nicht selbst instanziiert werden, nur Spezialisierungen von diesen. Properties und Indexer müssen hinsichtlich get und set gleich sein. Erkläre den Zusammenhang zwischen abstract und virtual! Eine virtuelle Funktion kann in einer Unterklasse überschrieben werden (muss aber nicht). Eine abstrakte Funktion muss in der erbenden Klasse überschrieben werden, damit diese nicht selber abstrakt ist. Virtuell wird dann verwendet, wenn die Funktionalität der Klasse lediglich erweitert wird (=> Grundlegende Funktion ist brauchbar). Abstract wird verwendet, wenn die erbenden Klassen zwar gleiche Funktionen haben, deren Funktionalität jedoch sehr unterschiedlich ist. 9. Interfaces in C# Was versteht man unter einem Interface und wie wird ein Interface in C# implementiert und verwendet? Eine Schnittstelle enthält nur die Signaturen von Methoden, Delegaten oder Ereignissen. interface ISampleInterface { void SampleMethod(); } class ImplementationClass : ISampleInterface { // Methode vom Interface wird implementiert void SampleMethod() { //Code } static void Main() { // Objekt declarieren ImplementationClass obj = new ImplementationClass(); // Methode aufrufen obj.SampleMethod(); } } Eine Schnittstelle kann ein Member eines Namespaces oder einer Klasse sein und Signaturen der folgenden Member enthalten: Methoden Eigenschaften Indexer Ereignisse Eine Schnittstelle kann von einer oder mehreren Basisschnittstellen erben. Wenn eine Basisklassenliste sowohl eine Basisklasse als auch Schnittstellen umfasst, muss die Basisklasse zuerst in der Liste stehen. Eine Klasse, die eine Schnittstelle implementiert, kann Member dieser Schnittstelle explizit implementieren. Auf einen explizit implementierten Member kann nicht durch eine Klasseninstanz zugegriffen werden, sondern nur durch eine Schnittstelleninstanz. Explizit implementierten Member Wenn eine Klasse zwei Schnittstellen erbt, die einen Member mit derselben Signatur enthalten, bewirkt die Implementierung dieses Members in der Klasse, dass beide Schnittstellen diesen Member als ihre Implementierung verwenden. interface IControl { void Paint(); } interface ISurface { void Paint(); } class SampleClass : IControl, ISurface { // Both ISurface.Paint and IControl.Paint call this method. public void Paint() { } } Falls die beiden Schnittstellen-Member jedoch nicht dieselbe Funktion erfüllen, kann dies zu einer falschen Implementierung von einer oder beiden Schnittstellen führen. Es ist möglich, einen Schnittstellenmember explizit zu implementieren – also einen Klassenmember zu erstellen, der nur über die Schnittstelle aufgerufen wird und für diese Schnittstelle spezifisch ist. Dazu benennt man den Klassenmember mit dem Namen der Schnittstelle und einem Punkt. public class SampleClass : IControl, ISurface { void IControl.Paint() { System.Console.WriteLine("IControl.Paint"); } void ISurface.Paint() { System.Console.WriteLine("ISurface.Paint"); } } Was sind Gemeinsamkeiten und Unterschiede von abstrakten Klassen und Interfaces? Eigenschaften Mehrfache Vererbung Default implementation Zugriffsmodifizierer Felder und Konstanten Interfaces ja Ein Interface kann keinen Code zur Verfügung stellen, sondern nur die Signaturen der Member Kann keine Zugriffsmodifizierer bei den Funktionen haben. Alle Methoden sind public. Kann keine Felder und Konstanten beinhalten 10. Ausnahmebehandlung in C# Erkläre den Mechanismus der Ausnahmebehandlung in C#! Nenne Beispiel für Exceptions! Wie können Exceptions geworfen werden? try Abstrakte Klassen nein Kann Code zur Verfügung stellen Kann Zugriffsmodifizierer bei den Methoden haben. Kann Felder und Konstanten beinhalten { MessageBox.Show("Executing the try statement."); //weiterer Code throw new NullReferenceException(); } catch (NullReferenceException exc) //fängt die geworfene Exception { MessageBox.Show("Caught exception #1." + exc); } catch (Exception lala) //würde andere Exceptions die in "weiterer Code" stehen fangen { MessageBox.Show("Caught exception #2." + lala); } finally //dieser Code wird ausgeführt egal ob eine Exception auftritt oder nicht { MessageBox.Show("Executing finally block."); } Wenn im Try Block ein Fehler auftritt (z.B. Problem beim Verbinden mit einer DB) so geht das Programm automatisch in den catch Block und führt den Code dort aus. Code im finally Block wird auch ausgeführt wenn im try Block eine Exception auftritt. Man kann Exceptions auch manuell „werfen“ siehe oben, es passiert das selbe wie bei einer automatischen Auslösung. Einige Exceptions: IndexOutOfRangeException DivideByZeroException OutOfMemoryException OverflowException ArrayTypeMismatchException SqlException OleDbException 11. Steuerelemente mit Windows.Forms Erkläre die Elemente Component, Control und Container! Beschreibe Möglichkeiten ihrer Anordnung mit Layoutmanagern. 12. Ereignisbehandlung in C# Erkläre die Ereignisbehandlung (Delegates und Events). Eine Klasse kann ein Ereignis dazu verwenden, eine andere Klasse (oder andere Klassen) über ein aufgetretenes Ereignis zu benachrichtigen. Ereignisse verwenden das Veröffentlichen-AbonnierenPrinzip; eine Klasse veröffentlicht die Ereignisse, die sie ausgeben kann, und Klassen, die Interesse an bestimmten Ereignissen haben, können diese Ereignisse abonnieren. Ereignisse werden in grafischen Benutzerschnittstellen häufig dazu eingesetzt, über eine Benutzerauswahl zu informieren, sie eignen sich jedoch auch für asynchrone Operationen, beispielsweise für Dateiänderungen oder das Empfangen von E-Mail-Nachrichten. Die Routine, mit der ein Ereignis aufgerufen wird, wird durch eine Zuweisung (delegate) definiert. Zur einfacheren Handhabung von Ereignissen legen die Entwurfskonventionen für Ereignisse fest, dass die Zuweisung immer zwei Parameter umfasst. Der erste Parameter bezeichnet das Objekt, durch das das Ereignis ausgelöst wurde, der zweite Parameter ist ein Objekt, das Informationen zum Ereignis enthält. Dieses Objekt leitet sich immer von der EventArgs-Klasse ab. Hier ein Beispiel zu Ereignissen. using System; class NewEmailEventArgs: EventArgs { string subject; string message; public NewEmailEventArgs(string subject, string message) { this.subject = subject; this.message = message; } public string Subject { get { return subject; } } public string Message { get { return message; } } } class EmailNotify { public delegate void NewMailEventHandler(object sender, NewEmailEventArgs e); public event NewMailEventHandler OnNewMailHandler; protected void OnNewMail(NewEmailEventArgs e) { if (OnNewMailHandler != null) OnNewMailHandler(this, e); } public void NotifyMail(string subject, string message) { NewEmailEventArgs e = new NewEmailEventArgs(subject, message); OnNewMail(e); } } class MailWatch { EmailNotify emailNotify; public MailWatch(EmailNotify emailNotify) { this.emailNotify = emailNotify; emailNotify.OnNewMailHandler += new EmailNotify.NewMailEventHandler(IHaveMail); } void IHaveMail(object sender, NewEmailEventArgs e) { Console.WriteLine("New Mail: {0}\n{1]", e.Subject, e.Message); } } class Test { public static void Main() { EmailNotify emailNotify = new EmailNotify(); MailWatch mailWatch = new MailWatch(emailNotify); emailNotify.NotifyMail("Hello!", "Welcome to Events!!!"); } } Die NewEMailEventArgs-Klasse enthält die Informationen, die bei der Ausgabe eines NewEmailEreignisses übergeben werden. Die EMailNotify-Klasse ist für die Ereignishandhabung verantwortlich; über diese Klasse wird die Zuweisung deklariert, mit der definiert wird, welche Parameter bei Auftreten des Ereignisses übergeben werden. Des Weiteren enthält die Klasse eine Definition des Ereignisses selbst. Die OnNewMail()-Funktion wird zur Ausgabe des Ereignisses eingesetzt, die Helferfunktion NotifyMail() enthält die Ereignisinformationen, packt diese in eine Instanz von NewEmailEventArgs und ruft OnNewMail() zur Ereignisausgabe auf. Die Klasse MailWatch ist ein Konsument der EmailNotify-Klasse. Sie übernimmt eine Instanz der EmailNotify-Klasse und koppelt die IHaveMail()-Funktion an das OnNewMailHandler-Ereignis. Abschließend erstellt die Main()-Funktion Instanzen von EmailNotify und MailWatch und ruft die Funktion NotifyMail() zur Ereignisausgabe auf. 13. Grafikprogrammierung mit C# Erkläre die Behandlung und das Auslösen des Paint-Ereignisses! Erkläre den Zusammenhang zwischen Bildschirmkoordinaten und logischen Koordinaten! Wie können Linien gezeichnet und Flächen gefüllt werden? Wie werden Grafikdateien angezeigt? 14. Dialoge mit Windows.Forms Wie können Standarddialoge verwendet werden? Erkläre die Erstellung und den Aufruf eines benutzerdefinierten Dialogs! Erkläre den Unterschied zwischen modalen und nicht modalen Dialogen! Wie können OK und Abbruch behandelt werden? Standarddialoge: Jeweils ein typisches Beispiel. Die Dialoge haben natürlich noch TAUSEND zusätzliche Properties. ColorDialog: ColorDialog color = new ColorDialog(); color.FullOpen = true //für Benutzerdefinierte Farben (wo man aus dem bunten Feld auswählt) if (color.ShowDialog() == DialogResult.OK) { this.BackColor = color.Color; } OpenFileDialog: OpenFileDialog open = new OpenFileDialog(); open.Title = "Welche Konfiguration wollen sie öffnen?"; open.DefaultExt = "dat"; open.AddExtension = true; open.Filter = "Data file (*.dat)|*.dat"; open.RestoreDirectory = true; open.InitialDirectory = @"D:/"; if (open.ShowDialog() == DialogResult.OK) { string path = open.FileName; } FolderBrowser: FolderBrowserDialog browser = new FolderBrowserDialog(); if (browser.ShowDialog() == DialogResult.OK) { string path = browser.SelectedPath; } SaveFileDialog: SaveFileDialog save = new SaveFileDialog(); save.Title = "Wo wollen sie die Konfiguration speichern?"; save.DefaultExt = "dat"; save.AddExtension = true; save.Filter = "Data file (*.dat)|*.dat"; save.RestoreDirectory = true; save.InitialDirectory = @"D:/"; if (save.ShowDialog() == DialogResult.OK) { string path = save.FileName; } FontDialog: FontDialog fontdialog = new FontDialog(); if (fontdialog.ShowDialog() == DialogResult.OK) { this.Font = fontdialog.Font; } Benutzerdefinierter Dialoge: Eigene Form erstellen. z.B. „Einstellungen“; Über Properties werden kann man auf die Daten des Dialogs zugreifen. Einstellungen settings = new Einstellungen(vorname, nachname); if (settings.ShowDialog() == DialogResult.OK) { vorname = settings.Vorname; nachname = settings.Nachname; } Modale/nicht modale Dialoge: Die meisten Dialoge sind modale Dialoge! Modale: werden mit dialog.ShowDialog() aufgerufen. Müssen geschlossen werden damit man wieder mit der Anwendung von der sie geöffnet wurden, arbeiten kann. Bsp: Standarddialoge Nicht modale: Werden mit dialog.Show() aufgerufen. Formular muss nicht geschlossen werden um wieder in anderen Formularen arbeiten zu können. Mehr Programmieraufwand da Daten immer konsistent bleiben müssen. Bsp: Suchen Fenster in VisualStudio Wie können OK und Abbruch behandelt werden? Man erstellt im Dialog einen Button OK und einen Button Abbrechen. Bei diesen Buttons stellt man die Eigenschaft DialogResult auf „OK“ bzw. auf „Cancel“. Im Dialogaufruf greift man dann folgendermaßen darauf zu: if (dialog.ShowDialog() == DialogResult.OK){} bzw. if (dialog.ShowDialog() == DialogResult.Cancel){} I. Design-Pattern Strategy Erkläre das Ausgangs-Klassendiagramm der Entensimulation. Welche Probleme treten auf, wenn eine weitere Methode (z.B. fliegen()) hinzukommen soll? Welche Nachteile hat die Realisierung mit einem Interface? Erkläre das Strategy-Entwurfsmuster und die Anwendung dieses Musters auf die Entensimulation! Problemstellung: Ich möchte verschiedene Enten in mein Programm implementieren, die je nachdem fliegen, quaken oder eben nicht Fliegen bzw. Quaken können Problem bei Vererbung, ich muss alles überschreibe, wenn dem Programm eine neue Enten-Klasse hinzugefügt wird, muss er fliegen() und quaken() unter die Lupe nehmen und eventuell überschreiben. "Und wie wäre es mit einem Interface?" denkt der Programmierer. "Code-Verdopplung": z.B. Methode fliegen(), was wäre bei einer kleinen Änderung des Flugverhaltens bei z.B. 48 Unterklassen von Ente Interfaces enthalten keinen Implementierungscode und deshalb gibt es keine Code-Wiederverwendung und bei einer Änderung muss diese Änderung in allen Unterklassen geändert werden Entwurfsprinzip Nehme die Teile, die sich ändern können, und kapsle sie, damit du diese veränderlichen Teile später ändern oder erweitern kannst, ohne dass das Auswirkungen auf die Teile hat, die sich nicht ändern. Anwendung des Entwurfsprinzips auf die Enten? Fliegen und quaken kann sich ändern -> das wird herausgezogen und daraus ein eigener Satz von Klassen für das jeweilige Verhalten erstellt Vorteile: Auch andere Typen von Objekten können nun Flug- und Quakverhalten verwenden, weil diese Verhalten nicht mehr in der Enten-Klasse verborgen sind. Wir können neue Verhalten hinzufügen, ohne irgendeine bestehende Verhaltensklasse zu ändern und ohne die Enten-Klasse zu ändern. Der Schlüssel ist, dass eine Ente ihr Flug- und Quakverhalten jetzt delegiert, anstatt Quak- und Flugmethoden der Klasse Ente zu verwenden. Wie kann nun das Flug- und Quakverhalten in der Klasse Ente integriert werden Wir fügen der Klasse Ente zwei Instanzvariablen flugVerhalten und Quakverhalten hinzu. Die Variablen werden mit dem Typ des Interface (und nicht mit einem konkreten Implementierungstyp) deklariert, d.h.: jedes Ente-Objekt setzt diese Variable polymorph, damit sie den Verhaltenstyp referenziert, der zur Laufzeit gewünscht ist. Wir entfernen die Methoden fliegen() und quaken() aus der Klasse Ente und allen Unterklassen und ersetzen fliegen() und quaken() in der Klasse Ente durch tuFliegen() und tuQuaken(). Jede Ente hat ein FlugVerhalten und ein QuakVerhalten. Statt das Verhalten zu erben, erhalten die Enten ihr Verhalten durch Komposition (bzw. Assoziation). Formale Definition des Strategy-Musters: Das Strategy-Muster definiert eine Familie von Algorithmen, kapselt sie einzeln und macht sie austauschbar. Das Strategy-Muster ermöglicht es, den Algorithmus unabhängig von den Clients, die ihn einsetzen, variieren zu lassen. Falls Dynamisch das Ganze: Property FlugVerhalten fliegen oder ein SET Flugverhalten in der Ente einfügen using using using using System; System.Collections.Generic; System.Linq; System.Text; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { Stockente stockente = new Stockente(); stockente.tuFliegen(); stockente.tuQuaken(); Console.ReadLine(); } } abstract class Ente { protected FlugVerhalten flugVerhalten; protected QuakVerhalten quakVerhalten; public void tuFliegen() { flugVerhalten.fliegen(); } public void tuQuaken() { quakVerhalten.quaken(); } } interface FlugVerhalten { void fliegen(); } public class FliegtGarNicht : FlugVerhalten { public FliegtGarNicht() { } public void fliegen() { Console.WriteLine("Ich kann gar nicht fliegen"); } } interface QuakVerhalten { void quaken(); } class Quaken:QuakVerhalten { public Quaken() { } public void quaken() { Console.WriteLine("Quak"); } } class Stockente:Ente { public Stockente() { flugVerhalten = new FliegtGarNicht(); quakVerhalten = new Quaken(); } } } II. Design-Pattern Observer Erkläre die Aufgaben des Wetterdaten-Objekts! Welche Probleme treten auf, wenn weitere Anzeigeelemente hinzukommen sollen? Erkläre die Definition des Observer-Musters! Erkläre die Anwendung dieses Musters am Beispiel des Wetterdatenbeispiels und das Klassendiagramm! Das Wetterdaten-Objekt weiß wie auf die Wetterstation zugegriffen werden kann. Das Wetterdaten-Objekt ändert dann die Anzeige für drei unterschiedliche Anzeigeelemente: Aktuelle Wetterdaten Wetterstatistik Wettervorhersage Unsere Aufgabe ist es, eine Anwendung zu erstellen, die die drei Anzeigeelemente aktualisiert. Unsere Aufgabe ist es, messWerteGeaendert so zu implementieren, dass die drei Anzeigen aktualisiert werden. Das System muss erweiterbar sein, um problemlos weitere Anzeigeelemente hinzufügen bzw. vorhandene entfernen zu können. class WetterDaten { // Variablen public void messwerteGeändert() { // frische Messungen holen float temp = getTemperatur(); float feuchtigkeit = getFeuchtigkeit(); float druck = getLuftdruck(); // Anzeigen aktualisieren aktuelleBedingungenAnzeige.aktualisieren(temp,feuchtigkeit,druck); statistikAnzeige.aktualisieren(temp,feuchtigkeit,druck); vorhersageAnzeige.aktualisieren(temp,feuchtigkeit,druck); } // weitere Methoden } Probleme: Es kann nicht während der Laufzeit geändert werden. Neue Anzeige-Elemente können nur vor der Laufzeit im Quellcode hinzugefügt werden. Die Funktion muss in jeder Anzeige-Klasse implementiert werden, da diese aber nicht von einem Interface abgeleitet werden, muss diese Funktion in jeder Klasse neu angelegt werden. Definition des Observer-Musters: Das Observer-Muster definiert eine 1:n Abhängigkeit zwischen Objekten in der Art, dass alle abhängigen Objekte benachrichtigt werden, wenn sich der Zustand des einen Objekts ändert. Abhängig von der Art der Benachrichtigung können die Beobachter auch mit neuen Werten aktualisiert werden. Klassendiagramm: «interface» Subjekt +registriereBeobachter() +entferneBeobachter() +benachrichtigeBeobachter() KonkretesSubjekt +registriereBeobachter() +entferneBeobachter() +benachrichtigeBeobachter() - «interface» Beobachter +aktualisieren() * 1 KonkreterBeobachter +aktualisieren() Objekte nutzen das Subjekt-Interface, um sich als Beobachter zu registrieren oder zu entfernen. Ein konkretes Subjekt implementiert immer das Subjekt-Interface. Jedes Subjekt kann viele Beobachter haben. Alle möglichen Beobachter müssen das Interface Beobachter implementieren, damit die Methode aktualisieren aufgerufen werden kann, wenn sich der Subjektzustand ändert. Konkrete Beobachter können beliebige Klasse sein, die das Interface Beobachter implementieren. Jeder Beobachter registriert sich bei einem konkreten Subjekt, um Aktualisierungen zu erhalten. Observer-Muster: Das Observer-Muster bietet ein Objekt-Design, bei dem Subjekt und Beobachter locker gebunden sind, d.h. sie können miteinander agieren, müssen aber nur wenige Kenntnisse voneinander besitzten. Das Subjekt weiß über den Beobachter nur eine Sache, dass er eine bestimmte Schnittstelle implementiert: Das Subjekt muss die konkrete Klasse des Beobachters nicht kennen. Das Subjekt benötigt nur eine Liste der Objekte, die sich als Beobachter registriert haben. Es können jederzeit (auch während der Laufzeit) neue Beobachter hinzugefügt bzw. entfernt werden, ohne die Subjektimplementierung zu ändern. Änderungen am Subjekt oder an einem Beobachter haben auf den jeweils anderen keinen Einfluss. An beiden Klassen können Veränderungen vorgenommen werden, solange die entsprechenden Interfaces implementiert werden. Entwurfsprinzip: Strebe bei Entwürfen mit interagierenden Objekten nach lockerer Kopplung. Anzeigeelemente Implementiert Beobachter (für die Funktion aktualisieren) und AnzeigeElement (für anzeigen). III. Design-Pattern Decorator Erkläre den ersten Klassendiagrammentwurf zum Kaffehaus-Beispiel! Ich habe ein Kaffehaus mit Verschiedenen Grundgetränken (Espresso, Dunkle Mischung , Hauskaffee) Und danach gibt es noch extra Zutaten wie Milch, Soja, Schokolade, Milchschaum 1. Für jede Art eine eigene Klasse (z.B. EsspressomitMilchundSchoko, DunkleMischungmitSchoko) Das liefert jedoch ein Riesenklassendiagramm Welche Probleme ergeben sich durch die verschiedenen Zutaten? Wenn sich zum Beispiel der Preis für Milchschaum ändert muss ich das in Allen Klassen ändern, viel zu viel Aufwand, bzw.viel zu viele Klassen Welche Nachteile hat die Lösung mit den Zutaten in der Basisklasse? Andere Idee : -> Instanzvariabeln in der Basisklasse Nachteile: Preisänderungen bei den Zutaten könnten die Bearbeitung von bestehendem Code erforderlich machen. Neue Zutaten zwingen dazu, neue Methoden hinzuzufügen bzw. die preis() – Methode zu ändern Neue Getränke -> Für einige Getränke (Eistee) können aber einige Zutaten unpassend sein und würden trotzdem vererbt werden (z.B hasMilchschaum() Was ist wenn ein Kunde Doppelschoko möchte Decorator Erkläre das Klassendiagramm zur Lösung mit Hilfe des Decorator-Musters und erkläre die Preisberechnung Definition : Das Decorator-Muster fügt einem Objekt dynamisch zusätzliche Verantwortlichkeiten hinzu. Dekorierer bieten eine flexible Alternative zur Ableitung von Unterklassen zum Zweck der Erweiterung der Funktionalität. Bsp. Dunkle Röstung mit Schoko und Milchschaum Dunkle Röstung ist dabei das Grundgetränk welches vom Getränk erbt und eine Preisfunktion hat, die den Preis berechnet preis(). Schoko bzw. Milchschaum sind Dekorierer welches auch auf Getränk aufbaut Dekorierer ist ein Typ der das grundobjekt wiederspiegelt (in unserem Fall Getränk) Schoko hat auch einen preis(Methode ) und dank Polymorphie können wir jedes Getränk mit Schoko einpacken und das neue Objekt wie ein Getränk behandeln weil Schoko auch ein Untertyp von Getränk ist. Preisberechnung Klassendiagramm zur Lösung mit Hilfe des Decorator-Musters Getränk ist die Oberklasse mit der abstract klasse preis Hausmischung, Dunkle ,Röstung sind die Grundgetränke mit jeweils der Funktion preis (override) ZutatDekorierer ist eine Klasse dich auch auf Getränk und die Funktion getBeschreibung() beinhalteten Die Weiteren Zutaten wie sChoko etc bauen immer auf das Getränk auf, das ihnen Übergeben wird. Mit der Funktion preis() (override) wird der bissherige Getränkpreis um des jeweiligen Produktpreis erhöht. return .10 + getränk.preis(); using using using using System; System.Collections.Generic; System.Linq; System.Text; namespace _3decorator { class Program { static void Main(string[] args) { Getränk getränk = new Espresso(); Console.WriteLine(getränk.getBeschreibung() + " " + getränk.preis() + " $"); Getränk getränk2 = new DunkleRöstung(); getränk2 = new Schoko(getränk2); getränk2 = new Schoko(getränk2); getränk2 = new Milchschaum(getränk2); Console.WriteLine(getränk2.getBeschreibung() + " " + getränk2.preis() + " $"); Getränk getränk3 = new Hausmischung(); getränk3 = new Soja(getränk3); getränk3 = new Schoko(getränk3); getränk3 = new Milchschaum(getränk3); Console.WriteLine(getränk3.getBeschreibung() + " " + getränk3.preis() + " $"); Console.Read(); } } public abstract class Getränk { protected string beschreibung = "Unbekanntes Getränk"; public abstract double preis(); public virtual string getBeschreibung() { return beschreibung; } } public class DunkleRöstung : Getränk { public DunkleRöstung() { beschreibung = "Dunkle Röstung"; } public override double preis() { return 0.99; } } public class Espresso : Getränk { public Espresso() { beschreibung = "Espresso"; } public override double preis() { return 1.99; } } public class Hausmischung : Getränk { public Hausmischung() { beschreibung = "Hausmischung"; } public override double preis() { return .89; } } public abstract class ZutatDekorierer : Getränk { protected ZutatDekorierer(Getränk getränk) {this.getränk = getränk;} protected readonly Getränk getränk; public override string getBeschreibung() { return getränk.getBeschreibung() + ", " + beschreibung; } } public class Schoko : ZutatDekorierer { public Schoko(Getränk getränk):base (getränk){} public override String getBeschreibung() { return getränk.getBeschreibung() + ", Schoko"; } public override double preis() { return .20 + getränk.preis(); } } public class Milchschaum : ZutatDekorierer { public Milchschaum(Getränk getränk): base(getränk){} public override String getBeschreibung() { return getränk.getBeschreibung() + ", Milchschaum"; } public override double preis() { return .10 + getränk.preis(); } } public class Soja : ZutatDekorierer { public Soja(Getränk getränk) : base(getränk){} public override String getBeschreibung() { return getränk.getBeschreibung() + ", Soja"; } public override double preis() { return .15 + getränk.preis(); } } } IV. Design-Pattern Singleton Für welche Zwecke wird das Singleton-Muster verwendet? Erkläre das Singleton-Muster und das zugehörige Klassendiagramm! Warum können trotz des Singleton-Musters unterschiedliche Threads jeweils eine Instanz erzeugen? Wie kann gewährleistet werden, dass auch bei mehreren Threads nur eine einzige Instanz erzeugt wird? Dieses Muster wird für Klassen benutzt, von denen nur ein Objekt benötigt wird. Zum Beispiel Klassen zur Verwaltung von Benutzereinstellungen, Datenbankverbindungen usw. Das Singleton-Muster verhindert, dass von einer Klasse mehrere Instanzen gibt und es wird ein globaler Zugriffspunkt auf diese Instanz bereitgestellt. Singleton -einzigeInstanz +getInstanz() Die Klasse besitzt eine einzige Instanz, die in der Klassenvariable (private) „einzigeInstanz“ gespeichert wird. Die Methode getInstanz() (static) ist für die Initialisierung einer Instanz zuständig bzw. gibt diese Methode die jeweilige Instanz zurück. Daher kann man an einer beliebigen Stelle mit Klassennamen.getInstanz() auf diese Instanz zugreifen. Mit dieser Implementation ist es jedoch möglich, dass verschiedene Threads jeweils eine Instanz erzeugen, da bei fast gleichzeitigem Aufruf Synchronistationsprobleme im Arbeitsspeicher auftreten können. Um das Ganze auch noch Threadsicher zu gestalten, wird die Funktion lock() in Verbindung mit einem statischen Objekt verwendet. Siehe Codebeispiel. sealed class Singleton { private Singleton() { } private static volatile Singleton instance; public static Singleton getInstance() { // DoubleLock if (instance == null) { lock(m_lock) { if (instance == null) { instance = new Singleton(); } } } return instance; } // Hilfsfeld für eine sichere Threadsynchronisierung private static object m_lock = new object(); } V. Design-Pattern Command Erkläre das Command-Muster am Beispiel 'Fernsteuerung'! Erkläre das Klassendiagramm zum Command-Muster! Mit dem Command-Muster lassen sich auf einfache Weise austauschbare Aktionen in einem Programm realisieren. In unserem Beispiel wird dies anhand einer Fernsteuerung dargestellt. Zur Realisierung verwendet man ein Befehlsarray für den Ein-Befehl und für den Aus-Befehl. Als Datentyp für dieses Array wird das Interface „Befehl“ verwendet. Die einzelnen Befehle wie z.B. „Licht an“ werden nun von diesem Interface abgeleitet und an dem entsprechenden Platz in den Befehlsarrays eingefügt. Um eine möglichst hohe Wiederverwendbarkeit zu gewährleisten wird bei der Erstellung einer neuen Instanz der einzelnen Befehle ein Verweis zu dem zu steuernden Geräts übergeben. Die einzelnen Befehle rufen in der Methode „ausführen()“ die jeweiligen Methoden der Geräteklasse auf. Somit können mit einem Befehl mehrere Tätigkeiten erledigt werden. StereoAnlage +ein() +aus() +setCD() +setDVD() +setRadio() +setLautstärke() Licht +ein() +aus() Empfänger aktion() void ausführen() { empfänger.aktion(); } Befehl VI. Design-Pattern Adapter Welche Probleme werden durch das Adapter-Muster abgedeckt? Problem: Man hat 2 Klassen die unterschiedliche Interfaces (z.B. Ente und Truthahn), implementieren. Will man jene jedoch (z.B. in einer Liste) gleich ansprechen, müsste man in beiden Klassen Code ändern. (Da Interfaces eine Implementierung all ihrer Funktionen verlangen) – Kurz Inkompatibilität zwischen Klassen aufgrund unterschiedlicher Interfaces. Lösung: Eine Zwischenklasse – Eine Adapterklasse. Man erzeugt eine neue Klasse die dasselbe Interface wie die erste Klasse (Ente) implementiert. Darin legt man eine Membervariable der anderen Klasse (Truthahn) an. In den Methoden, die durch die Implementierung des Interfaces (Ente) vorgeschrieben sind, ruft man jetzt die eigentlich gewollten Methoden der Membervariable auf. Erkläre das Klassendiagramm zum Adapter-Muster! Client * Stockente * «interface» Ente +quaken() +fliegen() TruthahnAdapter -truthahn : Truthahn +quaken() +fliegen() +quaken() +fliegen() * * «interface» Truthahn +truten() +fliegen() WilderTruthahn +truten() +fliegen() Der Client will nur Klassen ansprechen, die er über das Interface Ente zusammenfassen kann(hier als Beispiel Stockente). Jetzt braucht man jedoch aus irgendeinem Grund zusätzlich mehrere Arten von Truthähnen (hier als Beispiel WilderTruthahn). Diese implementieren jedoch nicht das Interface Ente, sondern Truthahn. Um dem Client zu ermöglichen, diesen trotzdem über das Ente Interface anzusprechen, ohne die neuen Truthahn-Klassen zu ändern (man kann oder will nicht), erstellt man eine Adapterklasse: TruthahnAdapter. Diese implementiert das Ente Interface und lässt sich somit wie andere Enten-Klassen benutzen. Jedoch führt sie in den Methoden quaken() und fliegen() die Methoden der Truthahn-Klasse (truten(), fliegen()) aus, die in der Membervariable truthahn gespeichert ist.