Frage zum heiss geliebten Thema MVC

Status
Nicht offen für weitere Antworten.

daNny

Aktives Mitglied
Hallo Java-Freunde!

Ich versuche mich nun gerade, in das Prinzip des Model-View-Control Pattern einzuarbeiten. Dazu habe ich nun eine kleine Anwendung geschrieben. Ich habe sie ziemlich klein gehalten, weshalb das Anwenden vom MVC-Prinzip wohl etwas überdimensioniert ist, aber es geht mir ja darum, das Prinzip zu verstehen.
Die Anwendung speichert in einem MemoryModel einen aktuellen Wert, der als Speicher dient, und einen Wert für das Intervall, mit dem man den Speicher erhöhen und verringern kann.
Als View-Komponente habe ich einen MemoryFrame erstellt, der als Hauptfenster dienen soll. Er bietet ein Label, in dem der aktuelle Wert des Models steht, und zwei Schaltflächen zum erhöhen und verringern des Wertes. DIe Schritte, um die der Wert erhöht wird, soll dabei in der Größe des im Model gespeicherten Intervals erfolgen. Zwischen den beiden Komponenten gibt es einen MemoryController, der als Vermittler zwischen beiden dienen soll. Grafisch sieht das ganze nun so aus:

mvc1.jpg


Wenn man jetzt auf den zusätzlich vorhandenen, dritten Button klickt, so soll sich ein neuer, selbst erstellter Dialog öffnen, mit dem man die Schrittefolge, in der sich der Wert ändert, ändern kann. Er sieht so aus:

mvc2.jpg


Ich poste nun hier einmal meinen Quellcode:

Code:
public class MemoryModel extends Observable {
    
    /** Das Intervall, in dem <code>value</code> erhöht/verringert wird */
    private int interval;
    
    /** Der aktuelle Wert */
    private int value;
    
    /**
     * Erzeugt ein neues MemoryModel
     * Der Standardwert für <code>interval</code> ist <code>1</code>, 
     * der Standardwert für <code>value</code> ist <code>0</code>
     */
    public MemoryModel() {
        this.interval = 1;
        this.value = 0;
    }
    
    /**
     * Erhöht <code>value</code> um den Wert <code>interval</code>
     */
    public void increment() {
        this.value = this.value + this.interval;
        setChanged();
        notifyObservers();
    }
    
    /**
     * Verringert <code>value</code> um den Wert <code>interval</code>
     */
    public void decrement() {
        this.value = this.value - this.interval;
        setChanged();
        notifyObservers();
    }
    
    /**
     * Gibt den aktuell gespeicherten Wert zurück
     * @return aktueller Wert
     */
    public int getValue() {
        return this.value;
    }
    
    public void setInterval(int interval) {
        this.interval = interval;
    }

}

Code:
public class MemoryFrame extends JFrame implements Observer {
    
    /** Die Anzeige für den aktuellen Wert des Models */
    private JLabel valueLabel;
    
    /** Der verwendete MemoryController */
    private MemoryController controller;
    
    /** Das verwendete MemoryModel, das observiert wird */
    private MemoryModel model;
    
    /**
     * Erstellt einen neuen MemoryFrame
     */
    public MemoryFrame(MemoryController controller) {
        this.controller = controller;
        this.model = controller.getModel();
        this.model.addObserver(this);
        createUI();
    }
    
    /**
     * Erstellt die GUI des Frames
     */
    public void createUI() {
        JPanel pane = new JPanel();
        this.valueLabel = new JLabel("" + model.getValue());
        
        // Button zum Erhöhen des Wertes
        JButton b1 = new JButton("Erhöhen");
        b1.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                controller.increment();
            }
        });
        
        // Button zum verringern des Wertes
        JButton b2 = new JButton("Verringern");
        b2.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                controller.decrement();
            }
        });
        
        // Button zum ändern der Intervalgröße
        JButton b3 = new JButton("Interval verändern");
        b3.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                ChangeIntervalController intController = new ChangeIntervalController(controller);
                
                // Wurde der neue Wert auch bestätigt oder das Fenster nur geschlossen?
                if (intController.getModel().isConfirmed()) {
                    controller.setInterval(intController.getModel().getInterval());
                }
            }
        });

        pane.add(this.valueLabel);
        pane.add(b1);
        pane.add(b2);
        pane.add(b3);
        add(pane);
        setTitle("Verrgingern/Vergrößern des gespeicherten Wertes");
        setSize(800, 100);
        setVisible(true);
    }
    
    /** 
     * Sorgt für ein Update des Labels, falls sich der Wert des Models geändert hat.
     * @param o das observierte Objekt
     * @param arg Optionales Datenobjekt
     */
    public void update(Observable o, Object arg) {
        this.valueLabel.setText("" + ((MemoryModel)o).getValue() );
    }

}

Code:
public class MemoryController {
    
    /** Das MemoryModel */
    private MemoryModel model;
    
    /** Instanz des Frames */
    private MemoryFrame frame;
    
    public MemoryController() {
        this.model = new MemoryModel();
        frame = new MemoryFrame(this);
    }
    
    /**
     * Gibt das Model diesen Controllers zurück
     * @return das Model
     */
    public MemoryModel getModel() {
        return this.model;
    }
    
    /**
     * Gibt die Instanz des Frames zurück
     * @return der Frame
     */
    public MemoryFrame getFrame() {
        return this.frame;
    }
    
    /**
     * Lässt das Model seinen Wert erhöhen
     */
    public void increment() {
        this.model.increment();
    }
    
    /** Lässt das Model seinen Wert verringern */
    public void decrement() {
        this.model.decrement();
    }
    
    /**
     * Ändert den Interval des Models
     * @param interval
     */
    public void setInterval(int interval) {
        this.model.setInterval(interval);
    }

}

Das wäre nun also der Teil, der sozusagen das Hauptfenster darstellt. Jetzt habe ich für den Dialog ebenfalls ein Model, ein View und ein Controller erstellt. Man könnte zwar auch einen einfachen Dialog über das JOptionPane erstellen, aber ich will das Prinzip ja verstehen, mit dem man an ein MVC erstelltes Projekt angeht. Hier also der Code, der für den Dialog zuständig ist:

Code:
public class ChangeIntervalModel extends Observable {
    
    /** Der Wert, auf den das Interval des MemoryModels gesetzt werden soll */
    private int newInterval;
    
    /** Zeigt an, ob dieses Intervall wirklick übernommen werden soll */
    private boolean confirmed;
    
    /** 
     * Setzt den Wert des neuen Intervals
     */
    public void setInterval(int interval) {
        this.newInterval = interval;
        setChanged();
        notifyObservers();
    }
    
    /**
     * Gibt den Wert des neuen Intervals zurück
     * @return das neue Interval
     */
    public int getInterval() {
        return this.newInterval;
    }
    
    /**
     * Erhöht <code>value</code> um den Wert <code>interval</code>
     */
    public void increment() {
        this.newInterval++;
        setChanged();
        notifyObservers();
    }
    
    /**
     * Verringert <code>value</code> um den Wert <code>interval</code>
     */
    public void decrement() {
        this.newInterval--;
        setChanged();
        notifyObservers();
    }
    
    /**
     * Setzt <code>confirmed</code> auf <code>true</code>
     */
    public void confirm() {
        this.confirmed = true;
    }
    
    /**
     * Gibt zurück, ob dieses Intervall wirklich übernommen werden soll
     * @return <code>true</code>, falls es übernommen werden soll
     */
    public boolean isConfirmed() {
        return this.confirmed;
    }

}

Code:
public class ChangeIntervalDialog extends JDialog implements Observer {
    
    /** Der Controller zum Wechsel des Intervals */
    private ChangeIntervalController controller;
    
    /** Das Model des Interval-Wechslers */
    private ChangeIntervalModel model;
    
    /** Label für das neue Interval */
    private JLabel newIntervalLabel;
    
    /**
     * Erstellt einen neuen Dialog zum Ändern des Intervals
     * @param frame Vater-Frame
     * @param modal 
     * @param controller der verwendete Controller
     */
    public ChangeIntervalDialog(JFrame frame, boolean modal, ChangeIntervalController controller) {
        super(frame, modal);
        this.controller = controller;
        this.model = controller.getModel();
        this.model.addObserver(this);
        createUI();
    }
    
