SWT und Separierung von Logik, Präsentation sowie Modell (also eine Form von MVC)

erdmaennchen

Mitglied
Startpunkt wären folgende Definitionen (natürlich vereinfacht dargestellt):
Java:
..
public static void main(String... args) {
    Model m = new Model();
    View v = new View();
    Controller c = new Controller(m, v);
}

Meine Interpretation von MVC ist relativ strikt und sieht folgendermaßen aus:
  • Änderungen am Model werden dem Controller bekannt, indem er per Listener das Model überwacht. Wenn diese Änderung auch eine Aktion bei der View verlangt, ruft der Controller entsprechende Methoden der View auf.
  • Wenn etwas in der View geändert wird, wird dies wiederum zum Controller propagiert. Dieser nimmt die tatsächliche Änderung der Daten im Model vor. Die Änderung der View läuft dann indirekt über den ersten Punkt.

Um diese Trennung strikt durchführen zu können und Model und View möglichst wenige Daten mitzugeben, sollte (wie oben in der main-Methode zu sehen) am besten nur der Controller Bescheid über View und Model wissen.

Lange Rede, kurzer Sinn: Das Problem besteht aktuell in den Event-Listenern für Buttons etc. Folgendes funktioniert leider nicht (um genau zu sein, scheint es komplett ignoriert zu werden):
Im Controller-Konstruktor:
Java:
    ...
    Controller(Model m, View v) {
        this.m = m;
        this.v = v;
        v.getButton().addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent se) {
                System.out.println("Test-Message");
            }
        });
    }
Ich würde an dieser Stelle erwarten, dass ein Klick auf den Button auch "Test-Message" in der Konsole erzeugt. Leider funktioniert das so aber nicht.

Mir ist bekannt, dass ich das Problem beheben könnte, indem ich etwas weniger restriktiv arbeite und eine klassischere Variante vom MVC verwende (also beispielsweise das Model an die View übergebe, die die Änderungsmethoden direkt aus dem Model aufruft). Mir ist an der Stelle vor allem wichtig, zu begreifen, warum es nicht funktioniert und wie mein gewünschtes Verhalten funktionieren könnte.

Ich hoffe, dass ich nicht bereits mit den ersten 10 Zeilen alle Leser vergrault habe und dass sich doch der ein oder andere für diese Frage / Problematik interessiert :).

Danke im vorhinein für jegliches Feedback!
 
G

Gast2

Gast
Wie sieht den deine View aus??? Ohne dass man die View kennt man wenig sagen!!!
Ich würde dir aber von dieser Variante abraten, das wird nach einer Weile sehr sehr umständlich!!!
 
M

maki

Gast
Bei MVC ist der Controller meist Teil der View & Umgekehrt, ausnahmen sind WebApps, technisch bedingt.
Wenn du noch weiter strukturieren willst, könnte MVP etwas für dich sein.
 
M

maki

Gast
Ach wirklich? ;)
Die View enthält doch die Controller (ja, es gibt mehrere), der SelectionListener oben ist so einer, wird von der View erzeugt unter normalen Umständen ;)
 
G

Gast2

Gast
Ach wirklich? ;)
Die View enthält doch die Controller (ja, es gibt mehrere), der SelectionListener oben ist so einer, wird von der View erzeugt unter normalen Umständen ;)

ja schon ??? aber darum muss die view doch kein bestandteil des controllers sein auch wenn es mehrere gibt??
EDIT: ah okay ich weiß was du meinst, du meinst den code der in dem SelectionListener passiert...

@TO
Ich hab hier mal ein einfaches SWT Beispiel wie ich ungefähr MVC in SWT Anwendungen einsetzen würde

Java:
package model;

public class Customer {

	private String name;

	public void setName(String name) {
		this.name = name;
	}

	public String getName() {
		return name;
	}
	
	@Override
	public String toString() {
		return name;
	}
}

Model

Java:
package model;


import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.List;


public class CustomerModel  {
    
