Programmierkurs C-Sharp: Objekte und Klassen

Aus Wikibooks

Wechseln zu: Navigation, Suche
Nuvola apps bookcase.svg Regal: Programmierung Nuvola apps bookcase 1.svg Programmierkurs C# Nuvola mimetypes dvi.png Objekte und Klassen, der erste Blick


Wikipedia
Wikipedia
Wikipedia hat einen Artikel zum Thema:
Wikipedia
Wikipedia hat einen Artikel zum Thema:

Inhaltsverzeichnis

[Bearbeiten] Objekte und Klassen

Objekte und Klassen bilden das Fundament der objektorientierten Programmierung.

Selbstverständlich gibt es dafür wunderschöne Definitionen; für uns reicht es aber zunächst, zu wissen, dass Objekte einzelne Exemplare (Instanzen) von Klassen sind. Klassen wiederum stellen so etwas wie eine „abstrakte Form“ dar, eine Sammlung von Eigenschaften und Methoden (Methoden entsprechen den Funktionen, die diese Klasse definiert). Im unten stehenden Sandkastenbeispiel entspricht eine Methode einer Aktion, die man mit einem Förmchen durchführen kann, also z.B. einen Sandkuchen damit formen. Klassen können von anderen Klassen abgeleitet werden, sie erben damit die Eigenschaften der Klasse von der sie abgeleitet sind. In C# ist jede Klasse implizit (also ohne, dass man es erwähnen muss) von der grundlegenden Klasse object abgeleitet und erbt ihre Eigenschaften.

Und weil objektorientierte Programmierung keineswegs das Ziel hat, alles schön kryptisch und kompliziert aussehen zu lassen, sondern eigentlich vielmehr beabsichtigt, das reale Leben in der Programmierung abzubilden, ist das dahinter steckende Grundprinzip auch hier dem Leben entnommen.

Wenn wir an unsere Sandkastenzeit zurückdenken, sind Klassen also die Sandförmchen. Und die schönen Kuchen, Türmchen, Muscheln, und was wir sonst noch so alles „gebacken“ haben, sind Objekte. Es ist logisch, dass man aus einer Form nahezu unendlich viele Kuchen und Türmchen machen kann, oder?! Und die Resultate unserer Anstrengungen sahen stets gleich aus; egal wie oft wir das Förmchen bemüht haben.

Mit Klassen verhält es sich ganz genauso. Nachdem wir uns einmal der Mühe unterzogen haben, die Klasse mit all ihren Attributen, Eigenschaften, Ereignissen, Feldern, Konstanten, Methoden und Variablen zu konstruieren, können wir beliebig viele Objekte erzeugen.

class Auto
{
}

So einfach sieht eine Klasse aus. Allerdings kann diese Klasse auch noch nichts – außer da sein (und das, was die Klasse object schon kann). Es ist nur eine leere Form. Und doch können wir schon das erste Objekt (oder auch beliebig viele Objekte) davon erzeugen:

Auto m_Objekt = new Auto();

Weil aber Objekte ohne jede Funktion ziemlich langweilig sind, erweitern wir die Klasse um ein paar Felder.

[Bearbeiten] Felder (Member)

Nehmen wir dazu eine Nummer, die Farbe und den Typ.

class Auto
{
  public int m_Nummer;
  public string m_Farbe;
  public string m_Typ;
}

Wenn wir uns jetzt ein Objekt dieser Klasse holen, können wir damit schon etwas anstellen. Der Zugriffsmodifizierer public zeigt dabei an, dass dieses Feld öffentlich zugänglich ist. Der Datentyp int bedeutet, dass m_Nummer eine beliebige Ganzzahl aufnehmen soll, während string bedeutet, dass m_Farbe und m_Typ einen beliebigen Text aufnehmen sollen.

// Objekt von der Klasse holen.
Auto m_Objekt = new Auto();

// Den Feldern Daten zuweisen.
m_Objekt.m_Nummer = 1;
m_Objekt.m_Farbe = "Rot";
m_Objekt.m_Typ = "Audi";

Das war einfach, nicht wahr? Aber jetzt taucht schon das erste Problem auf: Der öffentliche Zugriff auf die Felder erlaubt nun jedem, diese Daten beliebig zu ändern. Das ist eigentlich noch kein Problem, aber was passiert, wenn wir möchten, dass die Zahl m_Nummer a) nicht negativ und b) nicht größer als 2.000 sein darf? Und was ist, wenn wir nicht wollen, dass jemand den Typ m_Typ verändert, man ihn aber trotzdem lesen können soll?

Dafür gibt es die Eigenschaften.

[Bearbeiten] Eigenschaften