    /**
     * Erstellt die Oberfläche des Interval-Wechslers
     */
    public void createUI() {
        JPanel pane = new JPanel();
        newIntervalLabel = new JLabel("Geplantes Interval: " + this.model.getInterval());
        
        // Button zum Erhöhen des neuen Intervals
        JButton b1 = new JButton("Erhöhen");
        b1.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                controller.increment();
            }
        });
        
        // Button zum Verringern des neuen Intervals
        JButton b2 = new JButton("Verringern");
        b2.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                controller.decrement();
            }
        });
        
        JButton b3 = new JButton("Wert übernehmen");
        b3.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                controller.confirm();
                setVisible(false);
            }
        });

        pane.add(newIntervalLabel);
        pane.add(b1);
        pane.add(b2);
        pane.add(b3);
        add(pane);
        setSize(500, 200);
        setVisible(true);
    }
    
    /**
     * Ändert das Label auf den Wert des geplanten, neuen Intervals
     * @param o das observierte ChangeIntervalModel
     * @param arg optionales Datenobjekt
     */
    public void update(Observable o, Object arg) {
        this.newIntervalLabel.setText("Geplantes Interval: " + ((ChangeIntervalModel)o).getInterval());
    }

}

Code:
public class ChangeIntervalController {
    
    /** Das Model für den Interval-Wechsler */
    private ChangeIntervalModel model;
    
    /** Instanz des Hauptframes / Ermittelt durch den "Vater-Controller" */
    private JFrame mainframe;
    
    /**
     * Erstellt einen neuen ChangeIntervalController
     */
    public ChangeIntervalController(MemoryController controller) {
        this.model = new ChangeIntervalModel();
        mainframe = controller.getFrame();
        ChangeIntervalDialog diag = new ChangeIntervalDialog(mainframe, true, this);
    }
    
    /**
     * Gibt das verwendete Model zurück
     * @return das Model
     */
    public ChangeIntervalModel getModel() {
        return this.model;
    }
    
    /**
     * Erhöht den Wert des geplanten, neuen Intervals
     */
    public void increment() {
        this.model.increment();
    }
    
    /**
     * Verringert den Wert des geplanten, neuen Intervals
     */
    public void decrement() {
        this.model.decrement();
    }
    
    public void confirm() {
        this.model.confirm();
    }

}

Das war jetzt ziemlich viel zu lesen, aber vll. ist ja noch jemand hier unten angekommen ;)
Meine Frage ist nun: Habe ich das Prinzip des MVC halbwegs korrekt angewendet?
Sollte der Controller immer eine Instanz auf das Model UND den View als Attribut besitzen? Wenn man in den Code schaut, dann sieht man ja, dass ich in dem MemoryView einen neuen ChangeIntervallController erstelle, um den Dialog zum Ändern des Intervals anzuzeigen. Um den Dialog auch hinterher modal verwenden zu können, muss ich die Referenz auf das Vater-Fenster ja irgendwie an den Dialog übergeben können.

Habe ich das so ungefähr richtig gemacht? Der MemoryCOntroller kann ja die Referenz auf die View-Komponente zurückgeben. Deshalb habe ich dem ChangeIntervalController über den Konstruktor eine Instanz des Vater-Kontrollers mit übergeben.
Kann man das so machen?

Ich hoffe, dass mir jemand ein wenig kritik und anregung geben kann.

Danke schonmal ;)
 

HLX

Top Contributor
Das sieht im Prinzip schon ganz gut aus.

Du kannst das Ganze allerdings noch weiter modularisieren. Dein Frame braucht das Controller-Objekt nicht zwingend zu kennen. Du verwendest es derzeit in den Action-Performed-Methoden deiner Listener. Der Inhalt dieser Methoden ist im Sinne von MVC ebenfalls Bestandteil des Controllers. Er steuert nämlich einen Teil deiner Anwendung, in dem er Backend-Operationen aufruft und die GUI verändert. Somit besteht der Controller nicht nur aus dem Controller-Objekt, sondern aus der Gesamtheit aller anwendungssteuernden Elemente.

Der Controller kennt als zentrale Schnittstelle Model und GUI und kann somit ebenfalls die Listener zuweisen. Das muss nicht in der Frame-Klasse (GUI) geschehen. Wenn du also die Listener im Controller definierst wird die GUI von der Controller-Referenz befreit und kann im Prinzip auf verschiedene Controller aufgesetzt werden.
 

daNny

Aktives Mitglied
WIe genau habe ich das zu verstehen? Irgendwie bin ich da grad am überlegen:

Würde das heissen, dass ich die Buttons als Attribute des Frames definieren würde, und für jeden dann eine getter-Methode zur verfügung stelle, damit ich im Controller, der den Frame ja kennt, dem Button einen Listener zuweisen kann?

Das wäre jetzt das, was mir spontan dazu einfallen würde, was du gesagt hast.

Ich dachte immer, dass die zur View gehörende Anwendungssteurung auch im View bleiben sollte. Aber das wäre natürlich sehr praktisch, das ganze den Controller regeln zu lassen. Ich dachte halt, dass dieser nur Änderungen des Models delegieren soll...

Edit: Was mich nun noch brennend interessiert: Wenn ich nun über den Listener den Controller für den Dialog aufrufe, soll ich diesem dann am besten den "Vater-Controller" oder den "Vater-View" übergeben? Ich brauche ja auf jeden fall den Vater-View, um den Dialog zu erstellen.

Edit2: Ich habe das jetzt mal abgeändert, wie ich es mir so ungefähr vorstelle. Die Models sind komplett gleich geblieben, in den Views habe ich alles, was auf den Controller deutet, entfernt und die Listener entfernt, die Buttons dagegen als Attribute definiert und passende getter-Methoden erstellt.
Die Controller habe ich dahingehend angepasst, als dass sie nun die Listener setzen:

Die Views
Code:
public class MemoryFrame extends JFrame implements Observer {
    
    /** Die Anzeige für den aktuellen Wert des Models */
    private JLabel valueLabel;

    /** Das verwendete MemoryModel, das observiert wird */
    private MemoryModel model;
    
    private JButton b1;
    private JButton b2;
    private JButton b3;
    
    /**
     * Erstellt einen neuen MemoryFrame
     */
    public MemoryFrame(MemoryModel model) {
        this.model = model;
        this.model.addObserver(this);
        createUI();
    }
    
    /**
     * Erstellt die GUI des Frames
     */
    public void createUI() {
        JPanel pane = new JPanel();
        this.valueLabel = new JLabel("" + model.getValue());
        
        // Button zum Erhöhen des Wertes
        b1 = new JButton("Erhöhen");
        // Button zum verringern des Wertes
        b2 = new JButton("Verringern");
        // Button zum ändern der Intervalgröße
        b3 = new JButton("Interval verändern");
        pane.add(this.valueLabel);
        pane.add(b1);
        pane.add(b2);
        pane.add(b3);
        add(pane);
        setTitle("Verrgingern/Vergrößern des gespeicherten Wertes");
        setSize(800, 100);
    }
    
    public JButton getButton1() {
        return this.b1;
    }
    public JButton getButton2() {
        return this.b2;
    }
    public JButton getButton3() {
        return this.b3;
    }
    
    /** 
     * Sorgt für ein Update des Labels, falls sich der Wert des Models geändert hat.
     * @param o das observierte Objekt
     * @param arg Optionales Datenobjekt
     */
    public void update(Observable o, Object arg) {
        this.valueLabel.setText("" + ((MemoryModel)o).getValue() );
    }

}

Code:
public class ChangeIntervalDialog extends JDialog implements Observer {
        
    /** Das Model des Interval-Wechslers */
    private ChangeIntervalModel model;
    
    /** Label für das neue Interval */
    private JLabel newIntervalLabel;
    
    private JButton b1;
    private JButton b2;
    private JButton b3;
    
    /**
     * Erstellt einen neuen Dialog zum Ändern des Intervals
     * @param frame Vater-Frame
     * @param modal 
     * @param controller der verwendete Controller
     */
    public ChangeIntervalDialog(JFrame frame, boolean modal, ChangeIntervalModel model) {
        super(frame, modal);
        this.model = model;
        this.model.addObserver(this);
        createUI();
    }
    
    /**
     * Erstellt die Oberfläche des Interval-Wechslers
     */
    public void createUI() {
        JPanel pane = new JPanel();
        newIntervalLabel = new JLabel("Geplantes Interval: " + this.model.getInterval());
        
        // Button zum Erhöhen des neuen Intervals
        b1 = new JButton("Erhöhen");
        // Button zum Verringern des neuen Intervals
        b2 = new JButton("Verringern");
        // Button zum Übernehmen des Wertes
        b3 = new JButton("Wert übernehmen");
        pane.add(newIntervalLabel);
        pane.add(b1);
        pane.add(b2);
        pane.add(b3);
        add(pane);
        setSize(500, 200);
    }
    
