Muster: Java: Command

Das Command-Pattern erkläre ich anhand eines kleinen Beispieles. Es soll ein Fenster erstellt werden, welches bei einem Klick mit der linken Maustaste die Hintergrundfarbe ändert und bei einen Rechtsklick die Position wechselt. Wenn man das Mausrad drückt, soll die jeweils letzte Aktion rückgängig gemacht werden.

Dazu bauen wir das Programm in mehrere Klassen auf. Zunächst brauchen wir das Fenster. Hier passiert aber nichts spannendes. Der JFrame wird sichtbar gemacht und ein MouseListener wird hinzugefügt.

package wiki.pattern.command;

import javax.swing.JFrame;

public class MyFrame extends JFrame{
    private CommandHandler handler;
    
    public MyFrame(){
	handler = new CommandHandler();
	
	this.setSize(200, 200);
	this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	this.setVisible(true);
	
	this.addMouseListener(new MyListener(this));
    }
    
    public CommandHandler getHandler() {
        return handler;
    }
}

Auf die Klasse CommandHandler geh ich weiter unten ein. In der Klasse MyListener wird nur auf einen Mausklick mit einen entsprechendem Befehl reagiert:

package wiki.pattern.command;

import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

public class MyListener implements MouseListener{
    private MyFrame frame;
    
    public MyListener(MyFrame frame){
	this.frame = frame;
    }
  
    @Override
    public void mouseClicked(MouseEvent arg0) {
	if (arg0.getButton() == MouseEvent.BUTTON1) {
	    frame.getHandler().doIt(new ColorCommand(frame));
	} else if (arg0.getButton() == MouseEvent.BUTTON3){
	    frame.getHandler().doIt(new ColorCommand(frame));
	} else {
	    frame.getHandler().undoIt();
	}
    }

    @Override
    public void mouseEntered(MouseEvent arg0) {
	// TODO Auto-generated method stub
    }
    @Override
    public void mouseExited(MouseEvent arg0) {
	// TODO Auto-generated method stub
    }
    @Override
    public void mousePressed(MouseEvent arg0) {
	// TODO Auto-generated method stub
    }
    @Override
    public void mouseReleased(MouseEvent arg0) {
	// TODO Auto-generated method stub
    }
}

Die Befehle brauchen eine gemeinsame Schnittstelle, damit wir sie gleichartig behandeln können. Dazu definieren wir folgendes Interface:

package wiki.pattern.command;

public interface Command {
    public void doIt();
    public void undoIt();
}

Die Implementierung der beiden Methoden wird von den konkreten Befehlen übernommen. Am Beispiel des Kommando für den Farbwechsel kann das so aussehen:

package wiki.pattern.command;

import java.awt.Color;

public class ColorCommand implements Command{
    private MyFrame frame;
    private Color oldCol;
    private Color newCol;    
    
    public ColorCommand(MyFrame frame){
	this.frame = frame;
    }
    
    @Override
    public void doIt() {
	oldCol = frame.getContentPane().getBackground();
	newCol = new Color((int) (Math.random() * 255), (int) (Math.random() * 255), (int) (Math.random() * 255));
	frame.getContentPane().setBackground(newCol);
    }

    @Override
    public void undoIt() {
	frame.getContentPane().setBackground(oldCol);
    }
}

Wenn auf das Kommando die doIt Methode aufgerufen wird, wird der alte Wert gespeichert (der Befehl soll sich ja auch rückgängig machen lassen), und die neue Hintergrundfarbe wird gesetzt. Ob ein Befehl jetzt ausgeführt oder wieder rückgängig gemacht wird, hängt natürlich davon ab welche Maustaste gedrückt wurde. Die eigentliche Arbeit wird aber an den CommandHandler weiter gegeben.

package wiki.pattern.command;

import java.util.Stack;

public class CommandHandler {
    private Stack<Command> commands;
    
    public CommandHandler(){
	commands = new Stack<Command>();
    }
    
    public void doIt(Command command){
	commands.push(command);
	command.doIt();
    }
    
    public void undoIt(){
	if (!commands.isEmpty()) {
	    commands.pop().undoIt();
	}
    }
}

Die Klasse hat einen Stack über alle Befehle. Die Klasse Stack bietet sich hier besonders an, da das, was zu erst rein kommt ganz unten liegt und neues oben drauf kommt (first in - last out). Mit push(Command) wird also ein neuer Befehl auf den Stack gelegt. Und wenn es an der Zeit ist einen Befehl rückgangig zu machen, liefert die Methode pop() das oberste Element vom Stack und entfernt dieses gleichzeitig. Auf die von pop() gelieferte Referenzen rufen wir unsere undoIt()-Methode auf.