Eigenschaften kapseln die Felder und erlauben uns, ihren Zugriff gezielt zu steuern. Um das zu erreichen, modifizieren wir unsere Klasse ein bisschen. Zunächst setzen wir den Zugriffsmodifizierer auf protected, also „geschützt“. Damit verhindern wir, dass man diese Felder von außen bearbeiten kann.

class Auto
{
  protected int m_Nummer;
  protected string m_Farbe;
  protected string m_Typ;
}

Anschließend fügen wir die Eigenschaften hinzu:

class Auto
{
  protected int m_Nummer;
  protected string m_Farbe;
  protected string m_Typ;

  // Eigenschaften
  public int Nummer
  {
    // Was soll passieren, 
    // wenn jemand die Nummer lesen möchte?
    get { return m_Nummer; }

    // Was soll passieren, 
    // wenn jemand die Nummer schreiben möchte?
    set 
    { 
      // Wir prüfen, ob der Wert größer als 0
      // und kleiner oder gleich 2000 ist.
      // Wenn ja, dann übernehmen wir den Wert.
      // Wenn nein, dann ignorieren wir diesen Versuch, 
      // fehlerhafte Daten einzugeben.
      if ((value > 0) && (value <= 2000))
        m_Nummer = value;
    }
  }

  public string Farbe
  { 
    // Was soll passieren, 
    // wenn jemand die Farbe lesen möchte?
    get { return m_Farbe; }

    // Was soll passieren, 
    // wenn jemand die Farbe schreiben möchte?
    set { m_Farbe = value; }
  }

  // Schreibgeschützte Eigenschaft erstellen
  public string Typ
  {
    get { return m_Typ; }
    
    // Wenn wir die Schreib-Möglichkeit weglassen,
    // kann niemand mehr schreiben.
    // Das Resultat ist eine Eigenschaft,
    // die nur gelesen werden kann.
  }  
}

Jetzt haben wir mit Hilfe einer einfachen Überprüfung sicher gestellt, dass niemand mehr falsche Werte eingeben kann:

// Objekt von der Klasse holen.
Auto m_Objekt = new Auto();

// Test:
// Der Versuch, der Nummer die Zahl -1 zu übergeben,
// müsste einfach ignoriert werden.
m_Objekt.Nummer = -1;

// Ausgabe = 0
Console.WriteLine(m_Object.Nummer);

Stimmt’s? Das war auch einfach, oder?

Wikipedia
Wikipedia hat einen Artikel zum Thema:

[Bearbeiten] Konstruktor

Bisher haben wir bei unseren Tests von einer Besonderheit des .NET profitiert. Wenn eine Klasse keinen Konstruktor besitzt, dann stellt der Compiler beim Übersetzen des Quellcodes selbst einen zur Verfügung (Standard-Konstruktor). Dieser Konstruktor beschreibt, wie genau das Objekt von der Klasse erzeugt werden soll. Steht dort nichts, gibt es auch keine Besonderheiten. Alle Felder des Objekts werden automatisch mit ihren Standardwerten initialisiert, also m_Nummer mit Null, die beiden Strings mit null, einer Art besonderer Null für sogenannte Verweistypen, also Klassen, die keine Werttypen (also z.B. alle Zahlentypen wie int, float usw.) sind.

Das hat bisher durchaus ausgereicht. Allerdings haben wir uns jetzt die Nur-Lese-Eigenschaft Typ eingebrockt. Und so, wie sie jetzt dasteht, ist sie schlicht unerreichbar. Schreiben dürfen wir sie nicht, da die Eigenschaft Typ schreibgeschützt, und das Feld m_Typ „geschützt“, also von außen nicht erreichbar, ist.

Hier hilft uns der Konstruktor weiter. Für Konstruktoren gibt es in C# nur wenige zwingende Regeln. Die wichtigsten beiden lauten: Der Konstruktor muss genauso wie die Klasse heißen und darf keinen Rückgabetyp haben.

public Auto() {}

So sieht der Konstruktor aus, den der Compiler bisher selbst klammheimlich eingebaut hat, wenn wir unsere Tests gemacht haben. Also können wir ihn auch gleich dazuschreiben, nicht wahr?

class Auto
{
  public Auto() {}
}

Und wenn wir schon dabei sind, beheben wir gleich das Problem mit dem Typ. Logischerweise wird der Typ eines Autos ein einziges Mal direkt bei der Produktion festgelegt; später kann er nicht mehr geändert werden. Wenn wir das auch machen wollen, müssen wir unseren Konstruktor nur minimal verändern:

class Auto
{
  public Auto(string typ)
  {
    m_Typ = typ;
  }
}