    public JButton getButton1() {
        return this.b1;
    }
    public JButton getButton2() {
        return this.b2;
    }
    public JButton getButton3() {
        return this.b3;
    }
    
    /**
     * Ändert das Label auf den Wert des geplanten, neuen Intervals
     * @param o das observierte ChangeIntervalModel
     * @param arg optionales Datenobjekt
     */
    public void update(Observable o, Object arg) {
        this.newIntervalLabel.setText("Geplantes Interval: " + ((ChangeIntervalModel)o).getInterval());
    }

}

Die Controller

Code:
public class MemoryController {
    
    /** Das MemoryModel */
    private MemoryModel model;
    
    /** Instanz des Frames */
    private MemoryFrame frame;
    
    public MemoryController() {
        this.model = new MemoryModel();
        frame = new MemoryFrame(model);
        frame.getButton1().addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                increment();
            }
        });
        frame.getButton2().addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                decrement();
            }
        });
        frame.getButton3().addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                ChangeIntervalController intervalController = new ChangeIntervalController(frame);
                
                // Wurde der neue Wert auch bestätigt oder das Fenster nur geschlossen?
                if (intervalController.getModel().isConfirmed()) {
                    intervalController.setInterval(intervalController.getModel().getInterval());
                }
            }
        });
        frame.setVisible(true);
    }
    
    /**
     * Gibt das Model diesen Controllers zurück
     * @return das Model
     */
    public MemoryModel getModel() {
        return this.model;
    }
    
    /**
     * Gibt die Instanz des Frames zurück
     * @return der Frame
     */
    public MemoryFrame getFrame() {
        return this.frame;
    }
    
    /**
     * Lässt das Model seinen Wert erhöhen
     */
    public void increment() {
        this.model.increment();
    }
    
    /** Lässt das Model seinen Wert verringern */
    public void decrement() {
        this.model.decrement();
    }
    
    /**
     * Ändert den Interval des Models
     * @param interval
     */
    public void setInterval(int interval) {
        this.model.setInterval(interval);
    }

}

Code:
public class ChangeIntervalController {
    
    /** Das Model für den Interval-Wechsler */
    private ChangeIntervalModel model;
    
    /** Instanz des Hauptframes / Ermittelt durch den "Vater-Controller" */
    private JFrame parentFrame;
    
    /**
     * Erstellt einen neuen ChangeIntervalController
     */
    public ChangeIntervalController(JFrame parent) {
        this.model = new ChangeIntervalModel();
        parentFrame = parent;
        final ChangeIntervalDialog diag = new ChangeIntervalDialog(parentFrame, true, model);
        
        diag.getButton1().addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                increment();
            }
        });
        
        diag.getButton2().addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                decrement();
            }
        });
        
        diag.getButton3().addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                confirm();
                diag.setVisible(false);
            }
        });
    }
    
    /**
     * Gibt das verwendete Model zurück
     * @return das Model
     */
    public ChangeIntervalModel getModel() {
        return this.model;
    }
    
    /**
     * Erhöht den Wert des geplanten, neuen Intervals
     */
    public void increment() {
        this.model.increment();
    }
    
    public void setInterval(int interval) {
        model.setInterval(interval);
    }
    
    /**
     * Verringert den Wert des geplanten, neuen Intervals
     */
    public void decrement() {
        this.model.decrement();
    }
    
    public void confirm() {
        this.model.confirm();
    }

}

Gefällt mir persönlich eigentlich recht gut so :) Aber da ich noch nicht die riesen erfahrung in Java und entwicklung größerer Anwendungen habe, kann ich mir natürlich auch nen Bock geschossen haben ;)
 

HLX

Top Contributor
Die Definitionen gehen hier in der Tat etwas auseinander. Die von mir aufgeführte Aufteilung ist sehr an die des MVC-Webframeworks Struts angelehnt, muss allerdings nicht unbedingt die Krönung sein.

Es spricht im Prinzip nichts dagegen, Listener in der GUI zu halten, jedoch wird in deinem Falle der GesamtController an den Frame gebunden und der Frame wäre für andere Anwendungen/Controller nicht wiederverwendbar - muss nicht unbedingt ein Problem sein.

Bei der Ausgliederung der Listener kannst du die Zuweisung über Getter an der Komponente, oder über eine separate Methode z.B. addSubmitListener(ActionListener al) oder addIncrementListener(ActionListener al) durchführen.
 

daNny

Aktives Mitglied
Ups... *g*
Ich hätte vll. nicht editieren sollen oben ;)

Vll. gibts ja noch was über den neu eingefügten Code oben zu sagen.

Aber das leuchtet mir schon ein, dass des vll. sinnvoller ist, den Frame nicht daran zu binden. Gefällt mir eigentlich recht gut so...
 

HLX

Top Contributor
Sieht doch gut aus. Ich seh da, ohne mir das jetzt detailliert angesehen zu haben, erstmal keinen Bock.

Du könntest dir jetzt noch Gedanken um die Sichtbarkeit deiner Methoden machen. Die getter müssen ja nicht unbedingt public sein. Paketsichtbarkeit sollte reichen. Sollte der "Hauptcontroller" in einem anderen Package sein, kann es auch nicht schaden im Package des Frames abgeleitete Listener anzulegen. Aber das sind jetzt Individualitäten und haben mit dem Prinzip an sich nichts mehr zu tun. :wink:
 

tfa

Top Contributor
Ich finde es problematisch, dass der Controller sich die GUI-Elemente von der View holt, um die Listener zu setzen.
Was machst du, wenn eine zweite View hinzukommt, die die selben Actions auf z.B. MenuItems abbilden will?
Ich stelle die Beziehung zwischen Komponenten und UI-Model immer in einer Methode initViewController() in der View her, der das Exemplar des Controllers übergeben wird.
 

daNny

Aktives Mitglied
Vielen Dank für die Hilfe auf jeden Fall!

Ich denke, das könnte meine Softwareentwürfe in Zukunft ein wenig eleganter gestalten :)
 

tfa

Top Contributor
HLX hat gesagt.:
tfa hat gesagt.:
.
Was machst du, wenn eine zweite View hinzukommt, die die selben Actions auf z.B. MenuItems abbilden will?
Hmm, mir ist das Problem nicht klar. ???:L

Stell dir vor, du hast 2 Views für den Controller. Würdest du das dann so machen?
Code:
public MemoryController() { 
        this.model = new MemoryModel(); 
        frame = new MemoryFrame(model); 
        andererFrame = new AndererFrame(model);
        frame.getButton1().addActionListener(al1);
        frame.getButton2().addActionListener(al2);
        frame.getButton3().addActionListener(al3);
        andererFrame.getMenuItem1().addActionListener(al1);
        andererFrame.getMenuItem2().addActionListener(al2);
        andererFrame.getMenuItem3().addActionListener(al3);
}
Das hat den Nachteil, dass du für jede neue View den Controller anfassen musst, und zwar
in ziemlich großem Umfang.
Und was, wenn der andere View gar nicht immer benutzt wird? Er wird vielleicht nur von
bestimmten Benutzern in bestimmten Anwendungsfällen benötigt. So würde er aber immer
mit initialisiert und angelegt werden.
Die Controller muss die Views, die er kontrolliert, nicht mal im Detail kennen. Möglicherweise
wird dieser MemoryController von einem übergeordneten Controller mit einer View zusammengestöpselt,
deren Klassennamen er überhaupt nicht kennt.

Und außerdem ist das Beispiel oben inkonsequent, denn das MemoryModel wird der
View übergeben, die Action-Models aber nicht.
 

HLX

Top Contributor
tfa hat gesagt.:
Stell dir vor, du hast 2 Views für den Controller. Würdest du das dann so machen?
Code:
public MemoryController() { 
        this.model = new MemoryModel(); 
        frame = new MemoryFrame(model); 
        andererFrame = new AndererFrame(model);
        frame.getButton1().addActionListener(al1);
        frame.getButton2().addActionListener(al2);
        frame.getButton3().addActionListener(al3);
        andererFrame.getMenuItem1().addActionListener(al1);
        andererFrame.getMenuItem2().addActionListener(al2);
        andererFrame.getMenuItem3().addActionListener(al3);
}
Ich sehe nichts, was dagegen spricht.

