Posted on Leave a comment

Objektorientierte Programmierung

Objektorientierte Programmierung

blank

Objektorientierte Programmierung (engl.: object-oriented programming, kurz OOP) ist ein Programmierstil bei dem Software auf Basis von Klassen und Objekten aufgebaut wird. Da objektorientierte Programmierung mittlerweile die am weitesten verbreitete Form der Softwareentwicklung ist, sollten auch Programmieranfänger die Herangehensweise kennen und in ihren Grundzügen verstanden haben. In diesem Artikel wollen wir daher auf die Definition von objektorientierter Programmierung, die wichtigsten Bestandteile und Konzepte und ihre Vor- und Nachteile eingehen.

Was ist objektorientierte Programmierung?

Objektorientiere Programmierung gehört zu den imperativen Programmierparadigmen. Ein Paradigma bezeichnet einfach eine bestimmte Vorgehensweise bei der Programmierung. Während bei imperativen Paradigmen der Entwickler die einzelnen Schritte des Programmflusses festlegt, gibt es auch deklarative Paradigmen, bei denen nur das Ergebnis, nicht aber der Weg dahin beschrieben wird.

Insgesamt gibt es zwei wichtige imperative Paradigmen: Das prozedurale und das objektorientierte Paradigma. Bei prozeduraler Programmierung besteht der Code aus Unterprogrammen und Funktionen (zusammengefasst als Prozeduren), die zur Programmsteuerung verwendet werden. Bei Objektorientierung werden hingegen Klassen und Objekten genutzt. Die Objekte haben einen Zustand, der durch Attribute oder Eigenschaften definiert wird, und stellen Methoden zur Verfügung, die die Geschäftslogik beinhalten.

Ein wichtiger Aspekt, der in der Definition von OOP häufig vergessen wird, ist die Notwendigkeit, dass die Objekte ihren Zustand kapseln und nur durch das Versenden und Empfangen von Nachrichten miteinander kommunizieren.

Kurze Geschichte der objektorientierten Programmierung

Der Gedanke von Objekten im Sinne der objektorientierten Programmierung kam das erste Mal am MIT in den späten 1950er Jahren auf – hier bezeichnete der Begriff identifizierbare Entitäten mit Attributen. Die ersten Sprachen, die den Ansatz umsetzen waren Simula 67 und Smalltalk. Insbesondere Smalltalk und sein Entwickler Alan Key prägten die Entwicklung der objektorientierten Programmierung nachhaltig. In Smalltalk sind alle Teile der Software – auch Klassen, Nachrichten oder Datenspeicher – Objekte, wodurch das Paradigma stark erzwungen wird.

Einen großen Durchbruch erlebte Objektorientierung mit C++, der Weiterentwicklung der prozeduralen Sprache C, in den 1980er Jahren. Seit den 1990er Jahren sind viele weitere objektorientierte und heutzutage beliebte Programmiersprachen hinzugekommen, beispielsweise Java und C#. Die meisten modernen Sprachen unterstützen mittlerweile sowohl das objektorientierte als auch prozedurale Paradigma bzw. Mischformen aus beiden.

Begriffe der objektorientierten Programmierung

Steigen wir nun in die Theorie der objektorientierten Programmierung ein. Dazu wollen wir zuerst die wichtigsten Begriffe wie Objekt und Klasse definieren und anschließend einige Grundkonzepte näher betrachten.

Objekte und Methoden

Ein Objekt ist ein Element, das einen inneren Zustand (engl.: state) auf Basis von Eigenschaften bzw. Attributen (engl.: properties oder attributes) und Methoden (engl.: methods) besitzt. Die Methoden agieren auf den konkreten Werten der Eigenschaften und verändern damit den Zustand des Objektes. Außerdem dienen sie als Schnittstelle zu anderen Objekten, über die Nachrichten versendet und empfangen werden können. Das Objekt entscheidet immer selbst, wie es mit Nachrichten umgeht und seine Daten ändert. Dadurch befindet es sich in einem wohldefinierten, selbstkontrollierten Zustand. Objekte verfügen für gewöhnlich über Methoden, mit denen sie erzeugt und ggf. auch zerstört werden können. Diese Methoden heißen Konstruktoren bzw. Destruktoren.

