Was ist objektorientierte Programmierung?

Keylearnings:

  • Was ist objektorientierte Programmierung?
  • Was ist imperative Programmierung?
  • Was ist prozedurale Programierung?
  • Was ist ein Execution Stack?
  • Was ist Datenkapselung?
  • Was ist ein Event?
  • Was ist Polymorphie?

Hast du auch schonmal den Wald vor lauter Bäumen nicht gesehen?

So ging es mir, als ich mich zum erstenmal mit der objektorientierten Programmierung beschäftigt habe.

Damals kurz vor Weihnachten im Jahr 2006, als ich anfing ein Java Handbuch in die Hand zunehmen und ich alles über Klassen, Schnittstellen und Vererbung las.

Ich glaube auch viele andere Programmierer haben auf diese Weise die objektorientierte Programmierung kennengelernt.

Aber da gibt es ein Problem!

Wer war zuerst da? Das Ei oder die Henne?

Die objektorientierte Programmierung ist ein Konzept! Und Java, C++ oder Smalltalk sind lediglich nach diesem Konzept gebaute Programmiersprachen.

Und wenn du die objektorientierte Programmierung über eine konkrete Programmiersprache kennenlernst, ist das, überspitzt ausgedrückt(!), so als würdest du die Henne über das gelegte Ei beschreiben wollen.

Oder anders gesagt, du lernst etwas und nennst es dann objektorientierte Programmierung.

Ohne folgende Fragen zu beantworten:

Was ist objektorientierte Programmierung?
Warum programmieren wir objektorientiert?

Und vorallem „Was bringt uns das?“

Diesen Fragen möchte ich in diesem Artikel auf den Grund gehen.

Warum objektorientierte Programmierung?

Jede große Erfindung beginnt mit einem Problem.

In unserem Fall den Spaghettis, bzw. dem Spaghetticode. Aber dazu später mehr!

Meine erste Programmiersprache war die Sprache Basic. Aus heutiger Sicht ein Werk des Teufels.

In seiner Urform ist Basic eine imperative Programmiersprache, in der jede Programmanweisung der Reihe nach abgearbeitet wird.

Ein typisches Basic Programm sieht wie folgt aus:

10 I = 1
20 PRINT "TEST NR.";
30 I = I + 1
40 IF I<=10 GOTO 20

Und NEIN! Die Zeilennummern habe nicht ich hinzugefügt, die schreibt das Ur-Basic zwingend vor und werden für ein in dieser Programmiersprache wichtiges Sprachelement benötigt. Nämlich den Sprungmarken.

Wie wird dieses Programm ausgeführt?

Beim starten des Programms wird das komplette Programm als Maschinencode in den Speicher geladen und auf dem Prozessor Zeile für Zeile nacheinander ausgeführt.

Und hier stehen wir bereits vor dem ersten Problem! Ein Programm wird aufgrund der Verwendung von Kontrollstrukturen eben nicht sequentiell ausgeführt.

Wir verwenden Bedingungs-Anweisungen um einen Codeblock zu überspringen und Schleifen um Anweisungen wiederholt auszuführen.

Basic löst dieses Problem mit Hilfe von Sprungmarken.

Dem sogenannten GOTO Befehl, den wir in unserem Beispiel oben in Zeile 40 verwenden, und der dafür sorgt, dass solange die Variabe I einen Wert kleiner oder gleich 10 hat die Ausführung des Programms in Zeile 20 fortgesetzt wird.

Wenn wir bedenken, dass ein Programm üblicherweise aus zehntausenden von Zeilen besteht, können wir uns leicht ausmalen, dass dieses Vorgehen nur im Chaos enden kann.

Diese Programmierweise hat den Spitznamen Spaghetti-Code.

Die prozedurale Programmierung

Die nächste Evolutionsstufe war die prozedurale Programmiersprache. Ein berühmter Vertreter dieser Gattung ist die Programmiersprache Pascal.

