In diesem Beitrag wird dieses Prinzip anhand einer einfachen Zaehler-Application demonstriert.
Swing realisiert also kein reines MVC, sondern eine sog. Separable model architecture:
Trennung von Application und inhaltlicher Behandlung der Daten der Applikation. Der Teil für die Behandlung der Daten heisst Model (application-data model).
Diese Tatsache, dass die Application nochmal nachfragt, heisst Lightweight-Notification. Die Alternative wäre, dass das Model bei seiner Rückmeldung gleich die frischen Daten mitbringt (stateful notification).
Die Trennung von Model und Komponente wird dadurch unterstrichen, dass Modelle angepasst und ausgetauscht werden können. Bei Bedarf können sogar mehrere Komponenten auf 1 gemeinsames Model zugreifen und so gemeinsam dieselben Daten benutzen. Demzufolge besitzt jede Komponente folgende Methoden:
public void setModel( XYZModel model ) public XYZModel getModel()Umgekehrt besitzt jedes Model eine Methode, mittels derer es sich merkt, welche Komponente Interesse an den Daten hat:
public void addChangeListener( ChangeListener komponente )Diese Ausdrucksweise deutet an, dass die Komponente aus Sicht des Models die Rolle desjenigen spielt, welcher darauf lauscht ( engl. to listen ), ob im Model Änderungen eingetreten sind.
Für selbstprogrammierte Komponenten gilt, dass man selbst das passende Model bauen muss. Im folgenden Bsp. wird ein Zaehler vorgestellt, für den ein entsprechendes ZaehlerModel implementiert wird.
Die Application besteht aus 2 Interfaces und 3 Klassen.
Der Zusammenhang der Klassen und Interfaces ist wie folgt:
valueChanged( ZaehlerChangeEvent )
package mvc.einfach;
import java.util.EventListener;
public interface ZaehlerChangeListener extends EventListener
{
public void valueChanged( ZaehlerChangeEvent e );
}
In Bezug auf die GUI gibt es hier die üblichen JButton, JTextField, ActionListener, actionPerformed(ActionEvent).
In Bezug auf das Model gibt es setModel( ZaehlerModel ), getModel()
und vor allem valueChanged( ZaehlerChangeEvent )
siehe Interface. Diese Methode fragt beim Model nach: ...getValue()...
also Lightweight-notification, und verarbeitet den Wert weiter.
package mvc.einfach;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class ButtonZaehler extends JPanel implements ActionListener, ZaehlerChangeListener
{
private JButton jButton1;
private JTextField jTextField;
private ZaehlerModel zaehlerModel;
public ButtonZaehler( ZaehlerModel m )
{
super();
jButton1 = new JButton( "+" );
jTextField = new JTextField( 13 );
setLayout( new FlowLayout());
add( jTextField );
add( jButton1 ); // plus-Button
jButton1.addActionListener( this );
setModel( m );
}
public void actionPerformed( ActionEvent e ){
if( e.getActionCommand().equals( "+" )){
getModel().weiter();
}
}
public void valueChanged( ZaehlerChangeEvent e ){
jTextField.setText( getModel().getValue().toString());
}
protected void setModel( ZaehlerModel newModel ){
zaehlerModel = newModel;
zaehlerModel.addChangeListener( this );
}
protected ZaehlerModel getModel()
{
return zaehlerModel;
}
static public void main( String [] args ){
JFrame jFrame = new JFrame( "Zaehler" );
jFrame.getContentPane().add( new ButtonZaehler( new ZahlModel()));
jFrame.setSize( 234, 123 );
jFrame.setVisible( true );
}
}
Die Komponente spielt also in Bezug auf das Model eine Doppelrolle: Einerseits durch den JButton das Model zur Datenänderung veranlassen: getModel().weiter()getModel().getValue()
Diese Klasse hat auch den main der ganzen Application: Es wird 1 JFrame erzeugt und eine Instanz von ButtonZaehler, deren Konstruktor 1 passendes Model-Objekt übergeben wird.
fireStateChanged()valueChanged( ZaehlerChangeEvent )
Das Model erzeugt bei dieser Benachrichtigung ein Objekt der Klasse ZaehlerChangeEvent und schickt es mit der Nachricht an die Komponente. Die Komponente kann daraus weitere Informationen über das Model entnehmen, das diese valueChanged( ZaehlerChangeEvent )-Nachricht geschickt hat. Sie darf dieses aber auch ignorieren, was sie in unserem Bsp. auch tut.
package mvc.einfach;
import java.util.EventObject;
public class ZaehlerChangeEvent extends EventObject
{
public ZaehlerChangeEvent( Object source ){
super( source );
}
public Object getSource(){
return source;
}
}
weiter()getValue()
Das Interface definiert 1 Methode, mittels derer das Model sich merkt, wer von Datenänderungen benachrichtigt werden soll: addChangeListerner( ChangeListener )
Da die Komponente das Interface ChangeListener implementiert, kann sie vom Model benachrichtigt werden. Diese Benachrichtigung wird realisiert durch die Methode fireStateChanged()
Die konkrete Ausgestaltung dieser Methode überlässt das Model-Interface einem konkreten Model.
package mvc.einfach;
public interface ZaehlerModel
{
public Object getValue();
public void weiter();
public void addChangeListener( ZaehlerChangeListener l );
public void fireStateChanged();
}
Die Klasse hat für die Verwaltung des Zahlenwerts Variablen min, max, val
zum Verändern eine Methode weiter()
Zum Benachrichtigen der interessierten Klassen dient die Methode fireStateChange()
Allen ZaehlerChangeListenern wird dadurch in der for-Schleife die Nachricht geschickt: valueChanged( new ZaehlerChangeEvent( this ))
Das this heisst hier: das Model schickt eine Referenz auf sich selbst mit an den ZaehlerChangeListener, zum Zweck der Auswertung durch diesen.
In unserem Bsp. gibt es nur 1 ZaehlerChangeListener, das ist die Komponente.
Damit das Model überhaupt weiss, wer ZaehlerChangeListener ist, werden diese in einem Array listenerArray verwaltet ( Vektor wäre besser ), und es gibt 1 Methode, um die ZaehlerChangeListener in diesen Array einzutragen:addChangeListener( ZaehlerChangeListener )
package mvc.einfach;
public class ZahlModel implements ZaehlerModel{
private int min, max, val;
protected ZaehlerChangeListener listenerArray[] = new ZaehlerChangeListener[7];
private int count = 0;
public ZahlModel(){
val = 0;
min = 0;
max = 24;
}
public Object getValue() {
return new Integer( val );
}
public void addChangeListener( ZaehlerChangeListener l ){
listenerArray[ count++ ] = l;
l.valueChanged( new ZaehlerChangeEvent( this ));
}
public void weiter() {
if( val < max ){
val++;
}
fireStateChanged();
}
public void fireStateChanged(){
for( int i = 0; i < count; i++ ){
listenerArray[ i ].valueChanged( new ZaehlerChangeEvent( this ));
}
}
}
Soweit die kleinst mögliche Version eines einfachen Programms mit GUI und MVC.
Und nicht jeder Zaehler hat 1 plus-Button, manche haben einen Schieberegler usw, also anderes UserInterface. Diese Austauschbarkeit ist ein Vorteil von MVC.
Der andere Aspekt: Weil die ganze Swing-API selbst nach diesem Schema implementiert ist, muss man sich da eh auskennen.
Und: Moderne Entwurfsmethoden, UML, Patterns usw sind zu ihrer Implementierung auf Programmiersprachen (und Programmierer) angewiesen, die so etwas umsetzen können.
Weiter mit ausführlichem Zaehler-Bsp.