    public static final String CUSTOMER_REMOVE = "CUSTOMER_REMOVE";
    public static final String CUSTOMER_ADD = "CUSTOMER_ADD";
	
    private List<Customer> costumerList = new ArrayList<Customer>();
	protected transient PropertyChangeSupport listeners = new PropertyChangeSupport(this);
    
    
    
    /**
     * Adds a property-change listener.
     * @param l the listener
     */
    public void addPropertyChangeListener(PropertyChangeListener l){
        this.listeners.addPropertyChangeListener(l);
    }
    
    
    public void removePropertyChangeListener(PropertyChangeListener l){
        this.listeners.removePropertyChangeListener(l);
    }
    
    /**
     * Notificates all listeners to a model-change
     * @param prop the property-id
     * @param old the old-value
     * @param newValue the new value
     */
    protected void firePropertyChange(String prop, Object old, Object newValue){
        if (this.listeners.hasListeners(prop)) {
            this.listeners.firePropertyChange(prop, old, newValue);
        }
    }

    public void addCustomer(Customer o) {
        this.costumerList.add(o);
        // model has changed --> fire
        firePropertyChange(CUSTOMER_ADD, null, o); 
    }
    
    public void remove(Customer o) {
        this.costumerList.remove(o);
//      model has changed --> fire
        firePropertyChange(CUSTOMER_REMOVE, o, null); 
    }
    
    public List<Customer> getKundeList() {
		return new ArrayList<Customer>(costumerList);
	}
}

Controller:
Java:
package controller;

import model.Customer;
import model.CustomerModel;

import org.eclipse.jface.action.Action;

public class CreateCustomerAction extends Action{


	private static CustomerModel model = new CustomerModel();
	private Customer newCustomer;


	@Override
	public void run() {
		//Service Aufrufe DB usw.
		
		
		model.addCustomer(newCustomer);
	}

	public CustomerModel getCustomerModel() {
		return model;
	}


	public void setNewCustomer(Customer newCustomer) {
		this.newCustomer = newCustomer;
	}



	public Customer getNewCustomer() {
		return newCustomer;
	}

}

Die 3 Views:
Java:
package view;

import model.Customer;

import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Text;

import controller.CreateCustomerAction;

public class CreateView {

	private CreateCustomerAction action;
	
	public CreateView(Composite parent) {
		action = new CreateCustomerAction();
		final Group group = new Group(parent, SWT.SHADOW_ETCHED_IN);
		group.setLayout(new org.eclipse.swt.layout.GridLayout());
		group.setText("Kunde anlegen");
		
		final Text text = new Text(group, SWT.BORDER);
		GridDataFactory.fillDefaults().applyTo(text);
		final Button button = new Button(group, SWT.PUSH);
		button.setText("Kunde anlegen");
		button.addSelectionListener(new SelectionListener() {
			
			@Override
			public void widgetSelected(SelectionEvent e) {
				Customer c = new Customer();
				c.setName(text.getText());
				action.setNewCustomer(c);
				action.run();
				text.setText("");
				
			}
			
			@Override
			public void widgetDefaultSelected(SelectionEvent e) {
				
			}
		});	
		GridDataFactory.fillDefaults().applyTo(button);
	}
}

Java:
package view;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import model.CustomerModel;

import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;

import controller.CreateCustomerAction;

public class LastView implements PropertyChangeListener {

	private CreateCustomerAction action;
	private Label label;

	public LastView(Composite parent) {
		final Group group = new Group(parent, SWT.SHADOW_ETCHED_IN);
		group.setLayout(new org.eclipse.swt.layout.GridLayout());
		group.setText("Letzter Kunde");
		// Assuming parent is grid layout
		group.setLayoutData(new GridData(GridData.FILL_BOTH));
		
		action = new CreateCustomerAction();
		action.getCustomerModel().addPropertyChangeListener(this);
		label = new Label(group, SWT.NONE);
		label.setText("Letzter Kunde");
		GridDataFactory.fillDefaults().applyTo(label);
	}

