Zum Inhalt springen

Objektorientierte Programmierung

(im Aufbau)

Einführung

Du hast bisher prozedural programmiert, vieles war problemlos möglich. Mit Hilfe von Funktionen konntest du viele Aufgaben auch effizient und übersichtlich lösen. In diesem Artikel bringe ich dir das Objektorientierte Programmieren näher. Es handelt sich nicht um eine bestimmte Programmiersprache, viele Programmiersprachen beherrschen das Konzept der Objektorientierten Programmierung, es ist viel eher ein anderer Stil; man nennt es Programmierparadigma. Am Ende dieses Kapitels wirst du eine bessere Vorstellung von den Unterschieden zwischen den beiden Stilen haben, hier ist es noch nicht hilfreich, die Unterschiede zu benennen.

Die folgenden Erklärungen und Beispiele sind von meinem Kollegen, Sebastian Jakobi, inspiriert.

Probleme klassenweise lösen, statt einzeln

In der Objektorientierten Programmierung (OOP) löst man Probleme klassenweise, anstatt für jedes Objekt einzeln Eigenschaften und Fähigkeiten zu formulieren. So fasst man viele Objekte, die gewisse Ähnlichkeiten haben, zu Klassen zusammen. Das geschieht durch Abstraktion.

Beispiel: Es gibt viele verschiedene Menschen (=Klasse), deren Vertreter können bestimmte Personen (=Objekte) sein.

Die Objekte haben Merkmale (Attribute) und Fähigkeiten/Funktionalitäten (Methoden), die Ihnen durch die Klasse zugeordnet werden. Je nachdem, auf welcher Ebene eine Klasse formuliert wird, hat sie mehr oder weniger abstrakt formulierte Attribute und Methoden. Bei der Klasse Mensch wären Name, Alter, Körpergröße mögliche Attribute, das man einem Objekt zuordnen kann, Laufen, Sprechen, Essen wären Methoden (Fähigkeit). Nicht jedes Objekt belegt Attribute und Fähigkeiten auf gleiche Weise, sie sind also verschieden ausgeprägt. Verschiedene Objekte (Personen) einer Klasse (Mensch) können sich weiterhin unterscheiden, in diesem Beispiel also etwa in der Körpergröße, im Alter oder bei der Ausführung der Methode Laufen, Sprechen oder dergleichen. So sind einzelne Menschen unterschiedlich alt, können schneller, langsamer oder gar nicht laufen sowie mehr oder weniger essen.

Demnach ist die Klasse also eine Art Bauplan für Objekte.

Good to know: Eine Klasse ist ein eigener Datentyp, der komplexer ist als Integer, Floats etc. Wenn man Objekte erstellt, so wird ein Zeiger erstellt, der auf einen Speicherplatz im Arbeitsspeicher zeigt.

UML: Einen Plan erstellen

Bei der Objektorientierten Programmierung coded man in der Regel nicht “einfach drauf los”, sondern macht sich erst einen Plan. Das lohnt sich besonders in komplexeren Projekten. Wir lernen dies von Anfang an als Methode, damit du in der Lage bist, auch komplexere Projekte anzulegen.

Ein Ansatz ist, diesen Plan mithilfe von UML, speziell mit UML Klassendiagrammen (UML steht für Unified Modeling Language) zu erstellen.

Das Klassendiagramm für die Klasse Mensch beispielsweise könnte so aussehen:

Dabei gelten folgende Konventionen.

oberer Teil: Attribute
unterer Teil: Methoden

Zeichen vor Attribut/Methode:
– bedeutet private, + bedeutet public, # bedeutet protected

Hinter jede Variable schreibt man den Datentyp, bei Methoden gibt man die Parameter in den Klammern mit an und die Typen der Rückgabewerte hinter dem Doppelpunkt ebenso.[mfn]Was es mit der Methode Mensch(…) auf sich hat, klären wir weiter unten.[/mfn]

Solche Diagramme kannst du in einem Textverarbeitungsprogramm oder mit extra Tools erstellen. Ich habe es mit diesem Online-Tool gemacht. Tipp: Wähle dort “Start from blank”. Dieses Tutorial ist hilfreich.

Objektorientiert Programmieren (in Java)

Objektorientiert programmieren kannst du in Java, PHP, Python und vielen anderen Programmiersprachen. In diesem Abschnitt erkläre ich die Grundlagen zunächst in Java, eventuell auch mal in Python. Wichtiger als die Codezeilen sind die Prinzipien, die sind bei allen fast identisch.

Richtlinien beim OOP

