Objektorientierte Programmierung mit R6
Beim Programmieren gibt es verschiedene Ansätze und Paradigmen, um effizienten und gut organisierten Code zu schreiben. Ein bekanntes und weit verbreitetes Paradigma ist die objektorientierte Programmierung (OOP). Viele Herausforderungen in der Datenanalyse oder Data Science kann man zwar ohne OOP angehen, doch das Wissen um OOP in R kann sich durchaus lohnen – z.B. wenn man komplexere Systeme entwickelt. In diesem Post gehe ich deswegen auf objektorientierte Programmierung mit R6-Klassen ein (aus dem Package R6
).
Objektorientierte Programmierung (OOP)
Objektorientierte Programmierung ist ein Paradigma, bei dem Programme in Form von Objekten organisiert sind. Objekte sind Instanzen von Klassen, die Daten (Attribute) und Funktionen (Methoden) enthalten. Durch die Verwendung von OOP kannst du Code besser modularisieren und wiederverwenden. In R gibt es verschiedene Möglichkeiten, OOP umzusetzen. So gab es bereits S3- und S4-Klassen. Doch eine der modernsten Methoden sind die R6-Klassen.
Beispiel für objektorientierte Programmierung
Wenn du noch nichts über OOP gehört hast, dann findest du die Definition oben vielleicht etwas verwirrend, bzw. du kannst dir noch nicht viel darunter vorstellen. Deswegen nun ein kleines Beispiel. Ich bin hier nicht sonderlich kreativ, denn ich nutze ein klassisches und verbreitetes Beispiel: geometrische Formen. Ich verwende die einfachste Version, in der es erstmal nur um ein Rechteck geht.
Definition der Klasse
Zuerst definieren wir die Klasse Rechteck. Eine Klasse ist eine abstrakte Darstellung eines realen Objekts, eine Art „Bauplan“. Basierend auf der Klasse können wir anschließend so viele reale Objekte „bauen“, wie wir wollen. In der Programmierung spricht man dann von Instanzen (einer Klasse).
- Klasse: Rechteck
- Attribute: Breite, Höhe
- Methoden:
- Flächeninhalt berechnen: rechnet Breite x Höhe
Erstellen von Instanzen
Nun ist die Rechteck-Klasse definiert und wir können beliebig viele Instanzen erstellen:
- R1 = Rechteck(b=10, h=20)
- R2 = Rechteck(b=5, h=8)
Ich habe nun zwei Instanzen, die beide ein Rechteck sind.
Verwenden von Methoden
Ich kann nun die Methode „Flächeninhalt berechnen“ verwenden, um den Flächeninhalt des jeweiligen Rechtecks berechnet zu bekommen.
- R1.“Flächeninhalt berechnen“ → 200
- R2.“Flächeninhalt berechnen“ → 40
Im Folgenden schauen wir uns an, wie das alles in R funktioniert.
R6-Klassen
R6 ist ein Package in R, das die Erstellung von Klassen und Objekten erleichtert. Es wurde entwickelt, um die Schwächen der vorigen S3- und S4-Systeme zu überwinden und eine einfachere und intuitivere objektorientierte Programmierung in R zu ermöglichen.
Vorteile von R6
Anbei einige Vorteile von R6. Keine Sorge, wenn noch einiges unklar ist – ich zeige später anhand von praktischen Beispielen, was jeder Punkt bedeutet.
- Referenzsemantik: Im Gegensatz zu S3 und S4 verwendet R6 referenzielle Semantik, d.h., wenn ein Objekt geändert wird, wirkt sich dies auch auf alle anderen Referenzen des Objekts aus.
- Kapselung: R6-Klassen unterstützen die Kapselung von Daten und Funktionen, was zu besser strukturiertem und wartbarem Code führt.
- Vererbung: R6-Klassen ermöglichen die Vererbung, wodurch die Wiederverwendung von Code und die Erstellung flexibler und erweiterbarer Programme erleichtert wird.
Objektorientierte Programmierung mit R6
Zurück zu unserem Beispiel mit den Rechtecken. Anbei der entsprechende Code für unsere R6-Klasse:
library(R6)
Rectangle <- R6Class(
classname = "Rectangle",
public = list(
initialize = function(height, width) {
private$height <- height
private$width <- width
},
get_area = function() {
return(private$height * private$width)
}
),
private = list(
height = 0,
width = 0
)
)
Hier gibt es einiges zu bemerken:
- Wir definieren eine R6-Klasse mit der Funktion
R6Class
- Als erstes geben wir den Klassennamen an (Parameter
classname
) - Wir sehen, dass zwei Listen erstellt werden,
public
undprivate
, dazu gleich mehr - In
public
werden zwei Methoden definiert,initialize
undget_area
- In
private
werden zwei Attribute definiert,height
undwidth
Public und Private in R6
public
und private
sind grundlegende Konzepte aus der objektorientierten Programmierung und haben mit der bereits erwähnten Kapselung zu tun. Sie steuern die Sichtbarkeit und den Zugriff auf Attribute und Methoden innerhalb einer Klasse.
Public
Wenn ein Attribut oder eine Methode als public
deklariert wird, kann sie von überall aufgerufen oder verwendet werden. Einschließlich von Objekten, die von der Klasse erzeugt werden, sowie von anderen Klassen und Teilen des Programms. Public-Mitglieder sind die Schnittstelle der Klasse zur Außenwelt und können von jedem Objekt, das auf die Klasse zugreifen kann, verwendet werden.
Private
Wenn ein Attribut oder eine Methode als private
deklariert wird, kann sie nur innerhalb der Klasse verwendet werden, in der sie definiert ist. Private-Mitglieder sind für die interne Funktionsweise der Klasse gedacht und können nicht von außerhalb der Klasse oder von abgeleiteten Klassen (bei Vererbung) aufgerufen oder verwendet werden.
Die initialize-Methode in R6
Die initialize
-Methode ist speziell, denn sie dient dazu, das Objekt einer R6-Klasse während seiner Erstellung zu initialisieren. Wenn ein neues Objekt einer R6-Klasse mit der new
-Methode erzeugt wird, wird die initialize
-Methode automatisch aufgerufen. Das werden wir im nächsten Absatz auch sofort sehen.
Instanzen einer Klasse erstellen
Mit dem vorigen R-Code haben wir die Klasse erstellt. Und wie schon erwähnt, ist die Klassen nur eine Schablone, aus der wir nun „richtige Objekte“ erstellen können. Das machen wir mit Rectangle$new()
. Wir erstellen die zwei Instanzen R1 und R2, wie oben bereits beschrieben. Nachdem das getan ist, können wir den Flächeninhalt berechnen lassen mit der get_area()
-Methode.
# Create instances of the Rectangle class
R1 <- Rectangle$new(10, 20)
R2 <- Rectangle$new(5, 8)
# Use methods
R1$get_area()
R2$get_area()
Referenzsemantik in R6
Dieses Thema ist eher fortgeschritten; dennoch möchte ich kurz ein paar Sätze dazu loswerden. Referenzsemantik, oder Objektreferenz, bedeutet, dass Objekte oder Datenstrukturen durch Referenzen oder Zeiger anstatt durch ihren tatsächlichen Wert manipuliert werden. R6-Klassen verwenden Referenzsemantik, um Objekte zu erstellen, die auf ihren internen Zustand zugreifen und diesen verändern können ohne Kopien des Objekts zu erstellen. Dies ermöglicht eine effizientere Speichernutzung und bessere Leistung, insbesondere bei der Arbeit mit großen Datenstrukturen.
# This is NOT a copy of the R1 object, but a reference
R1b <- R1
# The rectangle object passed to the function will NOT be a copy, but a reference
print_area <- function(rect) {
area <- rect$get_area()
print(area)
}
print_area(R1)
Die Kommentare beinhalten bereits deutliche Aussagen: Das Erstellen von R1b ist keine Kopie von R1, sondern beide R-Objekte (R1
und R1b
) zeigen auf die selbe Instanz! Genauso wird R1
nicht kopiert, um an die Funktion print_area
übergeben zu werden. Es wird lediglich eine Referenz (= Speicheradresse) übergeben.
Vererbung mit R6
Zuletzt möchte ich noch auf das Thema Vererbung eingehen. Vererbung bedeutet, dass eine Klasse auf eine andere aufbauen kann und somit Methoden und Attribute übernimmt. Die vererbende Klasse kann natürlich trotzdem eigene Methoden oder Attribute hinzufügen. Da wir schon bei Rechtecken waren, erstellen wir nun eine Quadrat-Klasse. Ein Quadrat ist ein Rechteck, bei dem alle Seiten gleich lang sind.
Square <- R6Class(
classname = "Square",
inherit = Rectangle,
public = list(
initialize = function(length) {
private$height <- length
private$width <- length
}
)
)
Das wars auch schon! Hier ein paar Beobachtungen:
- Die Erstellung der Klasse ist strukturell gleich unserer Rechteck-Klasse
- Es ist
inherit = Rectangle
hinzugekommen; dadurch erbtSquare
vonRectangle
- Die
initialize
-Methode wurde überschrieben
Nun können wir wieder Instanzen erstellen. Das Besondere: Obwohl wir in der Square-Klasse keine Methode definiert haben, die den Flächeninhalt berechnet, können wir durch die Vererbung die get_area
-Methode verwenden!
# Create two squares
Q1 <- Square$new(5)
Q2 <- Square$new(25)
# Calculate area
Q1$get_area()
Q2$get_area()
Zusammenfassung
In diesem Post habe ich die Grundlagen der objektorientierten Programmierung in R gezeigt. Dazu haben wir uns R6-Klassen aus dem R6
-Package angeschaut. R6-Klassen sind eine leistungsfähige und benutzerfreundliche Möglichkeit, OOP in R umzusetzen und bieten Vorteile wie Referenzsemantik, Kapselung und Vererbung. Du kannst mit R6 modulareren, besser wartbaren und wiederverwendbaren Code erstellen, was gerade bei komplexeren Projekten lohnenswert sein kann.
Wenn du noch weitere Fragen zu R6 (oder auch anderen Themen) hast, schreibe mir gerne eine E-Mail.