	@Override
	public void propertyChange(PropertyChangeEvent evt) {
		if (evt.getPropertyName().equals(CustomerModel.CUSTOMER_ADD)) {
			label.setText(evt.getNewValue().toString());
		}

	}
}

Java:
package view;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import model.CustomerModel;

import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.List;

import controller.CreateCustomerAction;

public class ListView implements PropertyChangeListener{

	private CreateCustomerAction action;
	private List list;
	public ListView(Composite parent) {
		final Group group = new Group(parent, SWT.SHADOW_ETCHED_IN);
		group.setLayout(new org.eclipse.swt.layout.GridLayout());
		group.setText("Alle Kunde");
		// Assuming parent is grid layout
		group.setLayoutData(new GridData(GridData.FILL_BOTH));
		
		action = new CreateCustomerAction();
		action.getCustomerModel().addPropertyChangeListener(this);
		list = new List(group, SWT.H_SCROLL | SWT.V_SCROLL | SWT.MULTI);
		GridDataFactory.fillDefaults().align(SWT.FILL, SWT.FILL).grab(true, true).applyTo(list);
	}

	

	@Override
	public void propertyChange(PropertyChangeEvent evt) {
		if(evt.getPropertyName().equals(CustomerModel.CUSTOMER_ADD)){
			list.add( evt.getNewValue().toString());
		}
		
	}
}

und die Main

Java:
package view;

import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

public class Main {

	public static void main(String[] args) {
		Display display = new Display();
		Shell shell = new Shell(display);
		GridLayout gridLayout = new GridLayout();
		gridLayout.horizontalSpacing = 0;
		shell.setLayout(gridLayout);
		new CreateView(shell);
		new LastView(shell);
		new ListView(shell);
		shell.pack();
		shell.open();
		while (!shell.isDisposed()) {
			if (!display.readAndDispatch())
				display.sleep();
		}
		display.dispose();
	}
}
 

erdmaennchen

Mitglied
Vielen Dank für eure schnellen Antworten!

Klar, die Frage, wo man was am besten reinpackt ist höchstgradig subjektiv - es gibt keine perfekte Lösung bei solchen Design Patterns. Ich halte mich immer sehr gerne auf der restriktiven Seite auf, aber ich habe nach und nach immer mehr das Gefühl, mir damit selbst ins Bein zu schießen - es braucht teils einfach zu lang, Strukturen zu designen, die zu nahezu 100% gekapselt sind.

Ich werde es jetzt vermutlich ähnlich wie SirWayne machen und doch wieder mit Bean-ähnlichen Strukturen und PrpopertyChangeListener arbeiten. Zusätzlich werden die Views ihren Controller kennen. Die SelectionListener werden in den Views erstellt, rufen aber dann entweder Methoden ihres Controllers auf oder geben ihm eine Art notify. Das ist wohl die Art mit der ich mich am wohlsten fühle.

@ SirWayne: Danke vielmals für das umfangreiche Beispiel, das hilft mir sehr weiter! Ich kann es im Prinzip nur wegen dem JFace-Bezug nicht einsetzen - ich bin erst vor kurzem wieder auf Netbeans umgestiegen (und SEHR zufrieden damit) und leider scheint es mir nicht zu gelingen, die nötigen Libraries zu finden, um JFace verwenden zu können (der Compile geht noch aber das Ausführen wirft beispielsweise "java.lang.ClassNotFoundException: org.eclipse.core.commands.common.EventManager").

Edit: Ich habe mit dem JFace-Part noch nicht aufgegeben und vermute eine Lösung gefunden zu haben - ich schreibe das hier nur, damit sich keiner deswegen unnötige Mühe macht :).

Edit 2: Wie ich gerade schon vermutet hatte, habe ich es jetzt doch zum Laufen gebracht - man sollte sich beim Einbinden von Dependencies nicht auf Artikel verlassen, die sich auf sehr alte Versionen beziehen. Zu meiner Entschuldigung ist akuter Schlafmangel anzumerken ;)
 