Bei prozeduralen Programmiersprachen hast du die Möglichkeit dein (Haupt)-Programm in Unterprogramme aufzuteilen, die jeweils ein Teilproblem lösen.

Solche Unterprogramme nennt man Prozeduren.

Innerhalb eines Unterprogramms kannst du alle in dem Hauptprogramm enthaltene Prozeduren aufrufen, was uns zu dem Begriff des Exceutions Stacks führt.

Der Execution Stack

Jedes Unterprogramm ist ein in sich abgeschlossenes Programm mit Anweisungen und eigenen Variablen, das im Hauptprogramm eingebettet ist. Insbesondere können die verschiedenen Prozeduren nicht ohne weiteres gegenseitig auf ihre Speicherbereiche zugreifen.

Aber auch hier gilt, dass die Ausnahme die Regel bestätigt, nämlich durch die sogenannten globalen Variablen.

Den Speicherbereich im Arbeitsspeicher, in dem Platz für die globalen Variablen reserviert ist nennen wir Heap. Der Heap ist ein Bereich im Computerspeicher, auf den wir aus allen Unterprogrammen heraus zugreifen können.

Außerdem können wir, genau wie wir es bereits bei den Funktionen kennengelernt haben,  mit Hilfe von Parametern Daten an eine Prozedur übergeben und zurückliefern lassen.

Immer wenn du ein Unterprogramn aufrufst, wird Speicherplatz reserviert und der Maschinencode der aufgerufenen Prozedur sequentiell abgearbeitet.

Was ist objektorientierte Programmierung prozedural

Schauen wir uns das schematisch in Pseudocode an.

Hauptprogramm_Start{
 
 Prozedur_2{
  Anweisungsteil_Prozedur 2
 } 

 Prozedur_1{
   Aufruf Prozedur_2
 }

 Anweisungsteil_Hauptprogramm{
   Aufruf Prozedur_1
 }

}Hauptprogramm_Ende

Wir haben ein Hauptprogramm mit zwei Prozeduren. Die erste Prozedur rufen wir aus dem Einsprung-Punkt des Hauptprogramms auf. Anschließend rufen wir Prozedur 1 auf, aus der wir wiederum Prozedur 2 starten.

Die Abarbeitung des Programms erfolgt auf einem Stack. Dem sogenannten Execution Stack.

Durch den Aufruf von Prozedur 1 wird der Programmcode des ersten Unterprogramms oben auf den Stack gelegt.

Was ist objektorientierte Programmierung Exceution Stack 2.

Da Prozedur 1 aber Prozedur 2 aufruft, wird Prozedur 1 nicht vollständig abgearbeitet sondern es findet ein weiterer Push auf den Stack statt, mit dem Prozedur 2 oben auf den Execution Stack gelegt wird.

Was ist objektorientiterte Programmierung Exceution Stack

Weil Prozedur 2 kein weiteres Unterprogramm aufruft, wird diese vollständig abgearbeitet und aus dem Execution Stack entfernt. Anschließend wir die Ausführung an dem Punkt fortgesetzt, an der in Prozedur 1 das zweite Unterprogramm aufgerufen wurde.

Nachdem auch Prozedur 1 vollständig abgearbeitet ist, wird das Hauptprogramm an der Stelle fortgesetzt, an der wir ins erste Unterprogramm eingestiegen sind.

Also warum denn jetzt eigentlich objektorientierte Programmierung?

Das hört sich doch alles bisher ganz gut an. Also wofür benötigen wir diese objektorientierte Programmierung noch?

Einen Nachteil löst auch die prozedurale Programmierung nicht.

Die Priorität bei der prozeduralen Programmierung liegt nach wie vor auf den Anweisungen und nicht auf das Verhalten der vorkommenden Objekte. Ein Programm besteht immer noch aus einer Sequenz von Befehlen, die der Computer der Reihe nach abarbeitet.

Aber denk dran! Wir wollen mit unseren Programmen realen Menschen mit realen Problemen helfen.

Und reichen uns hierfür wirklich eine Reihe von Computerbefehlen, die nacheinander abgearbeitet werden?

