LF06.3-FIAE - Anwendungsentwicklung

1 Objektorientierte Programmierung

Nutzen Sie den C# Online Compiler. Alternativ können Sie eine bereits installierte IDE nutzen.

Zur Vertiefung kann der sololearn-Kurs bearbeitet werden.

1.1 Implementieren eines Klassendiagramms in C#

1.1.1 Erstellen einer Klasse

Entwerfen und implementieren Sie eine Klasse CDatum. Diese Klasse soll ein Kalenderdatum bestehend aus Tag, Monat und Jahr intern verwalten und an einer von Ihnen festgelegten Schnittstelle die folgenden Funktionalitäten bieten:

  • Setzen eines bestimmten Datums
  • Vergleich zweier Daten (gleich, vorher, nachher)
  • Addition einer bestimmten Anzahl von Tagen zu einem Datum
  • Bestimmung der Differenz zwischen zwei Daten im normalen Kalenderjahr
  • Bestimmung der Differenz zwischen zwei Daten im Bankjahr (360 Tage, jeder Monat 30 Tage)
  • Berechnung des Wochentages (0-6) eines Datums
  • Festlegung, ob es sich um einen Feiertag handelt
  • Erzeugen von Textstrings um das Datum in unterschiedlichen Formaten auszugeben
    (z.B. 2.4.2019, 04.05.2019, 3. Jan 2019, 5. Februar 2019)
  • Berechung des Julianischen Datums
  • Gibt es noch weitere Funktionen?

Schreiben Sie ein Testprogramm, mit dem alle Funktionen dieser Klasse intensiv getestet werden können.

1.1.2 Vererbung

Beispielaufruf von Basisklassenkonstruktoren:

class CPerson
{
    private string Name;
    public CPerson()  { Name = "MUSTERMANN"; }
    public CPerson(string N)  { Name = N; }  
}    
class CKunde : CPerson
{
    public CKunde():base()  {  }
    public CKunde(string N):base(N)   {  }
}

Methode aufrufen:

class CPerson
{
    public void ZeigDaten()
    { 
        Console.WriteLine("Name der Person: " + Name); 
    }
}

class CKunde: CPerson
{
    private int Kunden_ID;
    public void ZeigDaten()	
    {
        base.ZeigDaten();
      	Console.WriteLine("Kunden – ID    : " + Kunden_ID);	
    }
}

Aufruf über Basisklassenverweis:

static void Main(string[] args)
{
    CPerson P = new CPerson("Hansen");
    CKunde K = new CKunde("Maier",123);
    P = K; 
    P.ZeigDaten();  // Ausgabe: "Name: Maier"
}

Virtuelle Methode:

class CPerson
{
    virtual public void ZeigDaten()	
    {
        Console.WriteLine("Name der Person: " + Name);	
    }
}

class CKunde: CPerson
{
    public override void ZeigDaten()  
    {
        base.ZeigDaten();
      	Console.WriteLine("Kunden – ID    : " + Kunden_ID); 
    }
}

Polymorphismus:

static void Main(string[] args)
{
    CPerson P = new CPerson("Hansen");
    CKunde K = new CKunde("Maier",123);

    P = K; 
    P.ZeigDaten();  // Ausgabe: "Name: Maier - ID: 123"
}

Aufgabe zur Vererbung:
Implementieren Sie Klassen der Vererbungshierarchie CPerson, CKunde und CMitarbeiter.

  • Die Klasse CPerson soll die Attribute Name und Vorname haben.
  • Die Klasse CKunde hat ein zusätzliches Attribut Kunden_ID und die Klasse CMitarbeiter hat ein zusätzliches Attribut Abteilung.

Überschreiben Sie in jeder Klasse die Methode public override string ToString(){}. Diese Methode ist in der Klasse System.Object als virtuelle Methode angelegt.

Die Methode soll alle Daten des Objektes in einem String zurückgeben.

Speichern Sie dann einige Personen, Mitarbeiter und Kunden in einer ArrayList und geben Sie dann mit Hilfe von ToString() die Daten in einer geeigneten Schleife aus.

Beispiel einer Haupt-Methode:

static void Main()
{
   CPerson P = new CPerson( "Hansen" ) ;
   CKunde K = new CKunde( "Meier", 123 );
   CMitarbeiter M = new CMitarbeiter( "Kaiser", "EDV" );

   Collections.ArrayList AL = new Collections.ArrayList();

   AL.Add( P );
   AL.Add( K );
   AL.Add( M );

   foreach( object oB in AL )
      Console.WriteLine( oB.ToString() );
}

Ausgabe:
Name: Hansen
Name: Maier - Kunden-ID: 123
Name: Kaiser - Abetilung: EDV

1.1.3 Assoziation

Auf beiden Seiten 0..*.

