Fragenliste-Ausarbeitung

Werbung
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.
Herunterladen