Nicht wirklich! In der Realität müssen wir auf Ereignisse, den sogenannten Events reagieren.

Ein klassisches Ereignis ist beispielsweise ein Mausklick auf eine Schaltfläche.

Oder stell dir vor, du musst ein Verkehrsflusssystem für eine Straßenkreuzung implementieren.

Was ist objektorientierte Programmierung

Um beispielsweise eine Fußgängerampel prozedural zu implementieren, müssen wir im Hauptprogramm für das Verkehrsflusssystem  in einer Dauerschleife überprüfen, ob ein Fußgänger den Ampelknopf betätigt und sobald dies der Fall ist ein Unterprogramm anstoßen, in dem die komplette Funktionalität einer Fußgängerampel abgebildet ist.

Die Zustände der Ampel würden wir in einer globalen Variablen speichern.

Gleichzeitig muss in der selben Dauerschleife natürlich auch noch überprüft werden, ob ein Auto naht, das wir anhalten müssen, weil die Ampel auf Rot geschaltet hat.

Und was machen wir, wenn die Straßenkreuzung um eine weitere Fußgängerampel erweitert werden soll?

Dann könnten wir mit Copy and Paste arbeiten und die Variablen für die Zustände kopieren und eine 2 an die Bezeichnungen hängen.

In Pseudocode ausgedrückt sehe das ganze dann ungefähr wie folgt aus:

Hauptprogramm Verkehrflussystem{

 ZustandAmpel1;
 ZustandAmpel2;
     .
     .
 ZustandAmple10;

 posAuto1;
 posAuto2;
    .
    .
 posAuto89;

 posFußgänger1;
   .
 posFußgänger87;

 Startpunkt Hauptprogramm{
   Überprüfung in Dauerschleife{
    
       Hat Fußgänger Ampel gedrückt?
       
       Muss Ampel 7 umschalten?

       Naht Auto 9?

       Muss Auto 86 stehen bleiben?
               .
               .            
   }
 } 

}

Das geht! Aber ist wahnsinnig aufwendig! Außerdem mal Hand aufs Herz. Ist das schön? Sicher nicht!

Und vor allem was ist, wenn wir etwas an den Ampeln ändern wollen? In der prozeduralen Programmierung bleibt uns nicht anderes übrig als jede Ampel einzeln anzupassen.

Auch besteht eine Verkehrskreuzung nicht ausschließlich aus Fußgängerampeln. Wir benötigen ebenfalls Ampeln, die den Autoverkehr regeln.

Und obwohl sich die Fußgängerampel von der Auto-Ampel lediglich im Ampelmännchen und einem Knopf unterscheidet, sind wir in der prozeduralen Programmierung gezwungen diese zu großen Teilen neu zu implementieren.

Möchten wir außerdem noch Personen und Autos in unserem Verkehrsflusssystem abbilden, müssen wir für jede einzelne Person und für jedes Auto entsprechende Variablen und Unterprogramme bereitstellen.

Diese Nachteile sollen mit Hilfe der objektorientierten Programmierung beseitigt werden.

Was ist objektorientierte Programmierung?

Bei der objektorientierten Programmierung stellen wir uns nicht nur dir Frage, was unser Programm tun soll sondern legen insbesondere ein Augenmerk darauf, welche Dinge wir abbilden wollen und vor allem wie diese miteinander in Verbindung stehen.

Bleiben wir bei unserer Straßenkreuzung.

Aus objektorientierter Sicht setzt sich die Straßenkreuzung beispielsweise aus den Objekten Ampel, Autos, Fußgänger und Zebrastreifen etc. zusammen.

In der prozeduralen Programmierung müssen wir die Funktionalität JEDES Teilnehmers in unserem Programm für das Verkehrsflusssystem implementieren.

Wir hätten also in einer Kontrollstruktur zu regeln, dass sobald die Ampel X auf rot wechselt Auto a,b und c stehen bleibt. Noch komplizierter wird es, wenn wir den Fußgänger, der die Straße über einen Zebrastreifen überqueren will in unsere Simulation einbeziehen wollen.

