tutorials.de Buch-Aktion 05/2012
Seite 2 von 2 ErsteErste 12
ERLEDIGT
NEIN
ANTWORTEN
18
ZUGRIFFE
6186
EMPFEHLEN
  • An Twitter übertragen
  • An Facebook übertragen
AUF DIESES THEMA
ANTWORTEN
  1. #16
    limago limago ist offline Mitglied Brokat
    Registriert seit
    May 2007
    Ort
    Riedstadt (Hessen)
    Beiträge
    354
    Hi,

    ich muss noch mal darauf rum reiten. Vielleicht solltest Du Dein Design noch mal überdenken. Das mit dem Observerpattern von Laocoon ist sicher ein zielführender Vorschlag. Mein Tipp:

    Du hast ganz unten eine Model-Klasse, die nix, aber auch gar nix von Views oder anderen Klassen weiss. Dafür erbst sie von Observable, bzw implementiert einen eigenen Benachrichtigungsmechanismus. Jeder Aufruf eines Setters in der Modelklasse löst ein Updateereignisaus.

    Du machst eine zweite Klasse, den Controller (Adapter). Diese Klasse registriert sich sowohl beim Model, als auch beim View als Listener. (implementiert Observer, JListModel, oder was auch immer). Bei der Konstruktion der Klasse, gibst Du Referenzen, sowohl auf den View, als auch auf das Model mit und registrierst die Klasse bei beiden als Listener.


    Wenn jetzt im View ein Ereignis feuert, wird der Controller benachrichtigt. Er hat aber eine Referenz auf das Model und kann den (die) geigneten Setter aufrufen. Umgekehrt wird jeder Setteraufruf im Model mit einem Updateereignis feuern. Der Contoller bekommt das mit und zeichnet den View neu. Voila.

    Falls ich heute noch ein wenig Zeit habe, stelle ich eine simple Lösung ein.

    Gruß

    Edit:

    Hier eine Lösung mit JFace

    Zum Laufen lassen müsst ihr folgende Jars aus Eclipse einbinden

    Die beiden org.eclipse.swt jars, org.eclipse.jface und org.eclipse.jface.text, sowie org.eclipse.core.command

    Dann als SWT-Application starten.

    Die Klasse ContentProvider ist der Controller, der die Zeilen aus dem Model liefert. In Swing würde er das ListModel implementieren. Statt des Listviewer könnte man dann genausogut eine JList nehmen. Er registriert sich beim Model als Listener und zeichnet den Viewer neu sobald sich irgendwas im Model ändert. Gleichzeitig registriert er sich beim viewer als doubleClickListener und läßt das doppeltgeklickte Schwein fressen.

    Der Viewer hat keine Ahnung von der Struktur des Models und das Model keine Ahnung von der Struktur (oder gar der Existens) des Views.

    Der Button "neues Schwein" soll nur zeigen, dass das Schreiben ins Model sofort ein Neuzeichnen des Views auslöst.

    Die Tiere

    Code java:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    
    package de.tutorials.tiere;
     
    import java.util.Observable;
     
    public class Schwein extends Observable{
        
        private String name;
        private int gewicht;
        
        public Schwein() {
            this("nobody");
        }
        
        public Schwein(String name) {
            setName(name);
            setGewicht(10);
        }
     
        public int getGewicht() {
            return gewicht;
        }
     
        private void setGewicht(int gewicht) {
            this.gewicht = gewicht;
            fireEvent();
        }
     
        public String getName() {
            return name;
        }
     
        public void setName(String name) {
            this.name = name;
            fireEvent();
        }
        
        public void fressen() {
            setGewicht(getGewicht() + 1);
        }
     
        @Override
        public String toString() {
            
            return "Schwein [Name="+getName()+", Gewicht=" + getGewicht() + "]";
        }
        
        private void fireEvent() {
            setChanged();
            notifyObservers();
        }
        
        
        
     
    }

    Der Container für die Tiere (model)

    Code java:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    
    package de.tutorials.tiere;
     
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Observable;
    import java.util.Observer;
     
    public class SchweineModel extends Observable implements Observer{
     
        private List<Schwein> schweine = new ArrayList<Schwein>();
        
        public void add(Schwein e) {
            e.addObserver(this);
            schweine.add(e);
            fireEvent();
        }
     
        public void clear() {
            for(Schwein schwein: schweine)
                schwein.deleteObserver(this);
            schweine.clear();
            fireEvent();
        }
     
        public void remove(Schwein o) {
            o.deleteObserver(this);
            schweine.remove(o);
            fireEvent();
        }
     
        public Object[] toArray() {
            return schweine.toArray();
        }
     
        public void update(Observable o, Object arg) {
            fireEvent();
            
        }
     
        private void fireEvent() {
            setChanged();
            notifyObservers();
        }
        
     
    }

    Der Controller (Adapter, Zwischenstück, wie auch immer)

    Code java:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    
    package de.tutorials.tiere.provider;
     
    import java.util.Observable;
    import java.util.Observer;
     
    import org.eclipse.jface.viewers.DoubleClickEvent;
    import org.eclipse.jface.viewers.IDoubleClickListener;
    import org.eclipse.jface.viewers.IStructuredContentProvider;
    import org.eclipse.jface.viewers.IStructuredSelection;
    import org.eclipse.jface.viewers.ListViewer;
    import org.eclipse.jface.viewers.Viewer;
     
    import de.tutorials.tiere.Schwein;
    import de.tutorials.tiere.SchweineModel;
     
    public class SchweineContentProvider implements IStructuredContentProvider, Observer, IDoubleClickListener {
     
        private ListViewer viewer;
     
        public Object[] getElements(Object inputElement) {
            SchweineModel model = (SchweineModel) inputElement;
            return model.toArray();
        }
     
        public void dispose() {
            
     
        }
     
        public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
            this.viewer = (ListViewer) viewer;
            this.viewer.removeDoubleClickListener(this);
            this.viewer.addDoubleClickListener(this);
            if (oldInput == null)
                ((SchweineModel) newInput).addObserver(this); 
        }
     
        public void update(Observable o, Object arg) {
            //plumpes Neuzeichnen durch neusetzten der Quelle. (geht auch besser ;-))
            SchweineModel model = (SchweineModel) o;
            viewer.setInput(model);
        }
     
        public void doubleClick(DoubleClickEvent event) {
            IStructuredSelection selection = (IStructuredSelection) event.getSelection();
            Schwein schwein = (Schwein) selection.getFirstElement();
            schwein.fressen();
            
        }
     
    }

    Der Labelprovider liefert nur den String zum Object aus der Zeile, wäre in Swing Teil des ListModels
    Code java:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    package de.tutorials.tiere.provider;
     
    import org.eclipse.jface.viewers.LabelProvider;
    import org.eclipse.swt.graphics.Image;
     
    public class SchweineModelLabelProvider extends LabelProvider {
     
        @Override
        public Image getImage(Object element) {
            
            return null;
        }
     
        @Override
        public String getText(Object element) {
            
            return element.toString();
        }
     
    }

    Und zuletzt der View

    Code java:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    
    package de.tutorials.gui;
     
     
     
    import org.eclipse.jface.viewers.ListViewer;
    import org.eclipse.swt.SWT;
    import org.eclipse.swt.events.SelectionAdapter;
    import org.eclipse.swt.events.SelectionEvent;
    import org.eclipse.swt.layout.FillLayout;
    import org.eclipse.swt.widgets.Button;
    import org.eclipse.swt.widgets.Display;
    import org.eclipse.swt.widgets.Shell;
     
    import de.tutorials.tiere.Schwein;
    import de.tutorials.tiere.SchweineModel;
    import de.tutorials.tiere.provider.SchweineContentProvider;
    import de.tutorials.tiere.provider.SchweineModelLabelProvider;
     
    public class SchweineViewer {
        
        private static final Display display = new Display();
        private Shell shell = null;
        private ListViewer viewer = null;
        private SchweineModel model = null;
        private Button button = null;
        
        
        SchweineViewer() {
            getViewer();
            getButton();
            dispatch();
        }
     
        
        private void dispatch() {
            
            getShell().open();
            while(! getShell().isDisposed()) {
                if(! getDisplay().readAndDispatch()) 
                    getDisplay().sleep();
            }
        }
     
        
     
        private static Display getDisplay() {
            return display;
        }
     
     
        private Shell getShell() {
            if(shell == null){
                shell = new Shell(getDisplay());
                shell.setLayout(new FillLayout());
            }
            return shell;
        }
     
     
        private ListViewer getViewer() {
            if(viewer == null){
                viewer = new ListViewer(getShell(), SWT.SINGLE);
                viewer.setContentProvider(new SchweineContentProvider());
                viewer.setLabelProvider(new SchweineModelLabelProvider());
                viewer.setInput(getModel());
            }
            return viewer;
        }
     
     
        private SchweineModel getModel() {
            if(model == null) {
                model = new SchweineModel();
                model.add(new Schwein("Piggy"));
                model.add(new Schwein("Babe"));
            }
            return model;
        }
        
        
     
     
        private Button getButton() {
            if(button == null) {
                button = new Button(getShell(), SWT.PUSH);
                button.setText("Neues Schwein!");
                button.addSelectionListener(new SelectionAdapter(){
     
                    @Override
                    public void widgetSelected(SelectionEvent e) {
                        getModel().add(new Schwein());
                    }}) ;
            }
            return button;
        }
     
     
        public static void main(String[] args) {
            new SchweineViewer();
     
        }
     
    }
    Geändert von limago (10.05.07 um 10:40 Uhr)
     

  2. #17
    normaler_spinner normaler_spinner ist offline Mitglied Gold
    Registriert seit
    Jun 2005
    Beiträge
    117
    Wenn die View keine Ahnung hat von dem Model, warum existiert dann in der View eine Instanz von Model?
    Das ist ja das was ich jetzt strukturtechnisch nicht ganz auf die Reihe bekomme:
    Ich habe einen Controller, der kennt View und Model. In der View sollen die Klassen aus dem Model gezeichnet werden. Ändert sich das Model, wird eine Ereignis an die View gegeben. Und die muss sich ja jetzt irgendwo her die Klassen holen die im Model existieren. Wenn jetzt die View das Model aber nicht kennt, da strikte Trennung, und den eigenen Controller ja auch nicht, kann das ja irgendwie nicht funktionieren. Also müßte das Model ein Ereignis an den Controller schicken, der wiederum holt sich die Elemente aus dem Model und gibt sie an die View weiter.
    Das scheint mir dann aber doch ein langer langer Weg zu sein, gerade weil mein Model alle 200ms mit Daten aus einer SPS gefüttert wird und die Daten in annähernd Echtzeit auf die View sollen. Um mir den Weg über den Controller zu sparen, kennt das Model die View aber nicht umgekehrt, daher mal die ursprüngliche Fragestellung des Thread.

    Wie bekomme ich jetzt eine saubere Lösung die sowohl dem MVC entspricht aber trotzdem schnell genug ist?
     

  3. #18
    Avatar von Laocoon
    Laocoon Laocoon ist offline Mitglied Gold
    Registriert seit
    Nov 2004
    Ort
    Köln
    Beiträge
    127
    Wenn es sich immer um die gleichen Daten handelt geht das sogar mit dem Observer Pattern.
    Dein Modell ruft ja jedesmal im View die update Methode auf. Der kannst du doch einfach die geänderten Daten als Parameter mitgeben. Damit muss die View dann nicht wieder selbst aktiv werden und sich die Daten vom Modell holen.

    Grüße
    Daniel
     
    Ein Experte ist ein Mann, der hinterher genau sagen kann, warum seine Prognose nicht gestimmt hat.
    Winston Churchill

  4. #19
    limago limago ist offline Mitglied Brokat
    Registriert seit
    May 2007
    Ort
    Riedstadt (Hessen)
    Beiträge
    354
    Zitat Zitat von normaler_spinner Beitrag anzeigen
    Wenn die View keine Ahnung hat von dem Model, warum existiert dann in der View eine Instanz von Model?
    Die View gibt die Referenz nur an den ContentProvider weiter. Man könnte auch eine Lösung basteln, bei der der View nichts von dem Model mitgekommt. Aber bei JFace ist das halt so.

    Zitat Zitat von normaler_spinner Beitrag anzeigen
    Ich habe einen Controller, der kennt View und Model. In der View sollen die Klassen aus dem Model gezeichnet werden. Ändert sich das Model, wird eine Ereignis an die View gegeben. Und die muss sich ja jetzt irgendwo her die Klassen holen die im Model existieren. Wenn jetzt die View das Model aber nicht kennt, da strikte Trennung, und den eigenen Controller ja auch nicht, kann das ja irgendwie nicht funktionieren.
    Der Contoller ist technisch ein Adapter. Er kennt die Schnittstellen des Models und die des Views. Deswegen kann der Listviewer ja alles mögliche anzeigen ohne verändert werden zu müssen. Dem Model ist egal wer darauf schaut. Es wäre auch nicht schlimm, wenn der View Kenntnis vom Model hat. In vielen Fällen läßt sich das kaum vermeiden. Umgekehrt wäre es schlimmer. Ich müßte ja bei jedem neuen View, das Model anpassen, was zu sehr unruhigem und schlecht wiederverwendbaren Code führt. Ein Adapter verbindet die Dinge, ohne selbst Logik zu haben. Wenn Du nach Spanien in den Urlaub fährst und Deinen Föhn mitnimmst, kommst Du ja auch nicht auf die Idee, Werkzeug mitzunehmen um die Dose (bzw Föhn) vor Ort passend zu machen.


    Zitat Zitat von normaler_spinner Beitrag anzeigen
    Also müßte das Model ein Ereignis an den Controller schicken, der wiederum holt sich die Elemente aus dem Model und gibt sie an die View weiter.
    Jein. Ich bevorzuge diese Lösung, weil sie elegant ist. Es gibt aber auch Fälle, da sagt der Controller dem View nur, dass er sich ganz oder teilweise neu zeichen muss, voraufhin der View die Daten direkt vom Model holt. Microsoft packt den Controller oft ins Model und nennt das dann Dokument. Geht auch. Aber nehmen wir folgende Situation. Du hast sagen wir das Dateisystem als Model und willst einen File-Explorer bauen. Der besteht aus zwei Views. Einem TreeView und einem ListView auf die gleichen Daten. Ein TreeView hat eine ganz andere Schnittstelle als ein ListView. Der einfachste Weg, das zu Lösen ist zwei Controller zu schreiben. Einer verbindet den Tree mit dem Dateisystem, der andere die Liste. Egal ob Tree oder Liste (sagen wir durch ein Rename, oder Delete) den Datenbestand ändern, wird zunächst ein Setter im Model aufgerufen, der feuert mit einem Ereignis, die Controller lauschen und sagen ihren Views zeichnet euch neu. Jetzt kommt ein TableView hinzu. Die anderen Controller sowie, das Model musst Du nicht mehr anfassen. Du schreibst einen einfachen Controller, und schon wird zusätzlich die Tabelle versorgt. Wenn wir uns das weiter vor Augen führen, wirst Du sehen, das bei Swing dummerweise TableModel und ListModel nicht gleich sind. Hättest Du keine getrennten oder gar keine Controller, müsste die ein und selbe Klasse lauter sehr ähnliche Methoden implementieren um sowohl Liste als auch Tabelle zu versorgen. Der Code wäre nicht nur unschön, er müsste auch bei jeder Änderung der Views wieder geöffnet werden.

    Zitat Zitat von normaler_spinner Beitrag anzeigen
    Das scheint mir dann aber doch ein langer langer Weg zu sein, gerade weil mein Model alle 200ms mit Daten aus einer SPS gefüttert wird und die Daten in annähernd Echtzeit auf die View sollen. Um mir den Weg über den Controller zu sparen, kennt das Model die View aber nicht umgekehrt, daher mal die ursprüngliche Fragestellung des Thread.
    Der Weg ist gar nicht so lang. Es ist nur ein kleiner weiterer UP-Aufruf. Davon bekommst Du in 200 millis eine Menge unter . Was wäre die Alternative. Entweder passt Du das Model extrem an den View an, dann hast Du aber kein MVC-Pattern mehr, sondern eine harte Codierung. Du hast nicht mehr 2 wiederverwendbare Module, sondern nur noch eine enge Kopplung, nur einen monolitischen Block. Die beiden Klassen können nur noch zusammen existieren, also kannst Du gleich eine daraus machen. Und bei jeder Änderung musst Du beide anfassen (brrr gruselig). Oder aber du machts die Anpassung sagen innerhalb des Views und dann hast Du dort auch wieder Code. Du gewinnst also nichts.

    Natürlich sind Pattern nicht immer für optimale Performance bekannt . das ist auch nicht (immer) ihr Ziel. In der Regel muss man sich für optimale Performance oder optimale Wartbarkeit entscheiden. In diesem speziellen Fall, wird die Performance sicher nicht leiden. Um bei dem Beispiel des Föhns zu bleiben; Natürlich kostet ein Adapter Leistung. An der Dose rumzuschrauben spart vielleicht ein paar microwatt. Ich wette aber, Deine Haare werden genauso schnell trocken.
     

Ähnliche Themen

  1. Aufrufende Methode herrausfinden
    Von hoctar im Forum PHP
    Antworten: 2
    Letzter Beitrag: 03.08.10, 15:19
  2. Antworten: 16
    Letzter Beitrag: 11.07.10, 14:09
  3. aufrufende Klasse
    Von Crash123 im Forum Java Grundlagen
    Antworten: 1
    Letzter Beitrag: 18.05.09, 15:45
  4. PHP-Skript aufrufende Instanz
    Von Siebi71 im Forum PHP
    Antworten: 9
    Letzter Beitrag: 19.02.09, 14:57
  5. Ermitteln woher eine Klasse geladen wurde
    Von Thomas Darimont im Forum Java
    Antworten: 0
    Letzter Beitrag: 02.12.05, 20:53