An dieser Stelle gebe ich einige Richtlinien vor, die sich an Konventionen in der Programmierung orientieren. Halte dich unbedingt an alle diese Richtlinien.

  • Klassennamen beginnen immer mit einem Großbuchstaben. Bei aus mehreren Worten zusammengesetzten Namen nutzt man CamelCase.
  • Trenne den Bereich der Attribute von denen der Methoden, indem du Blöcke anlegst. Schreibe mithilfe von Kommentaren Überschriften über diese Bereiche.
  • Die Sichtbarkeit soll immer mit angegeben werden, siehe Kapselung.

Kapselung

Um unsinnige Fehler zu vermeiden, bspw. dass der Größe eines Menschen ein negativer Wert zugeordnet wird, verwendet man Kapselung. Man könnte natürlich auch beim Programmieren darauf achten, dass kein Objekt der Klasse Mensch ein negatives Alter oder eine negative Körpergröße hat oder dass man nicht durch 0 teilt etc. Mit der Kapselung jedoch kontrolliert man schon bei der Formulierung der Klasse solche Fehler.

Sichtbarkeit

Die Sichtbarkeit kontrolliert den Zugriff auf Attribute und Methoden einer Klasse.

  • private: Zugriff nur innerhalb der Klasse möglich
  • public: Zugriff von außen möglich, also von anderen Programmteilen und Klassen

Attribute sind üblicherweise private, man soll also nicht von außen darauf zugreifen können. Um den Zugriff darauf weiter zu regeln nutzt man stattdessen extra Methoden dafür, die dann public sind. Diese sind die get- bzw. set-Methoden. So kannst du beispielsweise einem Attribut, das nicht veränderlich sein soll, nur eine get-Methode und keine set-Methode geben.

Kapselung

Das ist ein Beispiel für eine Klasse. In unserem Beispiel haben wir hier eine einfache Klasse Mensch.

//Mensch.java
public class Mensch {
	// Attribute
	private String name;
	private int alter;
	private int groesse;

	// Methoden
	public int getAlter(){ //Beispiel für eine get-Methode
		return alter;
	}

	public void setAlter(int alterNeu){ //Beispiel für eine set-Methode, void: leerer Rückgabewert
		if(alterNeu > 0) alter = alterNeu; // >0 prüft, ob Wert sinnvoll. Mit der Kapselung ist beides sichergestellt: Zugriff und sinnvolle Werte
	}
        //... hier seien weitere Attribute und Methoden definiert
}

In dem obigen Beispiel haben wir folgendes gemacht:

  • Klasse Mensch definiert, sie hat die Attribute name, alter, groesse und Methoden getAlter, setAlter
  • die Methode get-Alter() gibt nur alter zurück, setAlter(alterNeu) prüft zunächst ob der Wert sinnvoll ist (also größer als 0 ist) und überschreibt dann den alten Wert mit dem neuen Wert.
  • Alle Attribute sind private, beide Methoden sind public
  • Wir stellen uns vor, ich hätte noch alle weiteren Attribute und Methoden definiert, die ich unten gleich nutze.
//Hauptprogramm.java, oft auch main genannt
//Wenn beide Dateien, Klassen-Datei und Hauptprogramm im selben Ordner sind, kannst du direkt darauf zugreifen.

public class Hauptprogramm {
	public static void main(String[] args) { //main-Methode, Klärung später
		Mensch m1 = new Mensch(); //erstellt Objekt m1 der Klasse Mensch
		m1.setName("Murat"); //setzt Attribut name von Objekt m1
		m1.setAlter(16); //setzt Attribut alter von Objekt m1
		m1.setGroesse(174); //setzt Attribut groesse von Objekt m1
		
		int a = m1.getAlter();	//fragt nach Alter von Objekt m1	
	}
}

Im Hauptprogramm kannst du also nun auf die Klasse Mensch zugreifen. Was passiert oben im Code? Schaue dir die Kommentare an. Du siehst: Um auf Attribute oder Methoden eines Objektes zuzugreifen, schreibt man den Objektnamen, einen Punkt und dann die Bezeichnung des Attributes oder der Methode, die Syntax sieht also so aus: objekt.eineMethode()

Konstruktoren

Konstruktoren sind eine tolle Sache. Du hast oben vielleicht scharfsinnig beobachtet, dass es ziemlich umständlich ist, eine Klasse zu erstellen, um dann mühselig alle Attribute mit set-Methoden zu belegen. Außerdem und noch viel wichtiger: Sobald ich eine set-Methode habe, kann ich das Attribut immer wieder ändern. Was ist nun, wenn ich ein Attribut nur einmal festlegen möchte und es nicht mehr veränderbar sein soll? Zum Beispiel den Namen eines Menschen?