Daher verfolgen wir in der objektorientierten Programmierung einen anderen Ansatz und fassen jedes Objekt als ein in sich geschlossenes Programm mit klar definierter Schnittstelle nach außen auf.

In JAVA wird ein Objekt durch eine sogenannte Klasse beschrieben. Haben wir beispielsweise eine Klasse für eine Ampel erstellt, können wir aus dieser beliebig viele Ampel-Objekte erzeugen.

Anders als in der prozeduralen Programmierung müssen wir also keinen redundanten Programmcode erzeugen. Insbesondere ist es nicht mehr nötig den selben Programmcode an verschiedenen Stellen zu pflegen.

Jedes Objekt wird in einem eigenen Speicherbereich erzeugt und existiert dort solange bis es zerstört wird, was in modernen Programmiersprachen wie Java automatisch vom sogenannten Garbage Collector übernommen wird.

Kommen wir zur ersten zentralen Säule in der objektorientierten Programmierung. Der Datenkapselung!

Was ist Datenkapselung?

Betrachten wir die Ampel mal ein wenig genauer.

Stell dir vor du stehst an einer Fußgängerampel und du möchtest über eine Straße.

Musst du wirklich verstehen wie die Ampel technisch funktioniert?

Nein, zum Glück nicht! Alles was du wissen musst, ist dass du einen Knopf betätigst und die Ampel dadurch von rot auf grün schaltet.

Der Knopf und die farbigen Lampen bilden eine Schnittstelle nach außen.

Und genauso wollen wir die Ampel auch programmieren. Der Entwickler, der die Ampel in das Verkehrsflusssystem einbaut soll sich mit den Details wie die Ampel intern funktioniert nicht auseinandersetzen müssen.

Dies erreichen wir indem wir die Ampel als Blackbox bereitstellen und dem Entwickler nur Möglichkeiten zum Abfragen und zum setzen der Ampelfarbe geben.

Bildlich kann man sich das ganze auf folgende Weise vorstellen:

Was ist objektorienierte Programmierung Datenkapselung!

Auf die Variablen, in denen der Zustand einer Ampel gespeichert ist, hat der Programmierer keinen direkten Zugriff. Dadurch verhindern wir unteranderem, dass die Ampel beispielsweise auf rot und grün gleichzeitig gesetzt werden kann.

Den Zustand der Ampel kann der Entwickler nur über die nach außen sichtbare Methode AmpelZustandAbfragen() abfragen.

Aber noch viel wichtiger! Der Zustand kann nur kontrolliert über den Aufruf der Methode drückeAmpelKnopf() erfolgen.

Was ist ein Event?

Gut, wir können jetzt Ampel-, Auto- und Fußgänger-Objekte erzeugen. Nur wie interagieren die Objekte in unserem Verkehrsflusssystem miteinander?

Wie wir bereits festgehalten haben, werden die Objekte unabhängig voneinander im Speicher erzeugt.

Aber wie wird das Fußgänger-Objekt darüber informiert, dass die Ampel von Rot auf Grün gewechselt hat?

Hierfür gibt es die sogenannten Events.

Events kann man sich als Nachrichten, die zwischen den Objekten ausgetauscht werden vorstellen.

Unseren Ampeln können wir einen sogenannten Event-Listener spendieren, bei dem wir die Fußgänger in unserem Verkehrsflusssystem registrieren.

Sobald die Ampel von Rot auf Grün wechselt, werden alle im Event-Listener registrierten Fußgänger benachrichtigt und können entsprechend reagieren.

Was ist objektorientierte Programmierung Events

Was ist Polymorphie?

Zu guter letzt wollen wir noch eine weitere wichtige Säule der objektorientierten Programmierung besprechen.

Die sogenannte Polymorphie! Oder auf deutsch auch Vielgestaltigkeit genannt.

