Galileo Computing <openbook>
Galileo Computing - Programming the Net
Galileo Computing - Programming the Net


C# von Eric Gunnerson
Die neue Sprache für Microsofts .NET-Plattform
C# - Zum Katalog
gp Kapitel 2 Grundlagen der objektorientierten Programmierung
  gp 2.1 Was ist ein Objekt?
  gp 2.2 Vererbung
  gp 2.3 Das Prinzip des Containments
  gp 2.4 Polymorphismus und virtuelle Funktionen
  gp 2.5 Kapselung und Sichtbarkeit

Kapitel 2 Grundlagen der objektorientierten Programmierung

In diesem Kapitel erhalten Sie eine Einführung in die objektorientierte Programmierung. Diejenigen von Ihnen, die bereits mit der objektorientierten Programmierung vertraut sind, können diesen Abschnitt überspringen.

Beim objektorientierten Entwurf gibt es verschiedenste Ansätze, was durch die Anzahl der zu diesem Thema veröffentlichten Bücher belegt wird. Die folgende Einleitung geht von einem recht pragmatischen Ansatz aus und beschäftigt sich weniger mit dem Design, auch wenn genau diese entwurfsbezogenen Ansätze für Anfänger recht nützlich sein können.


Galileo Computing

2.1 Was ist ein Objektdowntop

Ein Objekt ist eine Sammlung zueinander in Beziehung stehender Informationen und Funktionen. Ein Objekt kann etwas sein, das über ein entsprechendes Äquivalent in der tatsächlichen Welt verfügt (z. B. ein Mitarbeiter-Objekt), etwas, das virtuelle Bedeutung hat (beispielsweise ein Fenster auf dem Bildschirm), oder es kann einfach ein abstraktes Element innerhalb eines Programms darstellen (etwa eine Liste der zu erledigenden Aufgaben).

Ein Objekt setzt sich aus den Daten zusammen, die das Objekt selbst und die Operationen beschreiben, die für das Objekt ausgeführt werden können. Die in einem Mitarbeiter-Objekt gespeicherten Informationen beispielsweise können Informationen zur Person (Name, Adresse), arbeitsbezogene Informationen (Position, Gehalt) usw. enthalten. Zu den ausgeführten Operationen gehört vielleicht das Erstellen einer Gehaltsabrechnung oder die Beförderung des Mitarbeiters.

Im objektorientierten Entwurf besteht der erste Schritt darin, die Bedeutung der Objekte zu definieren. Bei der Verwendung von Objekten, die auch im wirklichen Leben vorkommen, ist dies einfach, wenn Sie jedoch in der virtuellen Welt arbeiten, verschwimmen die Grenzen. Hier zeigt sich die Kunst eines guten Designs, und dies ist auch der Grund dafür, weshalb eine gute Architektur gefordert ist.


Galileo Computing

2.2 Vererbung  downtop

Die Vererbung ist ein fundamentales Leistungsmerkmal eines objektorientierten Systems. Sie stellt die Fähigkeit dar, Daten und Funktionen von einem übergeordneten Objekt an ein untergeordnetes weiterzugeben, also zu vererben. Statt Objekte neu zu entwickeln, kann neuer Code auf der Arbeit anderer Programmierer basieren. Es werden lediglich einige neue Funktionen hinzugefügt. Das übergeordnete Objekt, auf dem der neue Code beruht, wird als Basisklasse bezeichnet, das untergeordnete Objekt als abgeleitete Klasse.

Der Vererbung kommt bei der Erläuterung des objektorientierten Designs große Bedeutung zu, tatsächlich verwendet wird die Vererbung jedoch seltener. Hierfür gibt es verschiedene Gründe.

Zunächst ist die Vererbung ein Beispiel für die im objektorientierten Design angeführte »Ist-Ein(e)«-Beziehung (engl. »Is-A«). Wenn ein System über ein Tier-Objekt und ein Katze-Objekt verfügt, kann das Katze-Objekt vom Tier-Objekt erben, denn eine Katze »Ist-Ein(e)« Tier. Bei der Vererbung ist die Basisklasse stets allgemeiner gefasst als die abgeleitete Klasse. Die Katze-Klasse würde die Essen-Funktion der Tier-Klasse erben und über eine erweiterte Schlafen-Funktion verfügen. Im wirklichen Leben sind derartige Beziehungen jedoch weniger gebräuchlich.