Natürlich können wir den Konstruktor noch um weitere Aufgaben ergänzen. Sinnvoll ist es sicherlich, auch die Nummer und die Farbe schon bei der Produktion festzulegen.

class Auto
{
  public Auto(int nummer, string farbe, string typ)
  {
    m_Nummer = nummer;
    m_Farbe = farbe;
    m_Typ = typ;
  }
}

Eine Klasse kann beliebig viele Konstruktoren enthalten, es mag schließlich Gelegenheiten geben, bei denen man die Farbe und die Nummer des Autos erst später festlegen will. Oder man will diese Festlegung jemand anderem überlassen.

Ein roter Audi wird jetzt so erzeugt:

// Objekt von der Klasse holen.
Auto m_RoterAudi = new Auto(1, "Rot", "Audi");

Einen Audi mit nicht festgelegter Nummer (vielleicht für eine Bananenrepublik?) und zum selbst Lackieren, erzeugt man auf diese Weise:

// Einen noch nicht genauer spezifizierten Audi erzeugen:
Auto m_RohAudi = new Auto("Audi");

In den Klammern übergeben wir die Startwerte. Auch den Typ. Et voilà, schon haben wir auch das Problem mit dem Nur-Lese-Typ erschlagen:

// Test des Typs
Auto m_RoterAudi = new Auto(1, "Rot", "Audi");

// Ausgabe = "Audi"
Console.WriteLine(m_RoterAudi.Typ);

Da wir aber nicht vorhaben, Autos für Bananenrepubliken zu produzueren, benutzen wir im Weiteren nur den vollständigen Konstruktor, der alle Felder der Klasse initialisiert.

Wikipedia
Wikipedia hat einen Artikel zum Thema:

[Bearbeiten] Methoden

Was wäre unser Auto ohne die Fähigkeit, den Motor starten, Gas geben, bremsen oder blinken zu können? All diese Fähigkeiten sind Aktionen, die man mit einem Auto im echten Leben durchführen kann. C# nennt diese Aktionen Methoden. Fügen wir also der Klasse Auto ein paar Methoden hinzu:

class Auto
{
  protected int m_Nummer;
  protected string m_Farbe;
  protected string m_Typ;

  public Auto(int nummer, string farbe, string typ)
  {
    m_Nummer = nummer;
    m_Farbe = farbe;
    m_Typ = typ;
  }

  public int Nummer
  {
    get { return m_Nummer; }
    set 
    { 
      if ((value > 0) && (value <= 2000))
        m_Nummer = value;
    }
  }

  public string Farbe
  { 
    get { return m_Farbe; }
    set { m_Farbe = value; }
  }

  public string Typ
  { 
    get { return m_Typ; }
  }

  // *****************************
  // Hier beginnt die Erweiterung:
  // *****************************

  public void StarteMotor() {}
  public void Beschleunige() {}
  public void Hupe() {}
  public void Blinke(bool links, bool rechts) {}
}

Nicht wirklich überraschend, oder? Schauen wir uns nun die Methode Blinke etwas genauer an:

public void Blinke(bool links, bool rechts) {}

Wir haben wieder unseren Zugriffsmodifizierer, wie wir ihn schon von den Feldern und Eigenschaften kennen.

public void Blinke(bool links, bool rechts) {}

Neu hinzu gekommen ist der Typ des Wertes, der von diesen Methoden zurück gegeben wird, der sogenannte Output der Methode. Soll nichts zurück gegeben werden, benutzen wir void, um anzuzeigen, dass wirklich gar nichts zurück zu erwarten ist. Das macht für das Blinken auch Sinn. Aber natürlich sind alle Datentypen erlaubt, also sowohl die Basisdatentypen, wie int, string, etc., komplexe Datentypen, wie etwa Datentabellen und Strukturen, oder sogar selbstdefinierte Typen, wie etwa eigene Klassen.

public void Blinke(bool links, bool rechts) {}

Auch der Name für eine Methode ist weder neu noch überraschend. Das kennen wir schon von Feldern und Eigenschaften: Was keinen Namen hat, kann man nicht ansprechen!

public void Blinke(bool links, bool rechts) {}

Aber jetzt kommt wieder etwas Neues: Die sogenannten Parameter. Meistens ist es notwendig, dass Methoden bestimmte Daten verarbeiten sollen. Diese Daten kann man auf dem Wege der Parameter-Übergabe an die Methode weiterreichen. Parameter sind also der Input einer Methode. Unsere Methode erwartet also die Übergabe der Information, welche Blinker eingeschaltet werden sollen. Der Datentyp bool sagt uns, dass wir einen Boolschen Wert (true oder false) übergeben dürfen.

public void Blinke(bool links, bool rechts) {}

