Nachdem wir nun die meisten Grundkonzepte der Programmierung (Variablen, Bedingungen, Schleifen, Methoden) kennengelernt haben, wenden wir uns nun einer Denkweise zu, die eine andere Art der Strukturierung und Organisation von Programmen darstellt - der sogenannten objektorientierten Modellierung.
Unter Objektorientierung versteht man eine Modellierung komplexer Systeme, bei der ein System durch ein Zusammenspiel verschiedener Objekte beschrieben wird. Der Begriff Objekt ist dabei unscharf gefasst: ausschlaggebend an einem Objekt ist nur, dass ihm bestimmte Attribute (Eigenschaften) und Methoden zugeordnet sind und dass es in der Lage ist, von anderen Objekten Informationen zu empfangen beziehungsweise an diese zu senden. Objekte müssen dabei nicht gegenständlich (konkret) sein, sondern können auch mehr oder weniger abstrakt sein. Entscheidend dabei ist, dass das Grundkonzept der Objektorientierung (Objekte mit bestimmten Eigenschaften und Methoden) sinnvoll umgesetzt werden kann. Programmiertechnisch entspricht diesem Ansatz das Konzept der Klasse, in der Objekte aufgrund ähnlicher Eigenschaften zusammengefasst werden. Ein Objekt wird im Programmcode als Instanz einer Klasse definiert.
Bisher haben wir in Processing Objekte mit Hilfe vordefinierter Methoden erzeugt. Die große Stärke einer objektorientierten Programmiersprache besteht darin, dass wir als Programmierer eigene Datentypen in Form von Klassen nach unseren Vorstellungen und Bedürfnissen erstellen können, wodurch das Verhalten unserer Objekte deutlich flexibler gestaltet werden kann. Eine Klasse beschreibt entsprechend den Aufbau eines komplexen Datentyps.
Beispiele für Objekte der Klasse Rechteck:
Objekte lassen sich in Form von Objektdiagrammen darstellen:
Diese Darstellung von Objekten beinhaltet neben dem Objektnamen, der nach einer Konvention immer klein geschrieben wird, die Attribute des Objekts und die jeweiligen Attributwerte. Sie definieren den Zustand des Objekts. Objekte mit denselben Attributen sind Instanzen einer Klasse. Eine Klasse stellt einen Konstruktionsplan für bestimmte Objekte dar, der mit all seinen Informationen auch ohne diese Objekte existiert. Eine Klasse ist also keine Menge von Objekten!
Die Definition einer Klasse beinhaltet drei Bestandteile:
- Die Datenfelder bzw. Instanzvariablen speichern die Daten, die das jeweilge Objekt benutzt.
- Die Konstruktoren erlauben es, neue Objekte zu erzeugen und diese in einen bestimmten Anfangszustand zu versetzen.
- Die Methoden implementieren das Verhalten der Objekte.
Eine Klasse kann keinen, einen oder mehrere unterschiedliche Konstruktoren besitzen. Sie dienen dazu, ein neu gebildetes Objekt einer Klasse in einen definierten Anfangszustand zu versetzen. Welcher dies ist hängt davon ab, welcher Konstruktor bei der Objektbildung aufgerufen wird. Ein leerer (Standard-) Konstruktor muss nicht angegeben werden, er wird bei Fehlen von der JVM (Java Virtual Machine) automatisch erzeugt. Aus diesem Grund hatten wir in unseren bisherigen Aufgaben keinen expliziten Konstruktor.
Instanzvariablen und Methoden haben wir bereits kennengelernt. Neu sind die sogenannten Konstruktoren. Diese haben die Syntax NameDerKlasse(parameter1, parameter2, …) { … }
Konstruktoren der obigen Klasse Rechteck: Rechteck()
weist dem Rechteck standardmäßig Werte zu.
Rechteck() { breite = 10; hoehe = 7; }
Wir erhalten bei Aufruf des Konstruktors ein Rechteck der Größe 10x7. Die restlichen Werte werden von der JVM auf einen internen Standardwert gesetzt.
Konstruktoren der obigen Klasse Rechteck: Rechteck(int breite, int hoehe)
weist dem Rechteck benutzerdefinierte Breite und Höhe.
Rechteck(int breite, int hoehe) { this.breite = breite; this.hoehe = hoehe; }
Wir erhalten bei Aufruf des Konstruktors ein Rechteck der Größe 10x7. Die restlichen Werte werden von der JVM auf einen internen Standardwert gesetzt.
Konstruktoren der obigen Klasse Rechteck: Rechteck(breite, hoehe, fuellfarbe)
gibt dem User die Möglichkeit, neben der Größe des Rechtecks auch noch dessen Füllfarbe festzulegen.
Rechteck(int breite, int hoehe, color fuellfarbe) { this.breite = breite; this.hoehe = hoehe; this.fuellfarbe = fuellfarbe }
Konstruktoren können also mit unterschiedlichen Parameterlisten deklariert sein. Man spricht hier vom Überladen des Konstruktors.
Neue Instanzen einer Klasse werden mit dem Operator new
erzeugt.
Rechteck rechteck1 = new Rechteck(); Rechteck rechteck2 = new Rechteck(12, 8); Rechteck rechteck3 = new Rechteck(15, 4, color(255, 0, 0));
Die drei erzeugten Rechtecke sind vom Typ Rechteck; d.h. sie sind Instanzen der Klasse Rechteck.
rechteck1
wird mit dem Standardkonstruktor erzeugt und hat die Größe 10x7.rechteck2
wird vom User auf die Größe 12x8 festgelegtrechteck3
bekommt vom User die Größe 15x4 und die Füllfarbe rot zugewiesen.
Hier sollte nun deutlich werden, dass das erzeugte Objekt eine Variable ist, die nach dem Plan der Klasse Rechteck aufgebaut ist. Erst wenn das Objekt erzeugt wurde, kann mittels Punktschreibweise auch auf die Eigenschaften des Objekts zugegriffen werden. Beispiel:
rechteck1.breite = 10; rechteck3.fuellfarbe = color(0, 255, 0);
Zuerst die Klasse (Klassen immer in einen neuen Tab schreiben):
class Rechteck { // Attribute int xPos; int yPos; int laenge; int breite; int linienbreite; color linienfarbe = color(255, 0, 0); color fuellfarbe = color(255, 255, 0); // Konstruktoren Rechteck() { xPos = 100; yPos = 100; laenge = 100; breite = 50; } Rechteck(int laenge, int breite, color fuellfarbe) { xPos = width / 2; yPos = height / 2; this.laenge = laenge; this.breite = breite; this.fuellfarbe = fuellfarbe; } // Methoden void zeichnen() { stroke(linienfarbe); fill(fuellfarbe); rect(xPos, yPos, laenge, breite); } void bewegen(int v) { xPos = xPos + v; if(xPos > width) { xPos = 0; fuellfarbe = color(random(256), random(256), random(256)); } } }
Nun können wir innerhalb des (Haupt)-Programms auf den neuen Datentyp zugreifen:
Rechteck r1; Rechteck r2; void setup() { size(500, 300); r1 = new Rechteck(100, 50, color(255, 0, 0)); r2 = new Rechteck(100, 50, color(255, 255, 0)); } void draw() { background(0); r1.zeichnen(); r1.bewegen(2); r2.zeichnen(); r2.bewegen(3); }
a) Bearbeitet den Sketch aus dem vorherigen Beispiel so, dass mehr als nur die beiden Rechtecke angezeigt werden.
b) Bearbeitet den Sketch so, dass sich die Rechtecke von rechts nach links bewegen.
In einem kleinen Programm wollen wir eine Anzahl von Blasen aufsteigen lassen.
a) Erstelle dazu eine Klasse Blase mit einem Konstruktor, mit dem wir Blasen unterschiedlicher Größe erzeugen können, die vom unteren Rand des Zeichenfensters aufsteigen.
b) Eine Methode display()
ist für die Darstellung der Blase zuständig, eine Methode aufsteigen()
für das Aufsteigen. Dabei soll die Geschwindigkeit über einen Parameter festgelegt werden.
c) Erzeuge anschließend im Hauptprogramm vier Blasen unterschiedlicher Größe, die mit unterschiedlicher Geschwindigkeit nach oben steigen.
Sketch
Blubbel b1; Blubbel b2; Blubbel b3; Blubbel b4; void setup() { size(400, 300); b1 = new Blubbel(100, 10); b2 = new Blubbel(200, 8); b3 = new Blubbel(250, 12); b4 = new Blubbel(350, 9); } void draw() { background(255); b1.display(); b1.aufsteigen(3); b2.display(); b2.aufsteigen(2); b3.display(); b3.aufsteigen(2); b4.display(); b4.aufsteigen(4); }
Blubbel
class Blubbel { float x; float y; float r; Blubbel(float x, float r) { this.x = x; this.r = r; y = height - r; } void display() { fill(color(0, 0, 255)); ellipse(x, y, r, r); } void aufsteigen(float v) { y = y - v; } }
Modifiziere die Methode aufsteigen()
so, dass weitere Blasen von einer beliebigen Stelle am Boden und mit veränderter Geschwindigkeit aufsteigen.
Du musst keine neuen Blasen erzeugen.
Wenn eine Blase "merkt", dass sie am oberen Rand angekommen ist, …
… "beamt" sie sich einfach an eine neue Zufallsposition am unteren Rand.
void aufsteigen(float v) { y = y - v; if (y < 0) { x = random(width - r); y = height; v = random(5); } }
Wir wollen nun einen Schwarm Ufos erzeugen, der uns entgegenfliegt.
a) Wir brauchen dazu eine Klasse Ufo, die das Erzeugen der UFO-Objekte ermöglicht.
b) Sie beinhaltet neben dem Konstruktor eine Methode display()
zum Darstellen der Objekte und eine Methode move()
zum Bewegen der UFOs. Der Konstruktor sollte dabei einen Parameter enthalten, mit dem die move()
-Funktion die UFOs in unterschiedliche Richtungen lenkt.
c) Im Hauptprogramm bauen wir unsere Ufo-Armee aus einzelnen UFOs zusammen. Es reichen dabei 3 bis 5 UFOs um den Eindruck eines ganzen Schwarms zu erzeugen.
Sketch
Ufo u1; Ufo u2; Ufo u3; Ufo u4; Ufo u5; void setup() { size(600, 400); background(255); u5 = new Ufo(150, 100, 70, color(random(256), random(256), random(256)), 1); u4 = new Ufo(200, 200, 30, color(random(256), random(256), random(256)), 2); u3 = new Ufo(350, 200, 20, color(random(256), random(256), random(256)), 3); u2 = new Ufo(450, 100, 60, color(random(256), random(256), random(256)), 0); u1 = new Ufo(180, 300, 10, color(random(256), random(256), random(256)), 1); } void draw() { background(0); u1.display(); u1.move(3); u2.display(); u2.move(2); u3.display(); u3.move(3); u4.display(); u4.move(4); u5.display(); u5.move(5); }
Ufo
class Ufo { // Attribute float xPos; float yPos; float b; color farbe; int r; // Konstuktor Ufo(float xPos, float yPos, float b, color farbe, int r) { this.xPos = xPos; this.yPos = yPos; this.b = b; this.farbe = farbe; this.r = r; } void display() { noStroke(); // Rumpf fill(farbe); ellipse(xPos, yPos, b, 4 * b / 5); ellipse(xPos, yPos, b * 2.5, b / 4); // Fenster fill(255); ellipse(xPos - b / 5 - 2, yPos - b / 5, b / 5, b / 5); ellipse(xPos, yPos - b / 5, b / 5, b / 5); ellipse(xPos + b / 5 + 2, yPos - b / 5, b / 5, b / 5); } void move(int v) { b += v; if (r == 0) { xPos -= 7; b = b + xPos / b; } if (r == 1) { xPos += 8; b = b + xPos / b; } if (r == 2) { yPos -= 7; b = b + yPos / b; } if (r == 3) { yPos += 9; b = b + yPos / b; } if (xPos < 0 || xPos > width || yPos < 0 || yPos > height) { xPos = width / 2; yPos = random(300) + 100; b = 0; farbe = color(random(256), random(256), random(256)); } } }
Wir wollen ein Programm erstellen, in dem ein kleines Auto vor der Kulisse eines Dorfes aus Häusern und Bäumen vorbeifährt. Erstelle zuerst die entsprechenden Klassen Haus, Baum und Auto und stelle das Hauptprogramm als eigene Klasse Autofahrt dar.
Sketch
Haus h1; Baum b1; Haus h2; Baum b2; Auto a; void setup() { size(600, 400); h1 = new Haus(400, 100, 100, 100); b1 = new Baum(250, 200, 20, 40); h2 = new Haus(100, 100, 100, 100); b2 = new Baum(300, 180, 18, 36); a = new Auto(100, 300, color(255, 0, 0)); } void draw() { background(100); h1.display(); b2.display(); h2.display(); b1.display(); a.display(); a.move(); }
Haus
class Haus { // Attribute int x; int y; int breite; int hoehe; // Konstruktor Haus(int x, int y, int breite, int hoehe) { this.x = x; this.y = y; this.breite = breite; this.hoehe = hoehe; } // Methoden void display() { rectMode(CORNER); fill(255); rect(x, y, breite, hoehe); fill(255, 0, 0); triangle(x, y, x + breite / 2, y - hoehe / 3, x + breite, y); } }
Baum
class Baum { // Attribute int x; int y; int breite; int hoehe; // Konstruktor Baum(int x, int y, int breite, int hoehe) { this.x = x; this.y = y; this.breite = breite; this.hoehe = hoehe; } // Methoden void display() { rectMode(CORNER); fill(255); rect(x, y, breite, hoehe); fill(0, 255, 0); triangle(x - breite * 2, y, x + breite / 2, y - hoehe * 3, x + breite * 3, y); } }
Auto
class Auto { // Attribute int x; int y; color c; // Konstruktor Auto(int x, int y, color c) { this.x = x; this.y = y; this.c = c; } // Methoden void display() { rectMode(CENTER); noStroke(); fill(c); rect(x, y, 80, 20); ellipse(x, y - 10, 40, 40); fill(0); ellipse(x - 25, y + 10, 20, 20); ellipse(x + 25, y + 10, 20, 20); } void move() { x += 3; if (x > width) { x = 0; } } }