Konstruktor-Methode

Im folgenden ergänze ich den Teil zum Konstruktor in der Klasse Mensch:

public class Mensch {

	// Attribute
	private String name;
	private int alter;
	private int groesse;

	// Konstruktor
	public Mensch(String n, int a, int g) {
		name = n;
		alter = a;
		groesse = g;
	}

	// Methoden
	public int getAlter() {
		return alter;
	}

	public void setAlter(int alterNeu) {
		if (alterNeu > 0)
			alter = alterNeu; // >0 prüft, ob Wert sinnvoll
	}

	public int getGroesse() {
		return groesse;
	}

	public void setGroesse(int groesseNeu) {
		if (groesseNeu > 0)
			groesse = groesseNeu; // >0 prüft, ob Wert sinnvoll
	}
}

Jetzt benötige ich eine einzige Zeile, um die Standard-Attribute für Mensch zu übergeben.

public class Hauptprogramm {
	public static void main(String[] args) {
		Mensch m1 = new Mensch("Murat",16,174);
		int a = m1.getAlter();
	}
}

Beachte bitte bei Java:

  • Der Konstruktor bzw. die Konstruktor-Methode hat den gleichen Namen wie die Klasse.
  • Der Konstruktor muss sich nicht um alle Attribute kümmern. Einige Attribute können auch durch extra Methoden festgelegt werden.
  • Es können mehrere Konstruktoren genutzt werden. Beispielsweise kannst du eine zweite Konstruktor-Methode Mensch() ohne Parameter nutzen, um die Attribute noch frei zu lassen oder Standard-Werte zu übergeben. Beispiel dazu:
// 2. Konstruktor
	public Mensch() {
		name = "Murat Mustermensch";
		alter = 17;
		groesse = 174;
	}

Du siehst, dass dieser Konstruktor keine Parameter annimmt, sondern einfach immer einen Murat Mustermenschen erstellt. Im Hauptprogramm kannst du zwei Objekte dann so erstellen:

public class Hauptprogramm {
	public static void main(String[] args) {
		Mensch m1 = new Mensch("Murat",16,174);
		Mensch m2 = new Mensch();
		int a = m1.getAlter();
	}
}
  • Beachte bitte: Um ein Attribut kann sich gleichzeitig immer nur ein Konstruktor kümmern. Wenn du also mehrere Konstruktoren erstellst, kannst du jedes Attribut jeweils nur einem Konstruktor zuordnen.

this-Referenz

Wir haben bisher bei der Übergabe von Parametern andere Bezeichner als die der Attribute gewählt, damit klar ist, welche Variable gemeint ist.

	// Konstruktor
	public Mensch(String n, int a, int g) {
		name = n;
		alter = a;
		groesse = g;
	}

In diesem Codeblock siehst du, dass ich nicht etwa Mensch(String name, ...), sondern Mensch(String n, ...) geschrieben habe, damit klar ist, was wem zugeordnet wird. Um eine bessere Lesbarkeit und gleichzeitig Eindeutigkeit gewährleisten zu können, schreiben wir das nun so:

	// Attribute
	private String name; // unten mit this.name referenziert
	private int alter;
	private int groesse;
	
	// Konstruktor
	public Mensch(String name, int alter, int groesse) {
		this.name = name; // this.name ist das Attribut name von oben
		this.alter = alter;
		this.groesse = groesse;
	}

Jetzt ist klar, dass die Attribute des zu erstellenden Objektes die Werte durch die Parameter erhalten.

In Python nutzt man self.name statt this.name, um auf das Attribut eines Objektes innerhalb der Klasse zuzugreifen.

Klassenattribute

Klassenattribute sind Attribute, die einer gesamten Klasse zugeordnet werden und nicht einem einzigen Objekt der Klasse. In unserem Beispiel könnte man der Klasse Mensch beispielsweise die Klassenattribute Bevölkerungszahl oder eine Liste aller Menschen zuordnen. Es gibt auch klassenweite Methoden. Diese Attribute bzw. Methoden nennt man auch statische Attribute bzw. statische Methoden, die main-Methode in Java ist so eine statische Methode.

Im UML unterstreicht man solche Attribute und Methoden, um zu verdeutlichen, dass es sich um statische Attribute und Methoden handelt.

Im Java-Code sieht es dann so aus:

public class Mensch {
	//Klassenattribute und -Methoden
	private static int bevoelkerungszahl = 0;
	