class Artikel
{
   private ArrayList BListe;
   public void Add(Bestellung B)
   {  
      BListe.Add(B);     //nur Verweise
   }
   :
}
class Bestellung
{
   private ArrayList PListe;
   public void Add(Artikel A)
   {  
      AListe.Add(A);     //nur Verweise
   }
   :
}

1.1.4 Aggregation

class Ganzes 
{ 
   private ArrayList TL;
   public void Add(Teil T)
   { 
      TL.Add(T); //Verweis   
   }
}
class Teil 
{
   private Ganzes G; //optional
   public Teil(Ganzes G)
   {
      G.Add(this); 
      this.G = G; //optional  
   }
}
static void Main()
{
   Ganzes G = new Ganzes();
   Teil T1 = new Teil(G);
   Teil T2 = new Teil(G);
}

1.1.5 Komposition

class Ganzes
{
   private ArrayList TL = new ArrayList();
   public void Add()  
   {  
      TL.Add( new Teil() ); 
   }
}

Arbeitsauftrag:

  1. Setzen Sie das Grundgerüst des Klassendiagramms für die Seminarverwaltung in C# um. Erstellen Sie nur die Klassen und die für die Verbindungen der Klassen notwendigen Attribute. Weitere Attribute und Methoden sollen nicht erstellt werden. Achten Sie dabei auf die korrekte Umsetzung der Vererbungen, Assoziationen, Aggregationen und Kompositionen.
    Kommentieren Sie den Quelltext ausführlich.
Datei:Seminarverwaltung.png

1.2 Überladen von Operatoren

Man unterscheidet unäre Operatoren (z.B. ++), die auf einen Operanden wirken und binäre Operatoren (z.B. +,-,*,/), die auf zwei Operanden wirken. Jeder Operator besitzt eine eigene Semantik, Orientierung und hat einen bestimmten Vorrang.

Teil der Definition der Klasse CBruch:

class CBruch 
{	
	/////////////////////////////////////////////////////////////////////
	// Assesoren
	public int Z
	{ get; private set; }

	public int N
	{ get; private set; }

	
	/////////////////////////////////////////////////////////////////////
	// Konstruktoren
	public CBruch()
	{
		Z = 0;
		N = 1;
	}
	public CBruch( int a, int b )
	{
		Z = a;
		if (b == 0)
			N = 1;
		else
			N = b;
	}
	public CBruch( int a )
	{
		Z = a;
		N = 1;
	}

	/////////////////////////////////////////////////////////////////////
	// Ueberladen des +-Operators
	public static CBruch operator + ( CBruch lhs, CBruch rhs )
	{
		...;
	}
}

Hauptprogramm, in dem zwei Brüche addiert werden sollen:

class Program
	{
		static void Main()
		{
			CBruch cbA = new CBruch( 3,4 );
			CBruch cbB = new CBruch( 5,6 );
			CBruch cbC = new CBruch();
			
			cbC = cbA + cbB;   // Fehler: Additionsoperator nicht bekannt
			
			Console.WriteLine( cbC.Z + „/“ + cbC.N );
		}
	}

Arbeitsauftrag:

  1. Erstellen Sie eine Klasse Bruch mit den genannten Konstruktoren, den Eigenschaften, einer Ausgabefunktion und den arithmetischen Operatoren + - * /.
  2. Führen Sie die Berechnung c = b + a * ( a – b ) für a=5/6 und b=7/8 aus. Kürzen Sie das Ergebnis und geben Sie es als Bruch aus.
  3. Im ersten Schritt ist die Ein- und Ausgabe über die Konsole ausreichend. Erweitern Sie das Programm um eine grafische Oberfläche.

1.3 Verkettete Liste

In dieser Aufgabe entwickeln Sie eine doppelt verkettete Liste[1], [2] (Klasse CLinkedList). Die Listenglieder sind vom Typ Node und enthalten jeweils eine Referenz vom Typ Node auf den Vorgänger bzw. Nachfolger des Listenglieds. Zusätzlich gibt es in Node eine (generische) Referenz vom Typ object auf die eigentlichen Listenelemente (d.h. auf die Nutzdaten, die in der Liste gespeichert werden sollen).

Der Zugriff auf Listenelemente erfolgt über Iteratoren. Iteratoren repräsentieren eine Position in der Liste und dienen zum Durchlauf durch die Liste. Dazu kapseln Iteratoren als internen Zustand eine aktuelle Position in der Liste.

Die möglichen Operationen auf einem Iterator sind durch das folgende Interface[3] definiert.

public Interface IIterator
{
  // Wechselt auf naechstes Glied in der Liste.
  // Liefert false, wenn Ende der Liste erreicht, sonst true.
  bool Next();
  // Wechselt auf das vorhergehende Glied in der Liste.
  // Liefert false, wenn Anfang der Liste erreicht, sonst true.
  bool Previous();
  // Auslesen und Setzen des aktuellen Listenelements, auf das
  // der Iterator zeigt.
  object Element{ get; set;}
}

