Grundprinzipien

Strukturierte Programmierung

In der strukturierten (prozeduralen) Programmierung werden Problemstellungen in Prozeduren und Funktionen auf der einen Seite und Daten auf der anderen Seite zerlegt. Dabei steht immer im Vordergrund was zu tun ist und nicht womit etwas getan werden muss. Zur Bearbeitung der Daten stehen lokale und globale Variablen zur Verfügung, wodurch gesteuert wird welche Funktionen auf welche Daten zugreifen dürfen.

AWP - Strukturierte Programmierung

Mit zunehmender Komplexität der Programme und Datenstrukturen wird die Wartung der Software immer schwieriger, da bei einer Änderung der Datenstruktur auch die Funktionen angepasst werden müssen, die darauf zugreifen.

Darüber hinaus wird es mit zunehmender Komplexität auch immer schwieriger, Problemstellungen in passende Funktionen und Datenstrukturen zu zerlegen.

Diese und weitere Probleme führten zu einem neuen Ansatz in der Softwareentwicklung, dem objektorientierten Ansatz.

Objektorientierter Ansatz

Statt ein Problem in Teilprobleme zu zerlegen, und diese durch Unterprogramme, wie in der strukturierten Programmierung, zu lösen, wird hier eine komplexe Problemstellung durch seine eigene Begriffswelt erläutert und implementiert. Das Objekt ist hier der zentrale Punkt.

Eine Problemstellung wird nicht in einzelne Unterprogramme zerlegt, sondern in einzelne Objekte die untereinander in Beziehung stehen gegliedert.

AWP - Objektorientierter Ansatz

Beispiel Fakturierung:
Diese komplexe Problemstellung wird in einzelne Objekte wie z. B. Kunde, Rechnung, Rechnungsposition, etc. zerlegt.

Kennzeichen objektorientierter Sprachen

Objekt

Ein Objekt abstrahiert einen Teil des Problembereichs und repräsentiert beispielsweise ein Ding, eine Person, einen Begriff, einen Vorgang, einen Zusammenhang.

Synonyme für Objekt: Instanz, Exemplar.

G. Booch definiert ein Objekt folgendermaßen:

  • Ein Objekt hat einen Status (Eigenschaften, Zustand, Daten),
  • es weist ein wohldefiniertes Verhalten auf (Operationen , Methoden) und
  • es besitzt eine eindeutige Objektidentität.
Eigenschaft

Eigenschaften von Objekten werden auch Attribute oder Membervariablen (in C++ und Java) genannt und werden in Form von Daten (Variablen) gespeichert.

Diese Daten sollen in der Regel nicht öffentlich, sondern in der Klasse gekapselt sein. Dies bedeutet, dass Eigenschaften nicht beliebig von außen veränderbar sind.

Methode

Methoden bezeichnen Funktionen, die von einem Objekt ausgeführt werden. Die Begriffe Methode, Operation, Dienst werden synonym verwendet. In C++ spricht man auch von Memberfunktionen, in Java von Instanzmethoden. Methoden sind immer an Objekte gebunden! Das bedeutet, eine Methode lässt sich – anders als die sonst üblichen Funktionen, bzw. Unterprogramme – nur im Zusammenhang mit einem Objekt aufrufen.

Beispiel: Das Objekt „Mitarbeiter Edelmann“ hat die Eigenschaften Name und Gehalt, auf die nur über die Methoden Name ändern bzw. Gehalt ändern zugegriffen werden kann.

AWP - Objekt Beispiel

Klasse

Unter der Klasse versteht man die Beschreibung für alle Objekte, die dieser Klasse angehören. Coad/Yourdan definieren eine Klasse als eine Sammlung von Objekten mit einer einheitlichen Menge von Attributen und Diensten, einschließlich einer Beschreibung zur Erzeugung neuer Objekte dieser Klasse. Die reine Existenz einer Klasse sagt jedoch noch nichts aus über die Anzahl der Objekte, die zu einem bestimmten Zeitpunkt des Programmablaufs existieren.

Eine Klasse spezifiziert also die gemeinsamen Eigenschaften und das gemeinsame Verhalten der von ihr erzeugten Objekte. Die Klassenbeschreibung umfasst

  • Interface (die öffentliche Schnittstelle) und
  • Implementierungen (der Methoden und Klasseneigenschaften).