tfa hat gesagt.:
Das hat den Nachteil, dass du für jede neue View den Controller anfassen musst, und zwar
in ziemlich großem Umfang.

So soll es sein:
wikipedia hat gesagt.:
Die Steuerung verwaltet die Sicht(en), nimmt von ihnen Benutzeraktionen entgegen, wertet diese aus und agiert entsprechend.
Der "große Umfang" ist mir nicht klar.

tfa hat gesagt.:
Und was, wenn der andere View gar nicht immer benutzt wird? Er wird vielleicht nur von
bestimmten Benutzern in bestimmten Anwendungsfällen benötigt. So würde er aber immer
mit initialisiert und angelegt werden.
Nein. Niemand zwingt dich alles im Konstruktor des Controllers zu initialisieren. Die Initialisierung eines Views kann auch in einem Listener erfolgen.

tfa hat gesagt.:
Die Controller muss die Views, die er kontrolliert, nicht mal im Detail kennen. Möglicherweise
wird dieser MemoryController von einem übergeordneten Controller mit einer View zusammengestöpselt,
deren Klassennamen er überhaupt nicht kennt.
Hast du hierzu ein Beispiel?

tfa hat gesagt.:
Und außerdem ist das Beispiel oben inkonsequent, denn das MemoryModel wird der
View übergeben, die Action-Models aber nicht.
Das MemoryModel brauche ich, aber was will ich mit einem Action-Model?

Wie schon öfters aus Diskussionen hervorgegangen ist, gibt es für MVC keine Musterlösung. IMHO gibt es verschiedene Herangehensweisen an diese Sache. Eine davon habe ich aufgezeigt aber es gibt sicherlich auch andere Wege. MVC zeigt im Prinzip eine Richtung auf, wie man diese nachher umsetzt ist eigentlich eine konzeptionelle Frage und hängt auch von der Anwendung ab.
 

daNny

Aktives Mitglied
Wie würdest du das ganze denn umsetzen?

Du meintest ja, du würdest der View-Komponente eine initViewController Methode spendieren, der eine Instanz des Controllers übergeben wird.
Aber was wäre in dem Fall der unterschied dazu, den Controller auch per Konstruktor zu übergeben? Man würde die View ja wieder an den Controller binden?

Vll. habe ich das auch noch nicht so recht verstanden. Interessieren tuts mich auf jeden fall...
 

tfa

Top Contributor
So soll es sein:
wikipedia hat gesagt.:
Die Steuerung verwaltet die Sicht(en), nimmt von ihnen Benutzeraktionen entgegen, wertet diese aus und agiert entsprechend.
Ich behaupte nichts anderes.

tfa hat gesagt.:
Und was, wenn der andere View gar nicht immer benutzt wird? Er wird vielleicht nur von
bestimmten Benutzern in bestimmten Anwendungsfällen benötigt. So würde er aber immer
mit initialisiert und angelegt werden.
Nein. Niemand zwingt dich alles im Konstruktor des Controllers zu initialisieren. Die Initialisierung eines Views kann auch in einem Listener erfolgen.
Aber es ist doch völlig egal, ob es im Konstruktor oder im Listener gemacht wird. Das Problem bleibt. Es gibt für Button1 und Menu1 nur einen Listener, aber wenn jetzt in einem Anwendungsfall Menu1 niemals verwendet wird, wird es in dieser Lösung trotzdem initialisiert, da der Listener auf jeden Fall erzeugt werden muss.

tfa hat gesagt.:
Die Controller muss die Views, die er kontrolliert, nicht mal im Detail kennen. Möglicherweise
wird dieser MemoryController von einem übergeordneten Controller mit einer View zusammengestöpselt,
deren Klassennamen er überhaupt nicht kennt.
Hast du hierzu ein Beispiel?
Ein Beispiel aus der Praxis, vielleicht nicht ganz so leicht zu erklären:
In meinem Projekt sind die Controller baumartig organisiert. Jeder Controller kann Kind-Controller und einen Parent-Controller haben. Ein Controller ist für die Anzeige der Fachobjekte zuständig, nennen wir ihn MasterController. Dieser MC erzeugt und kontrolliert eine Anzahl weitere "Spezial"Controller, die die Anzeige bestimmter Daten der Fachobjekte bewerkstelligen (jeweils mit eigenen Views). Einer dieser Controller zeigt z.B. detailiert Texte an: Kurztext, Langtext, Bemerkungen, Schlagwörter. Die Textdokumente werden in diesem TexteController definiert und im TextePanel an die JTextAreas bzw. JTextFields gebunden. Ganz einfach.
Ein anderer Spezialkontroller bietet jetzt eine Zusammenfassung aller relevanten Daten der Objekte an. Hier sollen auch Texte angezeigt werden, aber nur eine Zeile Kurztext und zwei Zeilen vom Langtext. Der MasterController stellt nun sicher, dass der ZusammenfassungsController sein TexteController-Exemplar mit benutzt, d.h. dem ZusammenfassungsTextePanel wird zur Initialisierung genau dieses TexteController-Objekt übergeben.
Und das, ohne dass TexteControllerüberhaupt von der Existenz des ZusammenfassungsTextePanel wissen muss. Man musste diese Klasse also nicht anfassen, um die Zusammenfassung zu programmieren. Stichwort geringe Kopplung. Vielleicht gibt es in Zukunft ja noch einen Ort, wo diese Texte angezeigt werden müssen.
Jetzt ist es auch so, dass Views nur dann erzeugt und initialisiert werden, wenn es nötig ist. Viele Anwender brauchen gar keine detailierte Texte-Sicht. Der Kurztext aus der Zusammenfassung reicht denen vollkommen aus. Das heißt, für die gibt es gar kein TextePanel -- wohl aber ein TexteController und ein ZusammenfassungsTextePanel. Mit der hier gezeigten Art, Views und Controller zu initialisieren, würde das nicht klappen.



tfa hat gesagt.:
Und außerdem ist das Beispiel oben inkonsequent, denn das MemoryModel wird der
View übergeben, die Action-Models aber nicht.
Das MemoryModel brauche ich, aber was will ich mit einem Action-Model?
Mit Action-Model meine ich die ActionListener. Das eine Model wird der View im Konstruktor übergeben und dort an die Komponente gekoppelt, die anderen holen sich die Komponenten von der View-Klasse. Ist das nicht inkonsequent?

Wie schon öfters aus Diskussionen hervorgegangen ist, gibt es für MVC keine Musterlösung. IMHO gibt es verschiedene Herangehensweisen an diese Sache. Eine davon habe ich aufgezeigt aber es gibt sicherlich auch andere Wege. MVC zeigt im Prinzip eine Richtung auf, wie man diese nachher umsetzt ist eigentlich eine konzeptionelle Frage und hängt auch von der Anwendung ab.

Das mag sein. Ich kann nur aus meiner Erfahrung sprechen. Und mit der Lösung, die ich oben beschreiben habe, hatte ich bisher am wenigsten Probleme.
 

tfa

Top Contributor
Wie würdest du das ganze denn umsetzen?

Du meintest ja, du würdest der View-Komponente eine initViewController Methode spendieren, der eine Instanz des Controllers übergeben wird.
Aber was wäre in dem Fall der unterschied dazu, den Controller auch per Konstruktor zu übergeben?
Das ist kein großer Unterschied. Kannst du machen wie du willst. Du kannst ihn dem Konstruktor übergeben und dann von dort initViewController() aufrufen.

Man würde die View ja wieder an den Controller binden?
Die View hat nur nur einen Controller, diese Abhängigkeit kann man verschmerzen.
Andersrum wär es schlimmer, wenn ein Controller viele Views hat (siehe oben).
 

HLX

Top Contributor
tfa hat gesagt.:
Ich behaupte nichts anderes.
Du hast dich darüber beschwert, dass du bei neuen Views den Controller anfassen musst. Das ist für mich jedoch eine klare Verwaltungsaufgabe und entspricht der Definition.

tfa hat gesagt.:
Aber es ist doch völlig egal, ob es im Konstruktor oder im Listener gemacht wird. Das Problem bleibt. Es gibt für Button1 und Menu1 nur einen Listener, aber wenn jetzt in einem Anwendungsfall Menu1 niemals verwendet wird, wird es in dieser Lösung trotzdem initialisiert, da der Listener auf jeden Fall erzeugt werden muss.
Du kannst in beiden Fällen den Zeitpunkt festlegen, in dem ein Listener initialisiert wird. Es gibt lediglich einen Unterschied hinsichtlich des Ortes der Listener-Definition, nicht der Zuweisung.