Polymorphie kann auf zwei Arten realisiert werden. Zum einen über die Vererbung und zum anderen über Schablonen, die man auch Templates bzw. in Java Generics nennt.

Polymorphie durch Vererbung

In unserem Verkehrsflusssystem gibt es unterschiedliche Kraftfahrzeuge wie Autos, Motorräder und LKW’s.

Mit Hilfe der Vererbung können wir ein Kraftfahrzeug zu einem Auto, einem Motorrad oder einem LKW verfeinern.

Das Kraftfahrzeug enthält die Eigenschaften und Methoden, die alle Kraftfahrzeuge gemeinsam haben, wie z.B. Bremsen und Lenken.

Ein LKW können wir von einem Kraftfahrzeug herleiten indem wir dem Kraftfahrzeug LKW spezifische Eigenschaften und Methoden wie z.B. Ladefläche und LKW_beladen() hinzufügen.

Hierdurch haben wir die Möglichkeit eine Variable für das Objekt Kraftfahrzeug zu deklarieren und können darin dann ALLE Ausprägungen eines Kraftfahrzeugs wie Auto, LKW oder Motorrad speichern.

Wir sprechen in einem solchen Fall auch von Subtyping.

Unter der Voraussetzung, dass sowohl PKW als auch LKW von Kraftfahrzeug abgeleitet ist, ermöglicht uns Subtyping folgendes (JAVA Beispiel).

Kraftfahrzeug fahrzeug1 = new LKW();
Kraftfahrzeug fahrzeug2 = new PKW();

Wir können also sowohl einen LKW als auch einen PKW in einer Variablen vom Typ Kraftfahrzeug speichern.

Im folgenden Video zeige ich dir nochmal Schritt für Schritt an einem Beispielprogramm wie Polymorphie in Java funktioniert.

Polymorphie durch Schablonen/Templates

Die zweite Art, mit der Polymorphie realisiert werden kann sind die Schablonen/Templates, die in Java auch Generics genannt werden.

Templates ermöglichen die flexible Verwendung von Datentypen.

Nehmen wir an wir möchten für unsere Kraftfahrzeuge eine Garage bauen. Dann können wir mit Hilfe von Generics sehr einfach festlegen, ob die Garage für LKW’s, Autos oder Motorräder sein soll.

Schauen wir uns das an einem JAVA Beispiel näher an.

public class Garage<T> {
  public <T>  fahrzeug = null;
}

Wir haben eine Klasse Garage, die lediglich das Attribut Fahrzeug enthält.

Aber von welchem Datentyp ist das Attribut Fahrzeug?

Hier kommt der Diamantenoperator ins Spiel, welchen wir als Platzhalter verstehen können, und über den wir den Datentyp beim Erzeugen eines Garagen-Objekts festlegen können.

Und das funktioniert wie folgt:

1: Garage<LKW> garage = new Garage<LKW>();

Mit Hilfe des Diamanten-Operators füllen wir den Platzhalter T in der Klasse Garage mit dem Datentyp LKW. Das Attribut fahrzeug ist in obigem Beispiel also vom Typ LKW.

Fazit: In diesem Artikel haben wir die Frage „Was ist objektorientierte Programmierung?“ beantwortet. Insbesondere haben wir die prozedurale und die objektorientierte Programmierung miteinander verglichen.  Aufgrund der objektorientierten Programmierung wird ein Entwickeln mit Bausteinen möglich. Hierdurch wird die Wiederverwendung und die Wartung von Programmcode vereinfacht.

Hat dir der Artikel gefallen? Dann folge uns am besten gleich auf Facebook!

P.S. Findest du auch das objektorientierte Programmierung eine Vereinfachung ist? Ich freue mich über einen Kommentar!

Hallo ich bin Kim und ich möchte ein großer Programmierer werden. Machst du mit?

Kommentare (2)

  • Antworte

    Hallo,

    habe den Artikel gerade gelesen. Finde ich wirklich interessant geschrieben, weiter so.

    • Ich danke dir!

Hinterlasse ein Kommentar