Folgende Abbildung veranschaulicht den Vergleich einer Klasse mit einer Schablone anhand eines Prägestempels, mit dem beliebig viele, identische Abdrücke (Objekte) erstellt werden können.

AWP - Stempel

Folgendes Beispiel stellt den Zusammenhang noch einmal dar:

AWP - Stempel UML

Vererbung

Die Idee der Klassen führt zur Idee der Vererbung. Im täglichen Leben benutzen wir wie selbstverständlich das Prinzip der Klassen, die sich wiederum in Unterklassen aufteilen. So wird zum Beispiel die Klasse der Tiere in Säugetiere, Insekten, Vögel, usw. aufgegliedert. Die Klasse der Fahrzeuge unterteilt sich u. a. in Personenwagen, Lastwagen, Busse und Motorräder.

Es entstehen Klassenhierarchien, die aus Ober- und Unterklassen bestehen. Oberklassen sind allgemeiner und auf einem abstrakteren Niveau gehalten als Unterklassen, die konkreter und spezieller sind. Die Bildung einer Klassenhierarchie wird durch Generalisierung und Spezialisierung erreicht. Bei der Generalisierung werden Gemeinsamkeiten zwischen Objekten verschiedener Klassen gesucht und in einer generalisierten Oberklasse festgehalten. Bei der Spezialisierung wird eine bestehende Klasse um spezielle Eigenschaften erweitert, so dass eine spezialisierte Unterklasse entsteht. Die Umsetzung dieses Konzepts wird Vererbung genannt.

Die Kindklasse erbt alle Eigenschaften/Methoden der Elternklasse. Sie kann auf der Basis dieser Eigenschaften/Methoden weitere einführen oder die der Elternklasse neu definieren. Elternklassen fassen die allgemeinen Eigenschaften/Methoden von mehreren Kindklassen zusammen.

Bei einer Hierarchie, wie unten dargestellt, erbt eine Unterklasse immer von einer Oberklasse. Es handelt sich hierbei um Einfachvererbung.

AWP - Einfachvererbung

Betrachtet man jedoch ein Beispiel aus der Biologie (siehe unten) so sieht man, dass Unterklassen nicht nur von einer Oberklasse, sondern auch von mehreren Oberklassen erben können. Diesen Zusammenhang bezeichnet man auch als Heterarchie, oder im objektorientierten Softwaredesign als Mehrfachvererbung.

Wie bereits erwähnt sind übergeordnete Klassen abstrakter als die untergeordneten. Häufig sind sie so abstrakt, dass davon keine konkreten Objekte gebildet werden. So würde zum Beispiel nie eine Instanz der Klasse Tier erzeugt. Solche Klassen, von denen keine Instanzen gebildet werden, nennt man abstrakte Klassen.

Klassen, von denen Objekte existieren, bezeichnet man als konkrete Klassen. Die oberste Klasse im Verzeichnisbaum (z. B. Tiere) ist die sog. Basisklasse.

In der Softwareentwicklung muss noch zwischen zwei Sichtweisen unterschieden werden, die in den einzelnen Programmiersprachen verschieden implementiert sind:

  • Vererbung des Interface Nur die Methoden, über die Eigenschaften der Objekte verändert werden, werden vererbt.
  • Vererbung der Implementierung Auch die implementierten Eigenschaften werden an die Unterklasse vererbt.

Auf diesen Zusammenhang wird in der Realisierung der objektorientierten Programmierung noch eingegangen.

AWP - Heterachie

Assoziation

Klassen und Objekte für sich genommen haben wenig Sinn bzw. sind nicht produktiv. Erst durch die Beziehungen untereinander entsteht ein System, das die Grundlage für eine Software bildet. Wenn man ein Computersystem betrachtet, so sind die einzelnen Komponenten wie Drucker, Monitor, Tastatur für sich genommen nicht gerade nutzbringend. Erst durch die Verbindungen untereinander entsteht ein brauchbares System. Ebenso verhält es sich in einem objektorientierten Softwaremodell. Erst wenn die Objekte interagieren und ihre Klassen in Beziehungen stehen, wird daraus ein brauchbares System.

Eine sog. Assoziation sagt lediglich aus, dass zwischen zwei Klassen eine Beziehung besteht. Neben der Assoziation gibt es auch noch sog. “part of”-Beziehungen (Komposition und Aggregation), aber dazu im Rahmen von UML mehr.

AWP - Assoziation

Nachricht