tfa hat gesagt.:
Mit Action-Model meine ich die ActionListener. Das eine Model wird der View im Konstruktor übergeben und dort an die Komponente gekoppelt, die anderen holen sich die Komponenten von der View-Klasse. Ist das nicht inkonsequent?
Nein ist es nicht. Die typische MVC-Grafik zeigt sehrwohl eine direkte unidirektionale Beziehung zwischen View und Model. Der View muss nunmal seine Daten irgendwo herbekommen. Dies kann man aber durchaus über Interfaces weiter entkoppeln. Der Inhalt der Event-Methoden der Listener ist als Bestandteil des Controllers etwas völlig anders und daher nicht damit zu vergleichen.

tfa hat gesagt.:
Das mag sein. Ich kann nur aus meiner Erfahrung sprechen. Und mit der Lösung, die ich oben beschreiben habe, hatte ich bisher am wenigsten Probleme.

Das mag in deinem Anwendungfall so sein, es gibt jedoch genügend Beispiele für andere Lösungswege. Stichwort Strategy-Pattern: Was wäre denn wenn du 5 Frames mit jeweils einem "Speichern"-Button hast und möchtest nun gerne die allgemeine Speicherstrategie ändern. Passt du dann in allen Frames die Listener an? Nehmen wir an, es gibt einen Lizenzierungs-Dialog, der zum einen beim Erststart der Anwendung gezeigt wird, jedoch außerdem auch zur Änderung der Lizenzinformationen zu einem späteren Zeitpunkt verwendet wird. In beiden Fällen ist u.U. das Verhalten des Controllers unterschiedlich (Erstinitialisierung etc.) Implementierst du den Dialog dann zweimal? Durch die Abtrennung des Controllers könntest du ein und denselben Dialog 2x verwenden.
EDIT: noch schlimmer wäre, du brauchst den Dialog in einer 2. Anwendung, kannst ihn aber nicht verwenden, weil du an den Controller gebunden bist.

Auch wenn es nicht sehr viel mit Swing zu tun hat bringe ich mal das Beispiel Apache Struts.
Der Controller besteht hier aus dem zentralen Controller (ActionServlet) der sämtliche Verbindungsinformationen hält, der zentralen Ablaufsteuerung (Requestprocessor) und einer Sammlung von Action-Klassen, die im Wesentlichen den Inhalt von Event-Methoden enthalten. Ein Strategiewechsel oder generell der Austausch von Actions oder die Wiederverwendung für gleichartige Views sind hier ein Kinderspiel. Die Änderungen können ohne einen View anzufassen durchgeführt werden.
 

tfa

Top Contributor
HLX hat gesagt.:
Du kannst in beiden Fällen den Zeitpunkt festlegen, in dem ein Listener initialisiert wird. Es gibt lediglich einen Unterschied hinsichtlich des Ortes der Listener-Definition, nicht der Zuweisung.
Es kommt nicht auf den Zeitpunkt der Initialisierung an, sondern darauf, wer für die Intialisierung zuständig ist.
Leider gehst du nicht auf mein Beispiel ein. Da habe ich versucht die Problematik zu beschreiben. Wahrscheinlich nicht verständlich genug.

tfa hat gesagt.:
Mit Action-Model meine ich die ActionListener. Das eine Model wird der View im Konstruktor übergeben und dort an die Komponente gekoppelt, die anderen holen sich die Komponenten von der View-Klasse. Ist das nicht inkonsequent?
Nein ist es nicht. Die typische MVC-Grafik zeigt sehrwohl eine direkte unidirektionale Beziehung zwischen View und Model. Der View muss nunmal seine Daten irgendwo herbekommen. Dies kann man aber durchaus über Interfaces weiter entkoppeln. Der Inhalt der Event-Methoden der Listener ist als Bestandteil des Controllers etwas völlig anders und daher nicht damit zu vergleichen.
Ich zwischen dieses beiden Entitäten ("MemoryModel", ActionModel aka "ActionListener") wirklich keinen qualitativen Unterschied entdecken (ich bin mir jetzt aber auch nicht mehr sicher, ob wir über das gleiche reden).


Das mag in deinem Anwendungfall so sein, es gibt jedoch genügend Beispiele für andere Lösungswege. Stichwort Strategy-Pattern: Was wäre denn wenn du 5 Frames mit jeweils einem "Speichern"-Button hast und möchtest nun gerne die allgemeine Speicherstrategie ändern. Passt du dann in allen Frames die Listener an?
Soll das heißen, du würdest tatsächlich 5 Listener für die 5 Knöpfe anlegen? Das fände ich furchtbar. Es gibt natürlich nur ein Model (z.B. ActionListener, ich nehme Action bzw. AbstractAction oder auch ButtonModel). Die oder ein Controller managt dieses Model, die Views also die 5 Frames stellen bei ihrer Initialisierung sicher, dass die 5 Buttons daran gebunden werden. Siehe mein Beispiel (wahrscheinlich bin ich kein guter Erklärer).

Nehmen wir an, es gibt einen Lizenzierungs-Dialog, der zum einen beim Erststart der Anwendung gezeigt wird, jedoch außerdem auch zur Änderung der Lizenzinformationen zu einem späteren Zeitpunkt verwendet wird. In beiden Fällen ist u.U. das Verhalten des Controllers unterschiedlich (Erstinitialisierung etc.) Implementierst du den Dialog dann zweimal? Durch die Abtrennung des Controllers könntest du ein und denselben Dialog 2x verwenden.
EDIT: noch schlimmer wäre, du brauchst den Dialog in einer 2. Anwendung, kannst ihn aber nicht verwenden, weil du an den Controller gebunden bist.
In diesem Fall gäbe es ein Interface, das beide Controller implementieren. Ein solchen Fall habe ich allerdings selten.
Sonst sehe ich aber keine Probleme.

Auch wenn es nicht sehr viel mit Swing zu tun hat bringe ich mal das Beispiel Apache Struts.
OK, wenn du aus der Web-Ecke kommst, können wir natürlich stundenlang aneinander vorbei reden.
Da ist sicher einiges anders. Allerdings kenne ich mich da zu wenig aus, um mitreden zu können.
 

daNny

Aktives Mitglied
Auch wenn es jetzt nicht mehr ganz zum Verlauf zur aktuellen Diskussion passt, habe ich dennoch ne kleine Frage:

Ich bin nun gerade dabei meine Anwendung zu gestalten, und habe entsprechend alles nach nem Model, View und Controller aufgeteilt. Meine View besteht nun beispielsweise aus einem Frame, der diverse Tabellen, Listen, Buttons usw... enthält. Die View kennt das Model und hat sich bei diesem auch als Observer registriert.

Wenn sich nun in meinem Model bestimmte Dinge ändern, werden die Beobachter ja benachrichtigt.
Das Model beinhaltet nun mehrere Dinge, wie z.B. mehrere Listen, die den Inhalt der Tabellen des Views wiederspiegeln, oder Arrays, die für ComboBoxen interessant sind. Wenn sich nun aus irgendeinem Grund z.B. die Liste für den Inhalt der ersten Tabelle (des Views) ändert, werden die Beobachter alle benachrichtigt. In meinem Fall ists dies ja nun der gesamte Frame.
Ist das ganze denn so überhaupt praktikabel? Immerhin hat der Frame ja nur eine update-Methode, und diese würde ja sämlichte Komponenten meiner View updaten.

Ich habe das ganze nun so gelöst, dass ich in dem Model bestimmte Flags setze, die ich dann per Getter-Methode abfrage, wie z.B. isListUpdated.
Das kann ich dann ja in der update-Methode erfragen, und so nur die Komponenten updaten, die sich auch wirklich geändert haben. Aber ich denke, das kann auf die Dauer ziemlich unübersichtlich werden.
Wie könnte ich das denn vll. eleganter lösen? Sollte ich z.B. diverse Panels in eine eigene Klasse verstauen, und sich diese dann als Beobachter beim Model registrieren? Diese View wüsste zumindest genau, was sie mit den Änderungen anzufangen hat.
 

HLX

