Home > Informatik > Informatik EF > Folge 5

5.3 - Workshop "Kreise"

In diesem Workshop geht es weniger um das Zeichnen von Kreisen in einer Java-Anwendung, als vielmehr um die Frage, wie man Graphik-Befehle bündeln und in eigene Klassen außerhalb der Anwendung auslagern kann. Dabei werden wir wieder ganz langsam Schritt für Schritt vorgehen, so dass möglichst viele Benutzer(innen) dieser Seite auch folgen können. Wir beginnen erst mal ganz "harmlos" und lassen ein paar Kreise in eine Java-Anwendung zeichnen. Das haben wir ja bereits in der Folge 5.1 gemacht.

Schritt 1 - Eine Anwendung mit mehreren Kreisen erstellen

Starten Sie BlueJ und legen Sie ein neues Projekt an mit einer Java-Anwendung an. Entfernen Sie den Quelltext aus dieser Klasse und kopieren Sie den Minimal-Quelltext einer Java-Anwendung in die leere Klasse hinein. Ersetzen Sie dann den Quelltext der paint-Methode durch diesen Quelltext:

    public void paint(Graphics g)
    {
        g.setColor(new Color(221,127,63));
        g.fillOval(50,50,100,100);
        g.setColor(Color.BLACK);
        g.drawOval(50,50,100,100);

        g.setColor(new Color(127,221,63));
        g.fillOval(150,150,70,70);
        g.setColor(Color.BLACK);
        g.drawOval(150,150,70,70);     

        g.setColor(new Color(255,127,127));
        g.fillOval(250,250,50,50);
        g.setColor(Color.BLACK);
        g.drawOval(250,250,50,50);            
    }

Wenn Sie die Klasse kompilieren und dann die main()-Methode starten, sollten drei farbige Kreise zu sehen sein:

Drei bunte Kreise in einer Reihe, die von links oben nach rechts unten geht. Dabei werden die Kreise immer kleiner. Die drei Kreise haben unterschiedliche Pastellfarben.

Drei farbige Kreise abnehmender Größe
Autor: Ulrich Helmich, Lizenz: siehe Seitenende.

Schritt 2 - Auslagern in eine Methode

Wenn wir uns den Quelltext der paint-Methode näher ansehen, fällt auf, dass im Grunde dreimal die gleichen vier Befehle ausgeführt werden:

  1. Eine Farbe für den Kreis wählen mit setColor().
  2. Den Inhalt des Kreises in dieser Farbe zeichnen mit fillOval().
  3. Die Farbe Schwarz wählen mit setColor().
  4. Die Umrandung des Kreises zeichnen mit drawOval().

Wenn man ein Programm analysiert und dabei feststellt, dass bestimmte Befehle immer wieder wiederholt werden, dann ist es üblich, diese Befehle in eine eigene Methode auszulagern. Genau das machen wir jetzt:

    public void drawCircle(Graphics g, int x, int y, int rad, Color c)
    {
       g.setColor(c);
       g.fillOval(x-rad,y-rad,rad*2,rad*2);
       g.setColor(Color.BLACK);
       g.drawOval(x-rad,y-rad,rad*2,rad*2);       
    }
    
    public void paint(Graphics g)
    {
        drawCircle(g,100,100, 50,new Color(221,127, 63));
        drawCircle(g,200,200, 35,new Color(127,221, 63));
        drawCircle(g,300,300, 25,new Color(255,127,127));    
    }

Die Methode drawCircle(), die wir gerade geschrieben haben, übernimmt das Zeichnen eines Kreises, sie führt die oben genannten vier Befehle aus. Außerdem nimmt die Methode jetzt die Koordinaten des Kreismittelpunktes als Parameter an und nicht mehr die Koordinaten der linken oberen Ecke! Das ist wesentlich natürlicher!

Die paint()-Methode hat sich damit drastisch verkürzt, da nur noch dreimal die Methode drawCircle() aufgerufen werden muss. Zugegeben, durch die lange Parameterliste geht etwas an Übersichtlichkeit verloren. Aber dafür haben wir das Zeichnen des Kreises auf eine Stelle im Quelltext konzentriert.

Vorteile der Auslagerung

Stellen Sie sich vor, der Rand der Kreise soll nicht eine Breite von 1 Pixel haben, sondern von 2 Pixeln. In dem "alten" Quelltext müssten Sie jetzt an drei Stellen der paint-Methode den entsprechenden neuen Befehl einbauen. Da wir aber das Zeichnen der Kreise in eine eigene Methode ausgelagert haben, müssen wir nur noch diese eine Methode verändern; eine zusätzliche Zeile oder eine veränderte Anweisung genügt hier vollkommen.

Schritt 3 - Auslagerung in eine eigene Klasse

Das Auslagern der eigentlichen Arbeit (Zeichnen eines farbigen Kreises mit schwarzer Umrandung) in eine eigene Methode war schon einmal ein guter Schritt. Man kann das Ganze aber noch weiter optimieren. Wir erzeugen jetzt nämlich eine eigene Klasse Circle, die sich um das Zeichnen von Kreisen kümmert. Greifen wir einmal einen Schritt vor und sehen uns eine Anwendung an, die bereits auf die fertige Klasse Circle zugreift:

import java.awt.*;
import javax.swing.*;

public class Anmwendung1 extends JFrame
{
    Circle kreis1, kreis2, kreis3;
    
    public Anmwendung1()
    {
        kreis1 = new Circle(100,100, 50,new Color(221,127, 63));
        kreis2 = new Circle(200,200, 35,new Color(127,221, 63));
        kreis3 = new Circle(300,300, 25,new Color(255,127,127));
        
        setSize(500,500);
        setTitle("Workshop 'Kreise'");
        setResizable(false);
        setVisible(true);
    }

    public void paint(Graphics g)
    {
        kreis1.paint(g);
        kreis2.paint(g);
        kreis3.paint(g);
    }

    public static void main(String[] args) 
    {
        new Anmwendung1();
    }
}

Was ist hier neu? Am Anfang werden drei Objekte der Klasse Circle deklariert:

Circle kreis1, kreis2, kreis3;

Um die Klasse Circle kümmern wir uns gleich. Zunächst mal weiter mit der Java-Anwendung.

Im Konstruktor der Anwendung werden die drei Circle-Objekte initialisiert; es wird also für jedes Objekt der Konstruktor der Klasse Circle aufgerufen. Dieser Konstruktor erwartet vier Parameter:.

  1. X-Position des Kreismittelpunktes
  2. Y-Position des Kreismittelpunktes
  3. Radius des Kreises
  4. Farbe des Kreises

Das sind genau die gleichen Parameter, wie wir sie bei der internen Methode drawCircle() in Schritt 2 verwendet haben. Lediglich der Graphics-Parameter g fehlt hier.

Nach der Initialisierung der drei Circle-Objekte wird das Fenster selbst initialisiert, wie üblich wird die Größe bestimmt, der Titel und so weiter.

In der paint()-Methode werden die drei Kreise nun gezeichnet. Das geschieht nach dem Prinzip der Delegation. Die Klasse Circle stellt offensichtlich eine eigene paint()-Methode zur Verfügung, die jetzt einfach aufgerufen werden kann. Dazu muss das Graphics-Objekt g an die Circle-Methode paint() übergeben werden.

Die Klasse Circle

Betrachten wir nun die neue Klasse Circle, die wir selbst schreiben müssen, weil sie von Java nicht zur Verfügung gestellt wird:

import java.awt.*;

public class Circle
{
    int x, y, rad;
    Color c;
    
    public Circle(int xPos, int yPos, int radius, Color color)
    {
        x   = xPos;
        y   = yPos;
        rad = radius;
        c   = color;
    }
    
    public void paint(Graphics g)
    {
       g.setColor(c);
       g.fillOval(x,y,rad*2,rad*2);
       g.setColor(Color.BLACK);
       g.drawOval(x,y,rad*2,rad*2);    
    }
}

Die Eigenschaften eines Kreises werden in Form von vier Attributen verwaltet:

  1. int x = X-Position des Kreismittelpunktes
  2. int y = Y-Position des Kreismittelpunktes
  3. int rad = Radius des Kreises
  4. Color c = Farbe des Kreises

Der Konstruktor der neuen Klasse nimmt die Werte dieser vier Attribute als Parameter entgegen und weist die Parameter-Werte dann den Attributen zu.

Die paint()-Methode der Klasse Circle benötigt einen Parameter der Klasse Graphics, damit sie in die Java-Anwendung zeichnen kann. Ansonsten wird der Kreis genau so gezeichnet wie bisher: Zuerst das farbige Innere, dann der schwarze Rand.

Der import-Befehl ganz zu Beginn des Quelltextes ist nötig, damit die Klasse Graphics mit ihren Methoden zur Verfügung steht.

Übungen

Kommen wir nun wieder zu ein paar Übungen, zunächst recht leicht, dann etwas anspruchsvoller.

Übung 5.3-1

Erweitern Sie die Klasse Circle so, dass der User auch die a) Dicke und b) Farbe des Kreisrandes über je einen Parameter bestimmen kann (Datentypen int bzw. Color).

Lösungsvorschlag (nur für Lehrpersonen, Zugangsdaten erforderlich).

Bevor wir mit der nächsten Übung weitermachen, noch ein kleiner Informations-Input:

Die Klasse Color hat zwei interessante und praktische Methoden, nämlich brighter() und darker(). Diese beiden Methoden machen die angewandte Farbe etwas heller bzw. etwas dunkler, sofern das technisch noch möglich ist. Die erste Methode kann man zum Beispiel für die Erzeugung von Glanzpunkten in einem Kreis verwenden, so dass er eher wie eine Kugel aussieht (mit viel Phantasie):

Eine Anwendung mit vier "Kugeln"
Autor: Ulrich Helmich 2021, Lizenz: siehe Seitenende