	public static int getBevoelkerungszahl() {
		return bevoelkerungszahl;
	}
        ...
	// Konstruktor
	public Mensch(String name, int alter, int groesse) {
		bevoelkerungszahl = bevoelkerungszahl+1;
		this.name = name; // this.name ist das Attribut name von oben
		this.alter = alter;
		this.groesse = groesse;
	}
        ...
}

Das Klassenattribut bevoelkerungszahl habe ich in diesem Beispiel in den Konstruktor aufgenommen. So steigt die Bevölkerungszahl mit jedem erstellten Objekt der Klasse Mensch.

Im Hauptprogramm kannst du dann Mensch.getBevoelkerungszahl() auf die statische Methode zugreifen. Statt also auf ein Objekt zuzugreifen, greift man direkt auf die Klasse zu.

Richtlinien beim OOP (Update) & weitere Hinweise

An dieser Stelle gebe ich einige Richtlinien vor, die sich an Konventionen in der Programmierung orientieren. Halte dich unbedingt an alle diese Richtlinien.

Basics

  • Klassennamen beginnen immer mit einem Großbuchstaben. Bei aus mehreren Worten zusammengesetzten Namen nutzt man CamelCase.
  • Eine Klasse hat Attribute und Methoden, siehe UML.
  • Um auf Attribute oder Methoden eines Objektes zuzugreifen, schreibt man den Objektnamen, gefolgt von einem Punkt und dem Namen des Attributs bzw. der Methode. Auf ähnliche Weise greift man auf statische Attribute und Methoden zu, da schreibt man den Klassennamen vor den Punkt statt eines Objektnamens.
  • Trenne den Bereich der Attribute von denen der Methoden, indem du Blöcke anlegst. Schreibe mithilfe von Kommentaren Überschriften über diese Bereiche.

Sichtbarkeit und Kapselung

  • Die Sichtbarkeit soll immer mit angegeben werden, siehe Kapselung.
  • Attribute sollten private sein, Methoden public [mfn] Private sollten Methoden nur dann sein, wenn von außen keine sinnvolle Nutzung vorgesehen ist und die Methode anderweitig aufgerufen wird, entweder standardmäßig (bspw. bei Erstellung eines Objektes oder in komplexeren Fällen wie Vererbung). [/mfn]

Konstruktoren

  • Der Konstruktor ist eine besondere Methode, der manchen oder allen Attributen bei Erstellung eines Objektes Werte übergibt. In Java (nicht in Python) ist es möglich, mehrere Konstruktoren anzulegen, wenn die Attribute jeweils höchstens nur in einem Konstruktor belegt werden.
  • Wir nutzen die this-Referenz, damit wir Parameter- und Attribut-Bezeichner gleich wählen dürfen, das verbessert die Lesbarkeit.

Vererbung

  • Rufe immer erst den Konstruktor der Oberklasse auf, das machst du mit super(params)[mfn]params steht für eine Aneinanderreihung von Parametern, durch ein Komma getrennt.[/mfn]. Der eigene Konstruktor folgt dann.
  • In Unterklassen kannst du eigene – also spezifischere – Attribute und Methoden anlegen. Geerbte Methoden können überschrieben werden.
  • Unterklassen können nicht auf private Attribute/Methoden der Oberklasse zugreifen, nur auf public und protected.

Beziehungen zwischen Klassen und Objekten

Assoziation, Aggregation und Komposition

Eine Art der Beziehung zwischen Objekten ist die Assoziation, die Aggregation und Komposition sind Spezialfälle der Assoziation.

Vererbung

Oberklassen, Unterklassen. Manchmal auch Basisklassen und abgeleitete Klasse, oft auch Elternklasse und Kindklassen (parent class, child class). Abgeleitete Klassen können nicht auf private Attribute oder Klassen zugreifen, nur public oder protected.

Schlüsselwort extends (“erweitert” die Klasse). Man schreibt nicht alle Attribute der Oberklasse dazu, diese sind vererbt. Umgang mit dem Konstruktor ist wichtig. Mit super(params) wird der Konstruktor der Oberklasse aufgerufen. Je nach Wahl der Parameter kann ich da – falls es mehrere gibt – auch entscheiden, welchen Konstruktor der Oberklasse ich aufrufe. Bevor man den eigenen Konstruktor in der Unterklasse aufruft, ruft man erst den Konstruktor der Oberklasse auf.

Ein Kommentar

  1. […] Meinem Empfinden nach geschieht das selten konsistent. Den Begriff Methoden nutzt man eher in der Objektorientierten Programmierung, beim prozedrualen Stil spricht man von Funktionen. Methoden sagt man auch, wenn man die Methoden […]

Schreibe einen Kommentar

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