Refactoring JavaDraw

JPanel ohne Design Pattern -> Command Pattern

Problemstellung: Alles im JPanel

  • Direkte Implementierung der Zeichenlogik in der Panel-Klasse DrawingPanelBefore
  • Lange if- oder switch-Anweisungen für Mausereignisse
  • Schwierige Erweiterung und Wartung

DrawingPanelBefore

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

public class DrawingPanelBefore extends JPanel implements MouseListener, MouseMotionListener {
    private Point startPoint;
    private Point endPoint;
    private String currentTool = "RECTANGLE"; // Beispielwerkzeug

    public DrawingPanelBefore() {
        addMouseListener(this);
        addMouseMotionListener(this);
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        if (startPoint != null && endPoint != null) {
            int x = Math.min(startPoint.x, endPoint.x);
            int y = Math.min(startPoint.y, endPoint.y);
            int width = Math.abs(startPoint.x - endPoint.x);
            int height = Math.abs(startPoint.y - endPoint.y);

            switch (currentTool) {
                case "RECTANGLE":
                    g.drawRect(x, y, width, height);
                    break;
                // Weitere Fälle für andere Werkzeuge
            }
        }
    }

    @Override
    public void mousePressed(MouseEvent e) {
        startPoint = e.getPoint();
    }

    @Override
    public void mouseReleased(MouseEvent e) {
        endPoint = e.getPoint();
        repaint();
    }

    @Override
    public void mouseDragged(MouseEvent e) {
        endPoint = e.getPoint();
        repaint();
    }

    // Leere Implementierungen für nicht genutzte Methoden
    @Override public void mouseClicked(MouseEvent e) {}
    @Override public void mouseEntered(MouseEvent e) {}
    @Override public void mouseExited(MouseEvent e) {}
    @Override public void mouseMoved(MouseEvent e) {}
}

Refactoring: Einführung des Command Patterns

  • Trennung von Zeichenlogik und UI-Handling

  • Jede Zeichenaktion wird durch ein Command-Objekt gekapselt

  • Einfache Hinzufügung neuer Zeichenwerkzeuge

  • DrawingPanelAfter, interface DrawingCommand, DrawRectangleCommand und weitere.

nach Refactoring

public interface DrawingCommand {
    void mousePressed(int x, int y);
    void mouseDragged(int x, int y);
    void mouseReleased(int x, int y);
    void execute();
    void undo();
}
public class DrawRectangleCommand implements DrawingCommand {
    private Point startPoint;
    private Point currentPoint;
    private DrawingPanelAfter panel;

    public DrawRectangleCommand(DrawingPanelAfter panel) {
        this.panel = panel;
    }

    @Override
    public void mousePressed(int x, int y) {
        startPoint = new Point(x, y);
    }

    @Override
    public void mouseDragged(int x, int y) {
        currentPoint = new Point(x, y);
        panel.repaint();
    }

    @Override
    public void mouseReleased(int x, int y) {
        currentPoint = new Point(x, y);
        panel.addRectangle(startPoint.x, startPoint.y, Math.abs(x - startPoint.x), 
                                                       Math.abs(y - startPoint.y));
        panel.repaint();
    }
    
    // Vorheriger Zustand, z.B. eine Liste aller bisher gezeichneten Formen
    private DrawingPanelAfter panel;

    @Override
    public void execute() {
        // Zeichnen des Rechtecks
    }

    @Override
    public void undo() {
        // Entfernen des zuletzt gezeichneten Rechtecks
    }
}
public class DrawLineCommand implements DrawingCommand {
    private Point startPoint;
    private Point currentPoint;
    private DrawingPanelAfter panel;

    public DrawLineCommand(DrawingPanelAfter panel) {
        this.panel = panel;
    }

    @Override
    public void mousePressed(int x, int y) {
        startPoint = new Point(x, y);
    }

    @Override
    public void mouseDragged(int x, int y) {
        currentPoint = new Point(x, y);
        panel.repaint();
    }

    @Override
    public void mouseReleased(int x, int y) {
        currentPoint = new Point(x, y);
        panel.addLine(startPoint.x, startPoint.y, x, y);
        panel.repaint();
    }
}
import java.util.ArrayList;

public class DrawingPanelAfter extends JPanel implements MouseListener, MouseMotionListener {
    private DrawingCommand currentCommand;
    private ArrayList<Shape> shapes = new ArrayList<>();

    public DrawingPanelAfter() {
        setDrawingTool("LINE"); // Standardmäßig Linien-Werkzeug
        addMouseListener(this);
        addMouseMotionListener(this);
    }

    public void setDrawingTool(String toolType) {
        switch (toolType) {
            case "RECTANGLE":
                this.currentCommand = new DrawRectangleCommand(this);
                break;
            case "LINE":
                this.currentCommand = new DrawLineCommand(this);
                break;
            // Weitere Werkzeuge können hier hinzugefügt werden
        }
    }

    public void addRectangle(int x, int y, int width, int height) {
        shapes.add(new Rectangle(x, y, width, height));
    }

    public void addLine(int startX, int startY, int endX, int endY) {
        shapes.add(new Line2D.Float(startX, startY, endX, endY));
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;
        for (Shape shape : shapes) {
            g2.draw(shape);
        }
    }

    @Override
    public void mousePressed(MouseEvent e) {
        currentCommand.mousePressed(e.getX(), e.getY());
    }

    @Override
    public void mouseReleased(MouseEvent e) {
        currentCommand.mouseReleased(e.getX(), e.getY());
    }

    @Override
    public void mouseDragged(MouseEvent e) {
        currentCommand.mouseDragged(e.getX(), e.getY());
    }

    // Leere Implementierungen für nicht genutzte Methoden
    @Override public void mouseClicked(MouseEvent e) {}
    @Override public void mouseEntered(MouseEvent e) {}
    @Override public void mouseExited(MouseEvent e) {}
    @Override public void mouseMoved(MouseEvent e) {}
}

Command Pattern Struktur

  • Command Interface: Definiert die Aktionen execute(), undo()
  • Konkrete Commands: Implementieren spezifische Zeichenaktionen
  • Invoker: Ruft Commands basierend auf Benutzerinteraktion aus
  • Client: Setzt das aktuelle Command basierend auf der UI-Auswahl

Command Pattern Struktur

Vorteile des Command Patterns

  • Modularität: Jedes Command ist eine eigenständige Einheit
  • Erweiterbarkeit: Neue Commands können leicht hinzugefügt werden
  • Flexibilität: Änderungen an einem Command beeinflussen nicht den Rest des Systems

Zusätzlicher Vorteil: Undo/Redo Mechanismus

  • Einfache Integration eines Undo/Redo-Mechanismus
  • History-Manager verwaltet die Befehlshistorie
  • Befehle können rückgängig gemacht oder wiederholt werden ohne die Hauptlogik zu beeinträchtigen

Implementierung des Undo/Redo Mechanismus

  • Command erweitern: Um undo Methode
  • History Manager: Verwaltet Undo/Redo Stacks
  • Integration: Undo/Redo Aktionen in der UI

Fazit

  • Das Command Pattern verbessert die Struktur und Wartbarkeit
  • Ermöglicht einfache Erweiterungen und Anpassungen
  • Der Undo/Redo-Mechanismus erhöht die Benutzerfreundlichkeit

Refactoring JavaDraw

By Harald Haberstroh

Refactoring JavaDraw

  • 96