Hier ist der Quelltext der Klasse Anwendung1, welcher dieser vier Kugel-Objekte erzeugt; die vier Zeilen befinden sich im Konstruktur der Klasse Anwendung1:

   kreis1 = new Circle( 80, 80, 50,new Color(221,127, 63));
   kreis2 = new Circle(150,150, 35,new Color(127,221, 63));
   kreis3 = new Circle(250,250, 25,new Color(255,127,127));
   kreis4 = new Circle(350,120, 75,new Color(127,127,191));

Ihre Aufgabe ist es nun, die Klasse Circle so zu verändern, dass ein solcher Glanzpunkt in die linke obere Ecke einer Kugel gesetzt wird. Der Durchmesser dieses Glanzpunktes sollte dabei vom Durchmesser der Kugel abhängen; je größer die Kugel, desto größer auch der Durchmesser des Glanzpunktes.

Wie erzeugt und verwendet man nun eine hellere Farbe?

Angenommen, in der Klasse Circle ist die Farbe des Kreises in dem Attribut c vom Typ Color gespeichert.

Mit g.setColor(c) setzen Sie die normale Farbe der Kugel, wie gewohnt. Mit g.setColor(c.brighter()) dagegen setzen Sie die hellere Farbe im gleichen Farbton.

Übung 5.3-2

Erweitern Sie die Klasse Circle so, dass die Kreise einen Glanzpunkt wie eben im Text beschrieben bekommen. Testen Sie die Klasse Circle dann mit Hilfe der Klasse Anwendung1.

Lösungsvorschlag (nur für Lehrpersonen, Zugangsdaten erforderlich).

Die Klasse Anwendung1:

import java.awt.*;
import javax.swing.*;

public class Anmwendung1 extends JFrame
{
    Circle kreis1, kreis2, kreis3, kreis4;
    
    public Anmwendung1()
    {
        
        kreis1 = new Circle( 80, 80, 50,new Color(221,127, 63));
        kreis2 = new Circle(150,150, 35,new Color(127,221, 63));
        kreis3 = new Circle(250,250, 25,new Color(255,127,127));
        kreis4 = new Circle(350,120, 75,new Color(127,127,191));

        setSize(500,500);
        setTitle("Uebung 5.3-2");
        setResizable(false);
        setVisible(true);
    }

    public void paint(Graphics g)
    {  
        kreis1.paint(g);
        kreis2.paint(g);
        kreis3.paint(g);    
        kreis4.paint(g);           
    }

    public static void main(String[] args) 
    {
        new Anmwendung1();
    }
}

Implementationsdiagramme

Im Zentralabitur NRW wird erwartet, dass Sie mit sogenannten Implementationsdiagrammen umgehen können. Das heißt, nicht nur lesen und erläutern, sondern auch selbst entwickeln oder zumindest erweitern. Betrachten wir ein solches Implementationsdiagramm für unsere beiden Klassen Circle und Anwendung1:

Implementationsdiagramm der beiden verwendeten Klassen
Autor: Ulrich Helmich 2021, Lizenz: siehe Seitenende

Eine Klasse wird in einem solchen Implementationsdiagramm durch einen horizontal dreigeteilten Kasten dargestellt. Im oberen "Fach" befindet sich der Bezeichner der Klasse. Ob Sie den Bezeichner jetzt zentrieren oder linksbündig schreiben, ist eigentlich egal. In den Abituraufgaben ist er meistens zentriert.

Im zweiten Fach werden die relevanten Attribute der Klasse aufgelistet, und zwar in einer etwas anderen Syntax als bei Java üblich. Das liegt daran, dass diese Diagramme unabhängig von einer bestimmten Programmiersprache sein sollen. Aber schwer zu verstehen sind diese Diagramme nicht. Man sieht bei der Klasse Anwendung zum Beispiel, dass es drei Attribute der Klasse Circle gibt, die kreis1 bis kreis3 heißen.

Im letzten Fach schließlich stehen die relevanten Methoden der Klasse. "Relevant" heißt hier, dass Sie nicht ständig alle Attribute und Methoden aufführen müssen, sondern nur die, die zum Verständnis des Diagramms erforderlich sind. Aus diesem Grund wurde die main()-Methode der Klasse Anwendung nicht mit aufgeführt, weil jede Java-Anwendung standardmäßig eine main()-Methode hat.

Übung 5.3-3

Betrachten wir einmal ein Implementationsdiagramm aus dem NRW-Abitur 2020, allerdings nur einen kleinen und vereinfachten Ausschnitt, weil Sie die Voraussetzungen zum Verständnis des gesamten Projektes noch nicht haben (zum Beispiel haben wir noch keine Arrays behandelt oder das Thema Vererbung).

Implementationsdiagramm in Anlehnung an eine Abituraufgabe von 2020
Autor: Ulrich Helmich 2021, Lizenz: siehe Seitenende

Erläutern Sie dieses Diagramm! Was wird hier modelliert? In welcher Beziehung stehen die beiden Klassen? Was kann man genau mit diesen beiden Klassen anfangen?

Was könnte man an diesem Modell noch verbessern?

Lösungsvorschlag (nur für Lehrpersonen, Zugangsdaten erforderlich).