Zuletzt bearbeitet:
G

Gast2

Gast
Vielen Dank für eure schnellen Antworten!

Klar, die Frage, wo man was am besten reinpackt ist höchstgradig subjektiv - es gibt keine perfekte Lösung bei solchen Design Patterns.
Das halte ich für ein Gerücht ^^...

ie SelectionListener werden in den Views erstellt, rufen aber dann entweder Methoden ihres Controllers...
Der SelectionListener ist auch ein Controller... Und die Actions darin sind dann auch Controller, darum würde ich es nicht über ein notify machen, denn das macht ja das Model!!!

leider scheint es mir nicht zu gelingen, die nötigen Libraries zu finden, um JFace verwenden zu können
Ein Grund um sich das Eclipse RCP Framework anzuschauen, das ist alles schöne geregelt ;)
 

erdmaennchen

Mitglied
Das halte ich für ein Gerücht ^^...
Du hälst also die oben beschrieben Struktur für DIE richtige Lösung? Allgemeine Aussagen über Struktur sind glaube ich immer ein wenig schwierig.

Der SelectionListener ist auch ein Controller... Und die Actions darin sind dann auch Controller, darum würde ich es nicht über ein notify machen, denn das macht ja das Model!!!
Wenn ich an Models gebundene Actions an Views übergebe und mit den Actions dann Änderungen vornehme, könnte man doch im Prinzip gleich die Models übergeben - in beiden Fällen sieht es für mich so aus, als ob die Views mehr Controller-Aufgabe übernehmen als angenehm ist. Im Prinzip gibt es in der Struktur auch wenige zentrale Punkte.
Änderungen an der UI, die das Model betreffen, werden über die Action mehr oder minder direkt auf das Model übertragen. Das Model informiert seine Listener über die Änderung. Die Views die zuhören merken, das etwas geändert wurde und ändern sich ebenfalls.

Vermutlich bin ich hier aber einfach wieder zu perfektionistisch (das passiert immer dann ganz gerne, wenn ich keine Auslieferungstermine habe und mich in Bereiche einfach selbst einlese) und das ist bereits eine deutlich genuge Trennung von Präsentation und Anwendungslogik...
 
M

maki

Gast
Änderungen an der UI, die das Model betreffen, werden über die Action mehr oder minder direkt auf das Model übertragen. Das Model informiert seine Listener über die Änderung. Die Views die zuhören merken, das etwas geändert wurde und ändern sich ebenfalls.
"Das Model" ist doch nur ein PresentationModel, also mehr oder minder nur für die GUI, denn komplexe Modelle werden sowieso Problem bekommen in der GUI Schicht (und nur dort existiert MVC/MVP), also dort nicht laufen.
Sieh dir mal das DataBinding an, damit kannst du auch Validatoren angeben.

Ps: Anwedungslogik sollte eigentlich nirgendwo in MVC/MVP auftauchen ;)
 
Zuletzt bearbeitet von einem Moderator:
G

Gast2

Gast
Du hälst also die oben beschrieben Struktur für DIE richtige Lösung? Allgemeine Aussagen über Struktur sind glaube ich immer ein wenig schwierig.
MVC ist kein Design Patter!!!

Vermutlich bin ich hier aber einfach wieder zu perfektionistisch (das passiert immer dann ganz gerne, wenn ich keine Auslieferungstermine habe und mich in Bereiche einfach selbst einlese) und das ist bereits eine deutlich genuge Trennung von Präsentation und Anwendungslogik...

Dein Model hält doch nur Daten und macht sonst nicht, eventuell bietet das Model noch eine validate Methode o.ä. an ...Wie maki schon sagt gibt es auch noch DataBinding mit SWT/JFace, was sehr gut ist.
In den Action(Controller) rufst du eigentlich deine Service(meisten sind die aufm Server) Methoden auf und die enthalten deine Logik und DB Zugriffe...
 

Ähnliche Java Themen

Neue Themen


Oben