Die Interaktion der Objekte wird über Nachrichtenaustausch realisiert. Eine Nachricht (Botschaft, message) ist eine Aufforderung eines (sendenden) Objekts an ein (empfangendes) Objekt, eine Methode auszuführen. Beispielsweise kann das Objekt Meier der Klasse Kunde die Nachricht getNettosumme an das Objekt der Klasse Rechnung schicken, um die Nettosumme zu erhalten. Bei Nachrichten müssen allerdings keine Daten übertragen werden. Es geht auch nicht darum, wer an wen Daten liefert, sondern wer wessen Methoden aufruft.

Sichtbarkeit

Wie wichtig der Begriff der Sichtbarkeit bzw. der Kapselung ist, soll am Beispiel der Ampelsteuerung erklärt werden. Betrachtet man eine bestimmte Ampel als Objekt, so könnten rotes Licht, gelbes Licht und grünes Licht als einzelne Attribute aufgefasst werden. Wären diese Eigenschaften nach außen uneingeschränkt sichtbar, so könnten durch Programmzugriffe auch Zustände erzeugt werden die nicht erlaubt sind, z. B.: rotes und grünes Licht leuchten.

Deshalb wird die Sichtbarkeit eingeschränkt, die Daten werden gekapselt. Dies bezeichnet man auch als information-hiding. Der Zugriff erfolgt hierbei über Methoden, die in der Ampelklasse implementiert sind, und nur erlaubte Zustände zulassen.