Eine Methode besitzt eine Signatur, die angibt, welche Eingabewerte sie erwartet und welchen Rückgabewert sie hat. Zudem kann in vielen Sprachen festgelegt werden, welche anderen Klassen auf die Methode zugreifen können. Dies bezeichnet man als Sichtbarkeit der Methode. Man unterscheidet:

  • Öffentliche Methoden (engl.: public), auf die von allen anderen Klassen zugegriffen werden kann.
  • Geschützte Methoden (engl.: protected), die nur für die Klassen selbst und abgeleitete Klassen sichtbar sind. Was genau eine abgeleitete Klasse ist, werden wir gleich klären.
  • Methoden auf Paketebene (engl.: package-private), die von Klassen im selben Paket gesehen werden können. Dazu muss die Programmiersprache Packages oder Namespaces unterstützen, wie es beispielsweise bei Java der Fall ist.
  • Private Methoden (engl.: private), die nur von der Klasse, die die Methode implementiert, aufgerufen werden können. Das ist beispielsweise notwendig, um eine längere Methode in kurze Subroutinen aufzuteilen, ohne die einzelnen Routinen anderen Objekten zugänglich zu machen.

Klassen

Zur Verwaltung gleichartiger Objekte wird das Konzept von Klassen verwendet. Klassen dienen als Vorlage, auf Basis derer zur Laufzeit konkrete Objekte erzeugt werden können. Ein Objekt ist damit die Instanz einer Klasse. Von einer Klasse kann es beliebig viele, voneinander unabhängige Instanzen geben. In ihrer Funktion als Blaupause definiert eine Klasse sowohl Attribute als auch Methoden und beschreibt damit das Verhalten all ihrer Instanzen.

Betrachten wir als Beispiel eine Klasse Person. Jeder Person hat einen Vornamen, einen Nachnamen und ein Alter. Außerdem kann sie bestimmte Tätigkeiten ausführen, beispielsweise essen oder trinken. Eine konkrete Person besitzt Werte für diese Attribute, beispielsweise den Namen Max Mustermann, und das Alter 38 Jahre oder den Namen Maike Musterfrau und das Alter 23 Jahre. Beide haben nicht nur diese Eigenschaften, sondern können auch essen und trinken, weil sie Personen sind.

Die Klasse Person und zwei Instanzen

Objektidentität

Ein wichtiger Aspekt von Objekten ist, dass sie unabhängig von ihren Eigenschaften identifizierbar sind. Das bezeichnet man als Objektidentität und es bedeutet, dass das Objekt immer dasselbe bleibt, auch wenn sich sein Zustand ändert – so wie eine Person immer noch dieselbe ist, nachdem sie ihren Namen oder ihre Adresse geändert hat. Dadurch muss das Objekt nicht all seine Eigenschaften oder seinen inneren Zustand preisgeben, um eindeutig adressierbar zu sein.

Wichtige Konzepte der objektorientierten Softwareentwicklung

Nachdem wir geklärt haben, was genau Klassen und Objekte sind, wollen wir sie in einen Zusammenhang setzen, indem wir einige wichtige Konzepte der objektorientierten Softwareentwicklung genauer betrachten. Diese sind Vererbung, abstrakte Klassen, Schnittstellen und Polymorphie. Anschließend wollen wir noch auf einige Prinzipien der objektorientierten Softwareentwicklung eingehen, insbesondere auf Datenkapselung und das Single Responsibility Principle.

Vererbung

Ein zentraler Bestandteil der objektorientierter Softwareentwicklung ist die Vererbung (engl. inheritance). Eine Vererbungsbeziehung besteht immer zwischen zwei Klassen und ist hierarchisch. Eine Klasse ist die Basisklasse (engl. super class, dt. auch Oberklasse, Elternklasse) und eine Klasse ist die abgeleitete Klasse (engl. sub class, dt. auch Unterklasse, Kindklasse). Die abgeleitete Klasse übernimmt alle Eigenschaften und Funktionen der Oberklasse. Sie kann ihrerseits neue Attribute und Funktionen hinzufügen oder bereits vorhandene mit einer eigenen Implementierung ersetzen. Das wird als Überschreiben bezeichnet.

Vererbung hat den Vorteil, dass eine bestehende Klasse erweitert werden kann, ohne dass sie nachträglich verändert werden muss. Zudem können Unterklassen im Code anstelle ihrer Oberklasse verwendet werden, da sie über alle Schnittstellen der Oberklasse verfügen.

Was genau bedeutet das?