Top Contributor
tfa hat gesagt.:
Es kommt nicht auf den Zeitpunkt der Initialisierung an, sondern darauf, wer für die Intialisierung zuständig ist.
Noch besser. Ich sehe die Zuständigkeit im Controller. Hier gehen dann wohl unsere Meinungen auseinander.

tfa hat gesagt.:
Leider gehst du nicht auf mein Beispiel ein. Da habe ich versucht die Problematik zu beschreiben. Wahrscheinlich nicht verständlich genug.
Korrekt. Ich wollte mich daran allerdings auch nicht aufhalten. Ich bin sicher, dass du gute Gründe für dein Vorgehen hast und dass dieses ebenfalls im Sinne von MVC korrekt ist. Daher habe ich lediglich Anwendungsfälle ergänzt, in denen ein anderes Vorgehen von Vorteil sein kann.

tfa hat gesagt.:
Ich zwischen dieses beiden Entitäten ("MemoryModel", ActionModel aka "ActionListener") wirklich keinen qualitativen Unterschied entdecken (ich bin mir jetzt aber auch nicht mehr sicher, ob wir über das gleiche reden).
Ich betrachte den Inhalt der Event-Methode eines Listeners als Bestandteil des Controllers. Das Problem liegt m.E. in der unterschiedlichen Definition von Controller. Wie bereits geschrieben, betrachte ich den Controller als Gesamtheit aller anwendungssteuernden Elemente. Dabei sind Event-Methoden der für einen oder mehrere Views zuständige Teil des Controllers.

tfa hat gesagt.:
Soll das heißen, du würdest tatsächlich 5 Listener für die 5 Knöpfe anlegen?
Natürlich nicht. So klang es nach deiner Beschreibung. :wink:

tfa hat gesagt.:
Es gibt natürlich nur ein Model (z.B. ActionListener, ich nehme Action bzw. AbstractAction oder auch ButtonModel). Die oder ein Controller managt dieses Model, die Views also die 5 Frames stellen bei ihrer Initialisierung sicher, dass die 5 Buttons daran gebunden werden. Siehe mein Beispiel (wahrscheinlich bin ich kein guter Erklärer).
Perfekt. :toll: - Einziger klitzekleiner Unterschied zu meiner Ausführung ist: der Controller initialisiert den Frame und stößt anschließend die Listenerzuweisung an.

tfa hat gesagt.:
Nehmen wir an, es gibt einen Lizenzierungs-Dialog, der zum einen beim Erststart der Anwendung gezeigt wird, jedoch außerdem auch zur Änderung der Lizenzinformationen zu einem späteren Zeitpunkt verwendet wird. In beiden Fällen ist u.U. das Verhalten des Controllers unterschiedlich (Erstinitialisierung etc.) Implementierst du den Dialog dann zweimal? Durch die Abtrennung des Controllers könntest du ein und denselben Dialog 2x verwenden.
EDIT: noch schlimmer wäre, du brauchst den Dialog in einer 2. Anwendung, kannst ihn aber nicht verwenden, weil du an den Controller gebunden bist.
In diesem Fall gäbe es ein Interface, das beide Controller implementieren. Ein solchen Fall habe ich allerdings selten.
Sonst sehe ich aber keine Probleme.
Was einmal mehr zeigt, welch unterschiedliche Implementierungsmöglichkeiten es gibt.

tfa hat gesagt.:
OK, wenn du aus der Web-Ecke kommst, können wir natürlich stundenlang aneinander vorbei reden.
Da ist sicher einiges anders. Allerdings kenne ich mich da zu wenig aus, um mitreden zu können.
Ich komme aus gar keiner Ecke. Wie bereits mehrfach erwähnt, möchte ich damit nur zeigen, dass es auch anders geht und das es Gründe dafür geben kann auf anderem Wege zu implementieren. Über MVC kann man Ewigkeiten diskutieren. Es gibt dafür keine Musterlösung. Daher sollte jeder über seine Umsetzung selbst entscheiden.
 

tfa

Top Contributor
Wenn sich nun in meinem Model bestimmte Dinge ändern, werden die Beobachter ja benachrichtigt.
Das Model beinhaltet nun mehrere Dinge, wie z.B. mehrere Listen, die den Inhalt der Tabellen des Views wiederspiegeln, oder Arrays, die für ComboBoxen interessant sind. Wenn sich nun aus irgendeinem Grund z.B. die Liste für den Inhalt der ersten Tabelle (des Views) ändert, werden die Beobachter alle benachrichtigt. In meinem Fall ists dies ja nun der gesamte Frame.
Ist das ganze denn so überhaupt praktikabel? Immerhin hat der Frame ja nur eine update-Methode, und diese würde ja sämlichte Komponenten meiner View updaten.

Wenn sich an deinem Table was ändert, sollte nur der zuständige TableModelListener informiert werden. Wenn du viele Tables hast, brauchst wahrscheinlich auch mehrere Listener. Andere Listener sollten davon aber unbetroffen sein.

@HLX: Dann sind wir uns einig, dass wir uns teilweise nicht einig sind. Auch ein Ergebnis :)
 

HLX

Top Contributor
daNny hat gesagt.:
Ich habe das ganze nun so gelöst, dass ich in dem Model bestimmte Flags setze, die ich dann per Getter-Methode abfrage, wie z.B. isListUpdated.
Das kann ich dann ja in der update-Methode erfragen, und so nur die Komponenten updaten, die sich auch wirklich geändert haben. Aber ich denke, das kann auf die Dauer ziemlich unübersichtlich werden.
Wie könnte ich das denn vll. eleganter lösen? Sollte ich z.B. diverse Panels in eine eigene Klasse verstauen, und sich diese dann als Beobachter beim Model registrieren? Diese View wüsste zumindest genau, was sie mit den Änderungen anzufangen hat.

Hatte deine Frage ganz übersehen.

Da gibt es etliche Möglichkeiten. Du kannst den Inhalt der Update-Methoden zur Verbesserung der Übersicht aufsplitten in kleinere Methoden. Aber auch separate Ableitungen für Komponenten sind eine Alternative. Hängt auch ein bisschen davon ab, wie die GUI nachher aufgebaut ist. Je nach komplexität wirst du möglicherweise eh bestimmte Inhalte ableiten um sie mit Funktionen anzureichern.
 

daNny

Aktives Mitglied
Okay... das Aufsplitten in mehrere Methoden wäre wirklich sinnvoll. Das werde ich mit Sicherheit schonmal abändern.

Nichtsdesto Trotz reagieren ja alle GUI-Komponenten des Frames auf jede Benachrichtigung von meinem Observable, also dem Model.
Ist das nicht ein riesen Performance-Problem für die Anwendung, wenn sich nur der Inhalt des Models ändert, der z.B. nur für eine JList wichtig ist, aber alle Elemente des Views benachrichtigt werden, wenn das Model die Nachricht an die Observer rausjagt?

Also die Methode mit den Flags muss ich mir glaube ich noch einmal überdenken... das wird auf die dauer zu unübersichtlich und das Model nur unnötig zugemüllt.

Zum Thema Auslagerung: Das bedeutet also, wenn ich z.B. Tabellen habe, die viel Inhalt tragen aber nur selten durch das Model geupdatet werden müssen, dann wäre es schon sinnvoll, für diese ein eigenes Model, View und Controller zu erstellen? Oder wäre es vll. klüger, den Controller beizubehalten und diesem einfach nur ein 2. Model (für die Tabelle beispielsweise) zu spendieren.

Denke mal, da gehört auch nen gesundes Maß an Erfahrung zu, was man erst mit der Zeit lernt. Spannend und interessant ist die Thematik auf jeden Fall. Und gelernt hab ich dazu ja auch schon einiges ;)

Edit: Was mir noch gerade einfällt: Man könnte ja auch vll. Interfaces einführen, die bestimmte Update-Aktionen definieren. Man hat bei dem in der API eingebauten Observer/Observable ja die Möglichkeit, ein optionales Datenobjekt zu übermitteln.
Wenn ich jetzt ein bestimmtes Objekt übergebe, das eines bzw. mehrere meiner zuvor definierten UpdateInterfaces implementiert, dann könnte ich in der Update-Methode der Beobachter/Views ja mit instanceof ermitteln, was sich alles updaten soll.
Wäre das vll. noch ein sinnvoller ansatz?
 

HLX

