Programmierkurs C-Sharp: Objekte und Klassen

Aus Wikibooks

Wechseln zu: Navigation, Suche
Regal: Programmierung Programmierkurs C# Bild:Wikibooks buchseite.svg 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 von Klassen sind. Klassen wiederum stellen so etwas wie eine "abstrakte Form" dar.

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. 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. Dieser Konstruktor beschreibt, wie genau das Objekt von der Klasse erzeugt werden soll. Steht dort nichts, gibt es auch keine Besonderheiten. 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 wichtigste lautet: Der Konstruktor muss genauso wie die Klasse heißen.

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;
  }
}

Ein Objekt wird jetzt so erzeugt:

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

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

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

// Ausgabe = "Audi"
Console.WriteLine(m_Objekt.Typ);
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 eben genannten 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:


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




Regal: Programmierung Programmierkurs C# Bild:Wikibooks buchseite.svg Objekte und Klassen, der erste Blick
Persönliche Werkzeuge