Als Zweites muss zur Verwendung der Vererbung die Basisklasse mit dem Hintergedanken der Vererbung entworfen werden. Dies ist aus verschiedenen Gründen wichtig. Wenn die Objekte keine geeignete Struktur aufweisen, kann die Vererbung nicht richtig funktionieren. Noch wichtiger: Ein Design, das die Vererbung verwendet, macht deutlich, dass der Autor der Klasse damit einverstanden ist, dass andere Klassen von dieser Klasse erben. Wenn eine neue Klasse von einer bestehenden abgeleitet wird, bei der dies nicht der Fall ist, kann dies zu einer Änderung der Basisklasse und damit zur Zerstörung der abgeleiteten Klasse führen.

Einige weniger erfahrene Programmierer gehen fälschlicherweise davon aus, dass die Vererbung in der objektorientierten Programmierung breite Anwendung finden sollte und setzen sie daher zu häufig ein. Die Vererbung sollte nur dann verwendet werden, wenn die sich ergebenden Vorteile tatsächlich genutzt werden. Siehe hierzu auch den Abschnitt »Polymorphismus und virtuelle Funktionen«.

In der .NET Common Language Runtime werden alle Objekte von einer Basisklasse namens object abgeleitet, und hierbei wird nur die Einfachvererbung unterstützt (d. h., ein Objekt kann nur von einer Basisklasse abgeleitet werden). Auf diese Weise wird die Verwendung einiger gebräuchlicher Wendungen verhindert, die in Mehrfachvererbungssystemen wie C++ genutzt werden. Gleichzeitig wird jedoch der Missbrauch der Mehrfachvererbung verhindert und eine Vereinfachung erreicht. In den meisten Fällen stellt dies einen guten Tausch dar. Die .NET-Laufzeitumgebung ermöglicht eine Mehrfachvererbung in Form von Schnittstellen ohne Implementierung. Die Schnittstellen werden in Kapitel 10, Schnittstellen, behandelt.


Galileo Computing

2.3 Das Prinzip des Containments  downtop

Wenn also Vererbung nicht die richtige Wahl ist, was dann?

Die Antwort lautet: Containment, auch Aggregation oder Enthaltenseinbeziehung genannt. Hierbei wird ein Objekt nicht als ein Beispiel eines anderen Objekts betrachtet, sondern als eine Instanz eines Objekts, die sich im Objekt befindet. Statt also über eine Klasse zu verfügen, die wie eine Zeichenfolge aussieht, enthält die Klasse eine Zeichenfolge (oder ein Array oder eine Hashtabelle).

Üblicherweise sollte beim Design der Ansatz des Containments gewählt werden, auf die Vererbung sollte nur zurückgegriffen werden, wenn dies erforderlich ist (z. B. wenn tatsächlich eine »Ist-Ein(e)«-Beziehung vorliegt).


Galileo Computing

2.4 Polymorphismus und virtuelle Funktionen  downtop

Vor einiger Zeit habe ich ein Musiksystem geschrieben, und ich wollte hierbei sowohl WinAmp als auch den Windows Media Player für die Wiedergabe unterstützen. Gleichzeitig sollte nicht jeder Codebestandteil wissen müssen, welches Programm verwendet wird. Ich definierte daher eine abstrakte Klasse, d. h. eine Klasse, mit der die Funktionen beschrieben werden, die eine abgeleitete Klasse implementieren muss. Auf diese Weise werden gelegentlich Funktionen bereitgestellt, die für beide Klassen nützlich sind.

In diesem Fall hieß die abstrakte Klasse MusicServer und verfügte über Funktionen wie Play(), NextSong(), Pause() usw. Jede dieser Funktionen wurde als abstrakt deklariert, damit sie durch die Playerklasse selbst implementiert würde.

Abstrakte Funktionen sind automatisch virtuelle Funktionen, die dem Programmierer die Verwendung des Polymorphismus zur Codevereinfachung ermöglichen. Ist eine virtuelle Funktion vorhanden, kann der Programmierer einen Verweis auf die abstrakte statt auf die abgeleitete Klasse erstellen, und der Compiler schreibt Code zum Aufrufen der geeigneten Funktionsversion zur Laufzeit.