Top Contributor
daNny hat gesagt.:
Nichtsdesto Trotz reagieren ja alle GUI-Komponenten des Frames auf jede Benachrichtigung von meinem Observable, also dem Model.
Ist das nicht ein riesen Performance-Problem für die Anwendung, wenn sich nur der Inhalt des Models ändert, der z.B. nur für eine JList wichtig ist, aber alle Elemente des Views benachrichtigt werden, wenn das Model die Nachricht an die Observer rausjagt?

Also die Methode mit den Flags muss ich mir glaube ich noch einmal überdenken... das wird auf die dauer zu unübersichtlich und das Model nur unnötig zugemüllt.
Das Flag kannst du vergessen. Die Update-Methode wird nur aufgerufen, wenn sich ein Observable geändert hat. Wenn du mehrere Observables je Frame hast kannst du diese per instanceof in der update-Methode abfragen und für jedes eine selbstdefinierte update-Methode aufrufen.

daNny hat gesagt.:
Zum Thema Auslagerung: Das bedeutet also, wenn ich z.B. Tabellen habe, die viel Inhalt tragen aber nur selten durch das Model geupdatet werden müssen, dann wäre es schon sinnvoll, für diese ein eigenes Model, View und Controller zu erstellen? Oder wäre es vll. klüger, den Controller beizubehalten und diesem einfach nur ein 2. Model (für die Tabelle beispielsweise) zu spendieren.
Ich denke das kommt darauf an. Willst du mehrere Tabellen gleichzeitig in einem View, dann wird wohl jeweils ein eigener Controller erforderlich sein. Willst du zwischen Tabellen switchen können, reicht auch ein eigenes Model. Bei Tabellen ist zu beachten, dass das Tablemodel als Swing-Bestandteil eher der View zuzuordnen ist. Das TableModel erhält seine Daten aus dem Model.

daNny hat gesagt.:
Edit: Was mir noch gerade einfällt: Man könnte ja auch vll. Interfaces einführen, die bestimmte Update-Aktionen definieren. Man hat bei dem in der API eingebauten Observer/Observable ja die Möglichkeit, ein optionales Datenobjekt zu übermitteln.
Wenn ich jetzt ein bestimmtes Objekt übergebe, das eines bzw. mehrere meiner zuvor definierten UpdateInterfaces implementiert, dann könnte ich in der Update-Methode der Beobachter/Views ja mit instanceof ermitteln, was sich alles updaten soll.
Wäre das vll. noch ein sinnvoller ansatz?
Die Update-Funktionen sind relativ GUI-spezifisch. Fraglich, ob eine Verallgemeinerung sinnvoll ist. Du kannst jedoch (wie oben beschrieben ) instanceof auf die Observables anwenden (1. Parameter der Update-Methode).
 

daNny

Aktives Mitglied
Ich versuche das ganze jetzt noch einmal etwas praktischer aufzuholen:

Nehmen wir einmal an, ich habe folgendes, simples Model und die passende View-Komponente dazu:

Code:
public class MyModel extends Observable {
    
    /** Liste für den ersten von zwei Datenbeständen */
    private List<String[]> content1;
    
    /** Liste für den zweiten von zwei Datenbeständen */
    private List<String[]> content2;
    
    /**
     * Ändert den Inhalt der ersten Liste
     * @param content neuer Inhalt
     */
    public void setContent1(List<String[]> content) {
        this.content1 = content;
        setChanged();
        notifyObservers();
    }
    
    /**
     * Ändert den Inhalt der zweiten Liste
     * @param content neuer Inhalt
     */
    public void setContent2(List<String[]> content) {
        this.content2 = content;
        setChanged();
        notifyObservers();
    }
    
    /**
     * Gibt die erste Liste mit dem Inhalt zurück
     * @return die erste Liste
     */
    public List<String[]> getContent1() {
        return this.content1;
    }
    
    /**
     * Gibt die zweite Liste mit dem Inhalt zurück
     * @return die zweite Liste
     */
    public List<String[]> getContent2() {
        return this.content2;
    }
}

Code:
public class MyView extends JFrame implements Observer{
    
    /** Das observierte Model */
    private MyModel model;
    
    private JTable table1;
    private JTable table2;
    
    /**
     * Erstellt eine neue View-Komponente
     * @param model das Model
     */
    public MyView(MyModel model) {
        super("MyView");
        this.model = model;
        this.model.addObserver(this);
        
        // Alle Komponenten inkl. der Tabellen erstellen
        
        // Den Inhalt aus this.model.getContent1 in table1 laden
        // Den Inhalt aus this.model.getContent2 in table2 laden
    }

    public void update(Observable o, Object arg) {
        
        // Dafür sorgen, dass die Tabelle geupdatet wird
        
    }
}

Der (hier nicht definierte) Controller ruft nun irgendwie die setContentn-Methode auf, und füllt somit eine der Listen des Models. Das Model sorgt nun dafür, dass seine Beobachter benachrichtig werden, was in diesem Fall meine einzige View-Komponente, MyView wäre.

Wie man im Model sieht, wird aber immer nur eine der beiden Listen geupdatet, und darauf die Beobachter benachrichtigt. Jetzt kann ich folgendes aber nicht umsetzen:

HLX hat gesagt.:
Das Flag kannst du vergessen. Die Update-Methode wird nur aufgerufen, wenn sich ein Observable geändert hat. Wenn du mehrere Observables je Frame hast kannst du diese per instanceof in der update-Methode abfragen und für jedes eine selbstdefinierte update-Methode aufrufen.

Ich habe ja nur das eine Observable, was aber für mehrere Komponenten der View zuständig ist. Aber logischerweise will ich nicht table2 aus der View updaten, wenn der Controller nur setContent1 aufgerufen hat.

Sorry, wenn ich immer wieder nachbohre, aber das ganze interessiert mich halt, wie man das am elegantesten lösen kann. Und genau hier hakt es noch bei mir.

Es ist klar, dass das ganze bei einer kleinen Anwendung eher nebensächlich ist, aber mir geht das ganze eher ums Prinzip.

Das mit dem 2. Controller habe ich dabei noch nicht so recht verstanden: Sollen sich die beiden Controller die eine View-Komponente "teilen", aber unterschiedliche Models besitzen, bei denen sich dann die einzelnen Tabellen registrieren würden?

Ich hoffe, dass ich das noch irgendwann verinnerlichen kann ;)
 

HLX

Top Contributor
Ok. Ich dachte du hättest für jede Liste ein eigenes Model. Wenn du mehrere Listen im Model hast, kannst du diese z.B. mit einer Nummer als Schlüssel in einer HashMap halten. Beim setzen der Werte, nimmst du die Liste aus der HashMap, aktualisierst sie und übergibst beim notifyObservers die Listen-Nummer.

In dem Zusammenhang frage ich mich, wofür du die Model-Referenz im Frame noch brauchst. Die wird eigentlich nie verwendet. Die Zuweisung des Frames als Observer kannst du auch im Controller machen. Im Frame hast du Zugriff aufs Model über das Observable-Argument in der Update-Methode.
 

daNny

Aktives Mitglied
Tja... gute Frage... mein Model wäre halt ne Extra-Zugabe ;)

Evtl. um die View mit Werten aus dem Model beim Initialisieren schon zu füllen, ohne dass das Model vorher seine Beobachter benachrichtigt hat.

Wofür braucht man es denn sonst, ausser, dass man sich beim Model registriert? Habe ich noch was übersehen?

Aber das mit ner Map zum auflisten, welche Komponenten geupdatet werden sollen, das klingt schonmal gut. Da schaue ich mal, wie ich das Umsetze.

Danke auf jedenfall schonmal für alle ausführlichen Erklärungen! ;)
 
Status
Nicht offen für weitere Antworten.
Ähnliche Java Themen
  Titel Forum Antworten Datum