Prinzipiell kennt die objektorientierte Programmierung (OOP) drei verschiedene Arten von Sichtbarkeit:

  • Public (+): Die Methoden und Attribute, die als public (öffentlich) deklariert werden, sind uneingeschränkt sichtbar.
  • Private (-): Die privaten Methoden und Attribute sind nur innerhalb der Klasse verfügbar, in der sie als private deklariert wurden.
    Von außen kann man nicht auf sie zugreifen.
  • Protected (#): Die geschützten Methoden und Attribute sind nicht nur innerhalb der Klasse, in der sie als protected deklariert wurden, ansprechbar, sondern auch innerhalb der von dieser Klasse abgeleiteten Klassen.
    Von außen kann man nicht auf geschützte Methoden und Attribute zugreifen.
Polymorphismus (Vielgestaltigkeit)

Der Begriff Polymorphismus bedeutet, dass eine Nachricht, die an verschiedene Objekte gesendet wird, dort zu unterschiedlichen Reaktionen führen kann.

So führt die Nachricht drucke() an die Klasse Konto gerichtet zu einem anderen Ergebnis als die gleiche Nachricht drucke() an die Klasse Kunde gerichtet. Dies ergibt sich daraus, dass die Methode drucke() in Konto anders implementiert ist als in Kunde.

Dies ist nicht nur möglich bei voneinander unabhängige Klassen, sondern auch bei Klassen die in einer Vererbungslinie stehen. So hat die Methode drucke() in Drucker (Basisklasse) andere Inhalte als drucke() in Laserdrucker (von Drucker abgeleitete Klasse). Dies wird möglich, durch das sog. Überschreiben von Methoden der Oberklasse durch Methoden der Unterklasse.

Durch das Überladen von Methoden kann der Name einer Methode innerhalb einer Klasse auch mehrmals verwendet werden. Die einzelnen Methoden (die man sich ja als Funktionen vorstellen kann) benötigen nur unterschiedliche Parameter zur Unterscheidung (Unterscheidungskriterien: Datentyp und Anzahl der Parameter). So könnte die Methode drucken() mit unterschiedlichen Parametern zu unterschiedlichen Ausgaben führen.

Klassen, Objekt, Instanz

Klassen

  • Bauplan für eine Reihe von ähnlichen Objekten
  • Aus einer Klasse können beliebig viele Objekte erzeugt werden
  • Beschreibt welche Attribute die Objekt-Instanzen der Klasse haben
  • Beschreibt welche Methoden die Objekt-Instanzen der Klasse haben
  • Klasse ist vereinfacht ausgedrückt ein eigener Datentyp
  • Klassen belegen zur Ausführungszeit des Programmes keinen Arbeitsspeicher
class C_Car
{
    // Attributes
    private int idoors;
    private bool bautoamtic;
    private string smanufacturer;
    private string scolour;
    
    // Methods
    public void setDoors(int pdoors) {
        idoors = pdoors;
    }
    
    public void setManufacturer(string pmanufacturer) {
        smanufacturer = pmanufacturer
    }
    
    public string getManufacturer() {
        return smanufacturer;
    }
    
    public string GetModel(string pManufacturer) {
        switch(pManufacturer)
        {
            case "Audi":
                return "A4";
            case "BMW":
                return "3";
        }
    }
}

Objekte

  • Aus dem Bauplan (=Klasse) können Objekte instanziiert werden, die sich durch Attribute / Methoden unterscheiden
  • Attribute = Eigenschaften
  • Erzeugung durch Schlüsselwort new
  • Konkrete Realisierung einer Klasse
  • Instanz und Objekt werden oft synonym verwendet
  • Instanz bezieht sich auf ein ganz bestimmtes Objekt einer Klasse
  • Beispiel: Hausbauplan => Klasse, Häuser => Objekte, bestimmtes Haus => Instanz
class Programm
{
    // Create an Object called myCar
    C_Car myCar = new C_Car();
    
    Console.Write("Enter manufacturer: ");
    myCar.setManufacturer(Console.ReadLine());
    
    Console.WriteLine("Current manufacturer ist: " + myCar.getManufacturer());
}

Eigenschaften, Methoden

Eigenschaften

  • Informationen, die einem Objekt zugeordnet sind
  • Haben immer einen Datentyp und eine Sichtbarkeit
  • Sichtbarkeit sollte nur public sein, wenn unbedingt notwendig
  • Existiert für jede Instanz einer Klasse, der Inhalt kann sich aber unterscheiden

Methoden

  • Funktionen, die Teil einer Klasse sind
  • Kann die Eigenschaften einer Klasse wie globale Variablen verwenden
  • Muss beim Aufruf einen Bezug auf die Klasse oder einer dessen Instanzen haben
  • Haben eine Sichtbarkeit und können statisch oder nichtstatisch sein
  • Besondere Methode: Konstruktor, Main

Beispiel

class PropertiesAndMethods
{
    public string sName;
    private int _iNr;
    
    public void SetNumber(int number)
    {
        _Nr = number;
    }
    
    public void Print()
    {
        Console.WriteLine(String.Format("Name: {0}, Nr: {1}", sName, _iNr));
    }
}

class Programm
{
    static void Main(string[] args)
    {
        PropertiesAndMethods testClass = new PropertiesAndMethods();
        testClass.SetNumber(1);
        testClass.sName = "Max Mustermann";
        testClass.Print();
    }
}

Datenkapselung, public-private

Sichtbarkeitsarten

  • Elemente wie Klassen, Funktionen und Attribute können verschiedene Sichtbarkeitsformen annehmen.
  • Sichbarkeitsmodifikatoren können den Zugriff auf verschiedene Elemente verhindern.

Dies nennt man Datenkapselung

Public

Alle public-Elemente können innerhalb und außerhalb der eigenen Klasse gelesen oder aufgerufen werden.

Private

Alle private-Elemente können nur innerhalb der eigenen Klasse gelesen oder aufgerufen werden, uudem sind in C# alle Elemente ohne Modifikator automatisch private.

Beispiel

class Program
{
    static void Main(string[] args)
    {
        Person greg = new Person("Gregor");
        greg.SayHello();
        greg.name = "Christoph"; // Compiler Error - Access denied due to the protection level
    }
}

class Person
{
    private string name;
    public void SayHello()
    {
        Console.WriteLine("Hallo, ich bin " + name);
    }
}

Konstruktor, Destruktor

Definition

  • Als Konstruktoren und Destruktoren werden in der Programmierung spezielle Prozeduren oder Methoden bezeichnet, die beim Erzeugen bzw. Auflösen von Objekten und Variablen aufgerufen werden.
  • Die Aufgabe von Konstruktoren ist, Objekte in einen definierten Anfangszustand zu bringen und so benötigte Ressourcen zu reservieren, insofern diese zum Zeitpunkt der Objekterstellung bereits bekannt sind.
  • Destruktoren sind in der Regel dafür verantwortlich, vom Objekt benutzte Ressourcen freizugeben.

Aufbau

  • Besitzt immer den Namen der Klasse
  • Hat keinen Rückgabetyp
  • Existiert immer(Defaultkonstruktor), selbst wenn nicht explizit erstellt, sobald aber ein eigener implementiert wird, ist dieser nicht mehr automatisch verfügbar

Beispiel

// Default Konstruktur
public Auto()
{
    Console.WriteLine("Konstruktor Default);
}
// Konstruktor mit Variablen
public Auto(String Name, String Motor, int size)
{
    sName = Name;
    sMotor = Motor;
    iSitze = Sitze;
    Console.WriteLine("Konstruktor 3 Arg");
}
// Konstruktor mit Konstruktor-Aufruf
public Auto(int Sitze): this("1 Variable", V6", Sitze)
{
    Console.WriteLine("Konstruktor 1 Arg");
}
// Destruktor
~Auto()
{
    Console.WriteLine(sName + " wurde zerstoert");
}

static void createAuto()
{
    Auto auto = new Auto();
    Auto auto2 = new Auto(4);
    Auto auto3 = new Auto("Mercedes", "v8", 5);
}

Die Ausgabe nachdem man die Methode createAuto() aufgerufen hat ist:

Konstruktor 3 Arg (Default)
Konstruktor Default
Konstruktor 3 Arg (1 Variable)
Konstruktor 1 Arg
Konstruktor 3 Arg (Mercedes)
Mercedes wurde zerstoert
1 Variable wurde zerstiert
Default wurde zerstoert

Getter, Setter

Vererbung, Polymorphie, Überschreiben

Polymorphie

Gibt es in einem Vererbungszweig einer Klassenhierarchie mehrere Methoden auf unterschiedlicher Hierarchieebene, mit gleicher Signatur jedoch mit unterschiedlicher Implementierung, wird erst zur Laufzeit bestimmt welche der Methoden für ein gegebenes Objekt verwendet wird.

Beispiel: Methode drucke() in zwei in einer Vererbungslinie stehenden Klassen

AWP - Polymophie

Methode drucke() der Unterklasse überschreibt die Methode drucke() der Oberklasse.

Überladen

Static – Klassen-methoden/-eigenschaften

Interfaces

Beispiel: UML zu C# Code

Folgendes UML Klassendiagramm ist gegeben:

AWP - UML Beispiel

Hieraus ergibt sich folgender Quellcode:

class CAuto
{
    // Eigenschaften (sind private -> Prinzip der Datenkapselung)
    private float fLeistung;
    private string sFarbe = "undefiniert";
    // Methoden (sind meist public -> öffentliche Schnittstelle)
    // Konstruktor (wird beim Erzeugen eines Objektes ausgeführt und kann überladen werden)
    public CAuto()
    {
        // besser kein direktes Setzen der Eigenschaft, damit eventuelle Zusicherungen nicht umgangen werden
        setLeistung(10);
        sFarbe = "schwarz";
    }
    public CAuto(float fLeistung, string sFarbe)
    {
        setLeistung(10); // 10 gesetzt, damit nach folgendem Setter wenigstens 10 in fLeistung steht
        setLeistung(fLeistung);
        this.sFarbe = sFarbe;
    }
    // Destruktor (wird beim Zerstören eines Objektes ausgeführt)
    ~CAuto()
    {
        Console.WriteLine("Auto mit der Farbe " + this.sFarbe + " wird zerstört");
        Console.ReadLine();
    }
    // set- und get-Methoden zum Schreiben  und Lesen der private-Eigenschaften
    public string getFarbe()
    {
        return this.sFarbe;
    }
    public void setFarbe(string sFarbe)
    {
        this.sFarbe = sFarbe;
    }
    public float getLeistung()
    {
        return fLeistung;
    }
    public bool setLeistung(float fLeistung)
    {
        bool NeueLeistungGesetzt = false;
        // Zusicherung, dass die Leistung immer >= 10
        if (fLeistung >= 10) {
            this. fLeistung = fLeistung;
            NeueLeistungGesetzt = true;
        }
        // Rückgabe, ob die Leistung geändert wurde oder der alte Wert bleibt
        return NeueLeistungGesetzt;
    }
}

class Program{
    static void Main(string[] args){
        CAuto MeinAuto = new CAuto(100, "rot"); // Erzeugen eines Objektes
        // indirekter Zugriff auf die private-Eigenschaften des Objektes über Aufrufe der public-Methoden
        MeinAuto.setLeistung(150);
        Console.WriteLine("Farbe: " + MeinAuto.getFarbe());
        Console.WriteLine("Leistung: " + MeinAuto.getLeistung() + " PS");
        Console.ReadLine();
    }
}