Betrachten Sie die Elektrogeräte in Ihrem Haushalt – sie alle haben extrem unterschiedliche Funktionen: Der Wasserkocher erhitzt Wasser, während die Lampe für Licht sorgt. Beide haben aber eine gemeinsame Schnittstelle, nämlich den Stecker. Für die Steckdose ist es vollkommen egal, ob sie einen Wasserkocher oder eine Lampe versorgt.

Wir könnten also diese Geräte in einer Basisklasse Elektrogerät (ElectricalDevice) mit einem Stecker (Plug) und einer Schnittstelle zum anschießen (connect()) zusammenfassen. Die Klassen Wasserkocher (WaterBoiler) und Lampe (Lamp) erben von dieser Klasse.

Eine Steckdose (Socket) würde dann eine Methode plug_in() implementieren, die einfach ein Elektrogerät als Parameter enthält und auf diesem die Methode connect() aufruft. Kommt ein neues Elektrogerät wie ein Mixer in der Küche hinzu, ist das für die Steckdose irrelevant, sie kann den Mixer genauso anschließen.

Mehrere Klassen erben von der Basisklasse ElectricalDevice. Die Klasse Socket kann sich dadurch mit allen Unterklassen verbinden, unabhängig davon, um welches Objekt es sich tatsächlich handelt. Der Modifizierer + steht für public, # steht für protected.

Abstrakte Klassen und Interfaces

Gehen wir nun noch einen Schritt weiter: Die Funktionen boil_water(), light() und mix() entsprechen jeweils der Funktion beim Anschalten des Geräts. An- und Abschaltbarkeit ist aber ein Verhalten, das jedes Gerät durch einen entsprechenden Schalter haben sollte. Wie genau am Ende Licht oder Hitze produziert werden, interessiert den Benutzer nicht. Wir können auf der Superklasse daher abstrakte Methoden wie turn_on() und turn_off() zur Verfügung stellen. Eine abstrakte Methode verfügt über keine eigene Implementierung – denn so eine könnten wir für das Elektrogerät gar nicht pauschal festlegen. Sie legt aber für alle Unterklassen fest, dass eine Implementierung für diese Methode vorhanden sein muss. Nimmt ein Mensch nun ein Elektrogerät in die Hand, kann er einfach den Anschalter bedienen. Er muss nichts über die interne Struktur des Gerätes wissen – im Grunde muss er nicht einmal wissen, was das Gerät tut.

Die Klasse ElectricalDevice wird eine abstrakte Klasse, die eine Methode turn_on() und turn_off() zur Verfügung stellt. Alle ableitenden Klassen können ihr Verhalten nun verbergen. Der Modifizierer – steht für private, abstrakte Klassen und Methoden werden kursiv dargestellt.

Wenn eine Klasse eine abstrakte Methode besitzt, muss auch die Klasse selbst immer abstrakt sein, da sie nicht instanziiert werden kann. Eine Klasse, die ausschließlich abstrakte Methoden aufweist, wird als Schnittstelle oder Interface bezeichnet. Sie enthält keinerlei Logik, sondern beschreibt lediglich, wie Objekte, die dem Interface entsprechen, von außen angesprochen werden können.

Polymorphie

Am Beispiel der Küchengeräte lässt sich ein weiteres Konzept der Objektorientierung erkennen, die sogenannte Polymorphie. Polymorphie kommt aus dem Griechischen und bedeutet in etwa Vielgestaltigkeit. Von Polymorphie spricht man, wenn eine Methodensignatur für verschiedene Klassen dieselbe ist, aber unterschiedlich implementiert wird. Das ist in unserem Beispiel für die Methode turn_on() der Fall: Diese verfügt für die abstrakte Klasse über keine Logik, aber jedes elektrische Gerät implementiert ein eigenes Verhalten. Polymorphie hat den Vorteil, dass Code gleichzeitig an vielen Stellen wiederverwendet und flexibel eingesetzt und erweitert werden kann.

Information Hiding

Ein wichtiger Aspekt, den man schon beim Klassendesign berücksichtigen sollte, ist die Datenkapselung bzw. das sogenannte Information Hiding. Jedes Objekt sollte die alleinige Hoheit über seine Daten haben, diese verbergen und nur über eine Schnittstelle nach außen zugänglich machen. Andere Klassen sollten nicht wissen, wie genau der Code innerhalb einer anderen Klasse aussieht, und auch keine Annahmen darüber treffen.

Separation of Concerns / Single Responsibility Principle