Mit Hilfe der Iteratoren können nun die üblichen Listenoperationen als Methoden der Klasse CLinkedList realisiert werden.

  1. Realisieren Sie Konstruktoren für die CLinkedList. Es soll möglich sein, eine leere Liste anzulegen. Zusätzlich soll eine Liste erzeugt werden können, die mit den Elementen eines Arrays vom Typ object initialisiert wird.
  2. Schreiben Sie ein Property Length, das die Länge (Anzahl der Elemente) der Liste zurückgibt.
  3. Implementieren Sie das Interface IIterator in der Klasse CListIterator. Welche zusätzlichen Elemente (Felder, Methoden, Konstruktoren etc.) brauchen Sie in der Klasse CListIterator? Fügen Sie diese hinzu!
  4. Entwickeln Sie die Methoden GetHead() und GetTail(), die Ihnen Iteratoren zurückliefern, die auf den Anfang bzw. das Ende der Liste zeigen.
  5. Realisieren Sie die Methode AddAfter(IIterator position, object element), die ein Element hinter der mit dem Iterator angegebenden Position in die Liste einfügt.
  6. Realisieren Sie die Methode AddBefore(IIterator position, object element), die ein Element vor der mit dem Iterator angegebende Position in die Liste einfügt.
  7. Realisieren Sie die Methode Remove(IIterator position), die das Element aus der Liste entfernt, auf das der Iterator zeigt.
  8. Schreiben Sie eine Methode Contains(), die testet, ob ein gewisses Element in der Liste enthalten ist. Wird das Element gefunden, so gibt Contains() einen Iterator auf das Element zurück, ansonsten null. Wie können Sie Elemente auf Gleichheit prüfen?
  9. Implementieren Sie eine Methode Print(), die Ihnen die Elemente der Liste auf der Konsole ausgibt.
  10. Testen Sie Ihre verkettet Liste sorgfältig.
(Lösungsansatz: Verkettete Liste)

2 Exception-Handling

Grundsätzlich unterscheidet man drei Arten von Fehlern:

Syntaxfehler
im Quelltext - werden vom Comiler gefunden.
Logische Fehler
Fehler in Programm-Logik - können durch Tests entdeckt werden.
Laufzeitfehler
Treten während der Laufzeit des Programms auf, wenn der Benutzer einen Wert eingibt, der zum Absturz des Programmes führt (z.B. Division durch 0). Ein mögliches Auftreten dieser Fehler muss vorhergesehen und vom Programm abgefangen werden. Anstatt eines Programmabsturzes muss der Benutzer eine aussagekräftige Fehlermeldung erhalten. Ausführliche Erläuterungen befinden sich in 'Laufzeitfehler behandeln' und 'Behandeln und Auslösen von Ausnahmen in .NET'.

Arbeitsauftrag:

  1. Erweitern Sie das Programm zur Bruch-Berechnung um ein umfangreiches Exception-Handling. Kommentieren Sie den Quelltext entsprechend und speichern Sie diesen ab.

3 Nachhaltige Software Entwicklung

Nachhaltigkeit gewinnt in vielen Lebensbereichen zunehmend an Bedeutung, obwohl der Begriff nicht neu ist und erstmals 1713 erwähnt wurde. Auch bei der Planung und Entwicklung von Software ist es wichtig auf eine sparsame Nutzung der Ressourcen zu achten, auch aus dem Grund der Performancesteigerung.

Die folgenden Links stellen einen kurzen Einstieg dar:

Arbeitsauftrag:

  1. Nennen Sie drei Maßnahmen durch die der Energieverbrauch einer Anwendung reduziert werden kann und erläutern Sie diese jeweils an einem Beispiel.
  2. Nennen Sie die vier Verbraucher, die bei der Entwicklung von Software berücksichtigt werden müssen und erläutern Sie die Beziehung, in der diese zueinander stehen.
  3. Wie kann die Energieeinsparung durch Maßnahmen in der Programmierung festgestellt werden?
  4. Nennen Sie Beispiele aus Ihrem beruflichen Umfeld, in denen eine Reduzierung des Energieverbrauchs durch die oben genannten Maßnahmen möglich wäre.

4 Literatur*

  1. Dr. Klaus Ringhand, Hans-Georg Wittmann: Entwickeln und Bereitstellen von Anwendungssystemen für IT-Berufe. Westermann, Braunschweig 2011, ISBN: 978-3-8045-5384-2
  2. Andreas Kühnel: Visual C# 2010 Das umfassende Handbuch. Rheinwerk Computing, Bonn 2010, ISBN: 978-3-8362-1552-7[4]

[*] Bei der angegebenen weiterführenden Literatur handelt es sich teilweise nicht um Schulbücher. Der Erwerb ist für den Unterricht nicht erforderlich und nicht vorgeschrieben. Die Bücher geben die Möglichkeit, sich tiefer in ein Teilgebiet ein zu arbeiten.