Zrebna Frage zu Test-Driven Development (TDD) Java Basics - Anfänger-Themen 3
I Frage Thymeleaf -> Fehler ignorieren und mit "" ersetzen? Java Basics - Anfänger-Themen 15
I Frage Thymeleaf -> Prefix / Suffix ändern? Java Basics - Anfänger-Themen 11
D Rekursions Probleme / frage Java Basics - Anfänger-Themen 4
T Frage zu Parse Java Basics - Anfänger-Themen 2
H Frage an die Profis Java Basics - Anfänger-Themen 4
J Eine konzeptionelle Frage zu OOP Java Basics - Anfänger-Themen 3
P Frage zu Rekursion und Backtracking Java Basics - Anfänger-Themen 2
H Frage zur Ausgabe Java Basics - Anfänger-Themen 4
H Frage zu arithmetischen Operationen Java Basics - Anfänger-Themen 20
F Kurze Frage zu replace() Java Basics - Anfänger-Themen 19
JavaSchmecktLecker Polymorphie Frage zur Methodenüberschreibung Java Basics - Anfänger-Themen 21
J Frage zu einem "Taschenrechner" code Java Basics - Anfänger-Themen 9
B Erste Schritte Frage zu Instanzierung und Referenzen Java Basics - Anfänger-Themen 8
DoubleM Runtime.getRuntime().exec Frage Java Basics - Anfänger-Themen 2
J Eine theoretische Frage zur Praxis - JPanel oder Canvas Java Basics - Anfänger-Themen 5
O Frage: Formaler Typbezeichner? Java Basics - Anfänger-Themen 3
I BlueJ Queue Frage für Klausur Java Basics - Anfänger-Themen 2
N Verständnis Frage zu Variablen Java Basics - Anfänger-Themen 3
N Spezielle frage zum Comparator Java Basics - Anfänger-Themen 6
L Frage zum Array Java Basics - Anfänger-Themen 1
A Frage zum UML Design Java Basics - Anfänger-Themen 1
I Hilfe bei Klausur Frage Java Basics - Anfänger-Themen 8
izoards Drucken Frage zu FAQ Beitrag Java Basics - Anfänger-Themen 2
J Frage zu meinem Code (OOP) Java Basics - Anfänger-Themen 4
sserio Split() -> Regex Frage. Java Basics - Anfänger-Themen 7
A OCA Study Guide: 2. Frage aus Kapitel 3 Java Basics - Anfänger-Themen 9
sserio Date Library Frage Java Basics - Anfänger-Themen 9
Max246Sch Frage zu Währungsrechner Code Java Basics - Anfänger-Themen 2
sserio Frage zu HashMaps Java Basics - Anfänger-Themen 20
sserio Frage zu Threading - Multithreading Java Basics - Anfänger-Themen 2
sserio Frage zu Lambda Ausdrücken Java Basics - Anfänger-Themen 7
sserio Frage zu BigInteger Java Basics - Anfänger-Themen 1
D Frage bzgl. Enum-Handhabung Java Basics - Anfänger-Themen 16
xxx12 Frage Java Basics - Anfänger-Themen 2
I Generelle Frage zu Mikroservices (Spring Boot?), Docker... Java Basics - Anfänger-Themen 7
R Frage zu Methoden (Rückgabewert u. ohne.) Java Basics - Anfänger-Themen 2
A Frage zur programmierung Java Basics - Anfänger-Themen 12
M Frage zur Methode split der Klasse String Java Basics - Anfänger-Themen 32
R Input/Output Frage zu Java IO Java Basics - Anfänger-Themen 6
M Frage zu printWriter Java Basics - Anfänger-Themen 5
C Frage zu OLSMultipleLinearRegression Java Basics - Anfänger-Themen 31
KogoroMori21 Frage zum Euklidischen Algorithmus Java Basics - Anfänger-Themen 11
S Verständnis-Frage zu einer HÜ? Java Basics - Anfänger-Themen 1
F Frage betreff Programm mit dem man C++-Code in JAVA-Code übersetzen lassen kann Java Basics - Anfänger-Themen 2
L Frage zur Ticket Maschine Java Basics - Anfänger-Themen 1
J Frage zu OOP-Klassendiagramm Java Basics - Anfänger-Themen 8
OSchriever Frage zu Compiler Java Basics - Anfänger-Themen 8
H Frage zu Throw Exception Java Basics - Anfänger-Themen 2
TimoN11 Frage zu Java-Vererbung (Cast) Java Basics - Anfänger-Themen 5
Bademeister007 Hallo Leute ich hab eine Frage zur ArrayList Java Basics - Anfänger-Themen 8
F Frage betreff Programmierbücher zu Lagerverwaltung als Konsolenprogramm Java Basics - Anfänger-Themen 3
dieter000 Kurze Frage kann mir ejmand kurz diesen Code erklären, bzw wie man die zeilen erklärt und so Java Basics - Anfänger-Themen 1
I String.split regex Frage Java Basics - Anfänger-Themen 2
N Best Practice Frage zum MVC-Pattern Java Basics - Anfänger-Themen 2
dieter000 Frage zu einem Beispiel... Java Basics - Anfänger-Themen 5
J Frage zum Loggen Java Basics - Anfänger-Themen 18
J Methoden Frage: Array-Werte in anderer Methode ändern Java Basics - Anfänger-Themen 4
Zrebna Frage zum "Referenzen-konzept" in Java Java Basics - Anfänger-Themen 8
JD_1998 Array-Position aus einer Methode in einer anderen ausgeben (Kurze Frage) Java Basics - Anfänger-Themen 2
marcooooo Frage zu bestimmten Beispiel Java Basics - Anfänger-Themen 31
NeoLexx equals()-Methode Verständnis Frage anhand Code Beispiel Java Basics - Anfänger-Themen 22
N Input/Output Eine Frage über system.out.println. Java Basics - Anfänger-Themen 10
B Erste Schritte Learning Coding (!) Frage an erfahrene Programmierer. Java Basics - Anfänger-Themen 23
M konzeptuelle Frage: In welcher Klasse definiert man am Besten Methoden, die die Kommunikation mit dem User regeln? Java Basics - Anfänger-Themen 8
B Frage zum Code verständnis im Resultat Java Basics - Anfänger-Themen 10
C Exception-Frage Java Basics - Anfänger-Themen 3
J Eine Frage zur Schreibweise == ? : Java Basics - Anfänger-Themen 3
S Frage des Designs Java Basics - Anfänger-Themen 1
JavaTalksToMe Extends/Implements Frage Java Basics - Anfänger-Themen 3
pkm Frage zu Servletfunktion Java Basics - Anfänger-Themen 0
B Frage zur Währungsumrechnung Java Basics - Anfänger-Themen 3
S Allgemeine Frage über Generics und Vererbungen Java Basics - Anfänger-Themen 5
Kirby.exe Frage zur Verwendung von Interfaces Java Basics - Anfänger-Themen 6
D Frage zu Strings einer Exception Java Basics - Anfänger-Themen 4
L Wie frage ich ab, ob in einem Array, Werte doppelt vorkommen? Java Basics - Anfänger-Themen 4
D Frage zur IDE IntelliJ IDEA Java Basics - Anfänger-Themen 6
H Frage zum 2d Array Java Basics - Anfänger-Themen 1
N Frage zum Newton-Fraktal Java Basics - Anfänger-Themen 1
H Frage zu interfaces Java Basics - Anfänger-Themen 1
J Frage dazu Variablen klassenübergreifend zu verändern Java Basics - Anfänger-Themen 22
I Frage zu SkipList Java Basics - Anfänger-Themen 4
G Frage zu JScrollPane Java Basics - Anfänger-Themen 12
Kirby.exe Allgemeine Frage Java Basics - Anfänger-Themen 3
W Frage zu anonymen Klassen Java Basics - Anfänger-Themen 4
J Kleine Frage zu OOP Java Basics - Anfänger-Themen 371
S Frage Klasse und Objekte Java Basics - Anfänger-Themen 2
F Frage zu Iteratoren Java Basics - Anfänger-Themen 2
C Erste Schritte Frage zur ArrayList Java Basics - Anfänger-Themen 15
J Frage zur Vererbung Java Basics - Anfänger-Themen 1
H Frage zur ermittlung eines doppelte Paars aus Sotieralgorithmus Java Basics - Anfänger-Themen 4
H Frage zum Array Java Basics - Anfänger-Themen 17
G Schach -Frage 2- Maussteuerung Java Basics - Anfänger-Themen 7
G Schach in Java - Allgemeine Frage zur Architektur Java Basics - Anfänger-Themen 7
B Fachliche Frage bei Rechnungen Java Basics - Anfänger-Themen 16
B Frage zu: String... strings -> Ungleiche Anzahl an Parameter? Java Basics - Anfänger-Themen 4
B Frage zu Datenbank Design - Rechnungen, Angebote... und deren Positionen Java Basics - Anfänger-Themen 4
H Frage zu Parameter einer Methode Java Basics - Anfänger-Themen 2
H Einfache Frage zur Punktnotation objektname.methode(wert) Java Basics - Anfänger-Themen 2
H Frage zu Parameter einer Methode Java Basics - Anfänger-Themen 3

Ähnliche Java Themen

Neue Themen


Oben