Als Separation of Concerns oder Single Responsibility Principle bezeichnet man die eindeutige Aufteilung von Verantwortlichkeiten innerhalb eines Softwaresystems. Jede Klasse sollte dabei genau eine Verantwortlichkeit abbilden und in sich stark zusammenhängend (kohäsiv) und zu anderen Klassen unabhängig (lose gekoppelt) sein. Nur dann können Objekte und Klassen ihre Interna verbergen und beliebig ausgetauscht oder erweitert werden. So wie Information Hiding wird auch das Single Responsibility Principle durch objektorientierte Sprachen nicht erzwungen, sollte aber beim Design der Software berücksichtigt werden.

Vor- und Nachteile von objektorientierter Programmierung

Objektorientierung bringt viele Vorteile mit sich – sonst wäre sie nicht das vorherrschende Paradigma der modernen Softwareentwicklung. Einer der größten Vorteile ist die Kapselung von Logik und Daten in einzelne Klassen. Das verbessert die Wartbarkeit und macht den Code leichter erweiterbar. Einzelne Teile der Software oder eines Workflows können ausgetauscht werden und das Gesamtsystem ist flexibel. Vererbungshierarchien unterstützen außerdem die Wiederverwendung von Code und vermeiden dadurch Redundanz. Zuletzt sind auch die verbesserte Testbarkeit und damit erhöhte Robustheit der Software zu nennen – jede Klasse kann für sich betrachtet und unabhängig von anderen Teilen des Gesamtsystems getestet werden.

Allerdings gibt es auch einige Nachteile der objektorientierten Programmierung. Obwohl das Denken in Objekten relativ nah an der natürlichen Denkweise des Menschen ist, ist der Kontrollfluss des Codes manchmal schwieriger nachzuvollziehen. Ein weiterer Nachteil ist es, dass sich Objekte oft nicht direkt auf relationale Datenbanken abbilden lassen – die wichtigsten Konzepte wie Identität, Zustand, Verhalten und Kapselung gibt es hier nicht. Diese Problematik wird oft als object-relational impedance mismatch beschrieben. Zuletzt kann eine zu starke Objektorientierung zu einer Verschlechterung der Laufzeit führen. Aus diesem Grund werden in der Praxis oft objektorientierte und prozedurale Ansätze gemischt.

Fazit

Objektorientiere Programmierung ist eines der wichtigsten Paradigmen der Softwareentwicklung. Bei diesem Programmierstil wird die Software auf Basis von Objekten und Klassen aufgebaut. Ein Objekt hat einen durch Attribute definierten Zustand, ein durch Methoden definiertes Verhalten und eine Identität, durch die es eindeutig adressierbar ist. Es stellt nach außen hin Schnittstellen zur Kommunikation bereit, verbirgt aber seinen Zustand und seine internen Prozesse.

Klassen sind Baupläne für Objekte und definieren, welche Eigenschaften und Methoden es grundsätzlich gibt. Objekte sind Instanzen einer Klasse, die konkrete Werte für die Eigenschaften besitzen und die Methoden ausführen.

Klassen können voneinander erben. Diese Vererbung ist eines der wichtigsten Konzepte der objektorientierten Programmierung. Eine Subklasse wird dabei von einer Basisklasse oder Superklasse abgeleitet und übernimmt all deren Felder und Methoden. Sie kann ihrerseits einige davon überschreiben oder neue hinzufügen. Eine Subklasse kann immer anstelle ihre Basisklasse verwendet werden. Werden mehrere Klassen von derselbe Basisklasse abgeleitet, sind diese untereinander austauschbar, wenn nur die Schnittstelle der Basisklasse benötigt wird. Die unterschiedliche Implementierung derselben Methode nennt man in diesem Zusammenhang auch Polymorphismus (Vielgestaltigkeit).

Objektorientierte Programmierung bringt viele Vorteile mit sich: Klassen können sehr gut unabhängig voneinander erweitert, getestet und ausgetauscht werden. So ergibt sich ein sehr flexibles und robustes Gesamtsystem. Allerdings ist es dazu notwendig, die Klassen und ihre Verantwortlichkeiten richtig zu designen und abzustecken. Die Komplexität kann bei objektorientierten Programmen außerdem auf den ersten Blick höher sein als bei rein prozeduraler Programmierung.

Ähnliche Produkte

Schreibe einen Kommentar

Ihre E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.