Zwischen den geschweiften Klammern erwartet C# nun noch die Erklärung, was genau getan werden soll. Bisher steht hier noch nichts drin; die Methode macht also auch nichts. Das ändern wir jetzt. Zwischen die geschweiften Klammern schreiben wir nun unsere Aktion, die ausgeführt werden soll, wenn wir blinken möchten:

public void Blinke(bool links, bool rechts)
{
  // Sollen wir links blinken?
  if (links == true && rechts == false)
  {
    Console.WriteLine("Links blinken");
  } 
  
  // Sollen wir rechts blinken?
  if (links == false && rechts == true)
  {
    Console.WriteLine("Rechts blinken");
  }

  // Oder ist der Warnblinker an?
  if (links == true && rechts == true)
  {
    Console.WriteLine("Der Warnblinker ist an.");
  }
} 

Kompliziert? Nicht wirklich, oder?! Wir prüfen Schritt für Schritt, welcher der Parameter true ist, auf welchen wir also reagieren müssen. Sind beide Parameter true, bedeutet das, dass wir den Warnblinker einschalten möchten. Der Lohn all dieser Mühe ist wundervoll:

// Zuerst holen wir uns, wie üblich, ein Objekt der Klasse Auto
Auto m_Objekt = new Auto(1, "Rot", "Audi");

// Jetzt blinken wir abwechselnd erst links ...
m_Objekt.Blinke(true, false);

// ... dann rechts ...
m_Objekt.Blinke(false, true);

// ... und zum Schluss schalten wir den Warnblinker ein.
m_Objekt.Blinke(true, true);

Unser Programmcode wird wieder ein ganzes Stück übersichtlicher, denn die komplexe Funktionalität, was genau passieren soll, haben wir in der Methode Blinke völlig transparent versteckt, oder, wie man auch sagt: gekapselt. Und, wichtiger noch: Die Funktionalität steht nur an einer einzigen Stelle. Wenn wir später daran noch einmal arbeiten müssen, brauchen wir auch nur eine einzige Stelle zu ändern. Wir schreiben also viel schneller viel sauberere Programme.

[Bearbeiten] Quellcode: Klasse Auto

Unsere ganze Klasse sieht bis hierher jetzt so aus:


Crystal Clear app terminal.png C#-Code:  

using System;

namespace Org.Wikibooks.De.CSharp.ObjekteUndKlassen
{
  class Auto
  {
    protected int m_Nummer;
    protected string m_Farbe;
    protected string m_Typ;

    public Auto(int nummer, string farbe, string typ)
    {
      m_Nummer = nummer;
      m_Farbe = farbe;
      m_Typ = typ;
    }

    // Eigenschaften
    public int Nummer
    {
      // Was soll passieren, 
      // wenn jemand die Nummer lesen möchte?
      get { return m_Nummer; }

      // Was soll passieren, 
      // wenn jemand die Nummer schreiben möchte?
      set 
      { 
        // Wir prüfen, ob der Wert größer als 0
        // und kleiner oder gleich 2000 ist.
        // Wenn ja, dann übernehmen wir den Wert.
        // Wenn nein, dann ignorieren wir diesen Versuch, 
        // fehlerhafte Daten einzugeben.
        if ((value > 0) && (value <= 2000))
          m_Nummer = value;
      }
    }
  
    public string Farbe
    { 
      // Was soll passieren, 
      // wenn jemand die Farbe lesen möchte?
      get { return m_Farbe; }
  
      // Was soll passieren, 
      // wenn jemand die Farbe schreiben möchte?
      set { m_Farbe = value; }
    }
  
    public string Typ
    {
      get { return m_Typ; }
    }
  
    public void StarteMotor() {}
    public void Beschleunige() {}
    public void Hupe() {}
  
    public void Blinke(bool links, bool rechts)
    {
      // Sollen wir links blinken?
      if (links == true && rechts == false)
      {
        Console.WriteLine( "Links blinken" );
      } 
     
      // Sollen wir rechts blinken?
      if (links == false && rechts == true)
      {
        Console.WriteLine( "Rechts blinken" );
      }
  
      // Oder ist der Warnblinker an?
      if (links == true && rechts == true)
      {
        Console.WriteLine( "Der Warnblinker ist an." );
      }
    } 
  }
}


[Bearbeiten] Siehe auch

Wikipedia: Klasse
Wikipedia: Basisklasse
Wikipedia: Abstrakte Klasse
Wikipedia: Objekt




Nuvola apps bookcase.svg Regal: Programmierung Nuvola apps bookcase 1.svg Programmierkurs C# Nuvola mimetypes dvi.png Objekte und Klassen, der erste Blick
Persönliche Werkzeuge