Ich möchte dies durch ein Beispiel verdeutlichen. Das Musiksystem unterstützt für die Wiedergabe sowohl WinAmp als auch den Windows Media Player. Der nachstehende Code gibt einen kurzen Überblick über das Aussehen der Klassen:

using System;
public abstract class MusicServer
{
    public abstract void Play();
}
public class WinAmpServer: MusicServer
{
    public override void Play()
    {
        Console.WriteLine("WinAmpServer.Play()");
    }
}
public class MediaServer: MusicServer
{
    public override void Play()
    {
        Console.WriteLine("MediaServer.Play()");
    }
}
class Test
{
    public static void CallPlay(MusicServer ms)
    {
        ms.Play();
    }
    public static void Main()
    {
        MusicServer ms = new WinAmpServer();
        CallPlay(ms);
        ms = new MediaServer();
        CallPlay(ms);
    }
}

Dieser Code erzeugt die folgende Ausgabe:

WinAmpServer.Play()
MediaServer.Play()

Polymorphismus und virtuelle Funktionen werden in der .NET-Laufzeitumgebung an vielen Stellen eingesetzt. Das Basisobjekt object beispielsweise verfügt über die virtuelle Funktion ToString(), die zum Konvertieren eines Objekts in eine Zeichenfolgendarstellung des Objekts verwendet wird. Wenn Sie die Funktion ToString() für ein Objekt aufrufen, das nicht über eine eigene Version von ToString() verfügt, wird die Version von ToString() aufgerufen, die Teil der Klasse object ist, wodurch einfach der Name der Klasse zurückgegeben wird. Wenn Sie die ToString()-Funktion überladen (eine eigene Version schreiben), wird statt dessen diese Version aufgerufen, und Sie können eine sinnvollere Operation ausführen. So könnten Sie beispielsweise den Namen des Mitarbeiters ausschreiben, der im Mitarbeiter-Objekt enthalten ist. Im Musiksystem bedeutete dies, die Funktionen Play(), Pause(), NextSong() usw. zu überladen.


Galileo Computing

2.5 Kapselung und Sichtbarkeit  toptop

Beim Entwurf von Objekten muss der Programmierer entscheiden, in welchem Ausmaß das Objekt für den Benutzer sichtbar ist bzw. dem Benutzer verborgen bleibt. Details, die für den Benutzer nicht sichtbar sind, bezeichnet man als in der Klasse gekapselt.

Im Allgemeinen besteht das Ziel beim Objektentwurf darin, die Klasse in größtmöglichem Umfang zu kapseln. Die Gründe hierfür lauten:

1. Der Benutzer kann die als privat deklarierten Elemente im Objekt nicht ändern, d. h. das Risiko, dass der Benutzer diese Details im Code ändert oder von diesen abhängig ist, wird verringert. Ist der Benutzer von diesen Details abhängig, können Objektänderungen zur Beschädigung des Benutzercodes führen.
2. Änderungen, die an den öffentlichen Bestandteilen eines Objekts vorgenommen werden, müssen eine weitere Kompatibilität mit der vorherigen Version sicherstellen. Je mehr Einsicht der Benutzer erhält, desto weniger Codeelemente können geändert werden, ohne den Benutzercode zu zerstören.
3. Größere Schnittstellen erhöhen die Komplexität des gesamten Systems. Auf private Felder kann nur von einer Klasse aus, auf öffentliche Felder kann über eine beliebige Instanz der Klasse zugegriffen werden. Der erforderliche Aufwand für die Fehlersuche steigt mit der Anzahl der öffentlichen Felder.

Dieses Thema wird in Kapitel 5, 101-Klassen, weiter ausgeführt.






1    Vielleicht sollte eine Studie wie »Mehrfachvererbung als schädlich eingestuft« veröffentlicht werden. Naja, wahrscheinlich gibt es sie schon irgendwo.

2    Falls eine Basisklasse des aktuellen Objekts vorhanden ist und diese ToString() definiert, wird diese Version aufgerufen.

   

Select * from SQL Server 2000




Copyright © Galileo Press GmbH 2001 - 2002
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press GmbH, Gartenstraße 24, 53229 Bonn, fon: 0228.42150.0, fax 0228.42150.77, info@galileo-press.de