4

Straight away I need to apologise for a bit lengthy post, but this has been bugging me for quite some time now. I have read a lot about MVC recently, and how it has its place within the Swing world of Java, and I still cannot understand why this is even remotely useful in any application being slightly more complex than the simple toy examples the tutorials provide. But let me start from the beginning...

I did all my GUI programming in C#/.Net 4.0, which was not extensive but extensive enough to get a good understanding of MVVM - this is a new version of MVC. It is quite a simple concept: You define you GUI using XAML (XML like description of comnponents), specifying bindings between e.g. table and its model, string values of text fields. These bindings correspond to object properties, which you define completely separately. That way, you have a complete decoupling between view and the rest of the world. On top, all changes within the model are "almost" automatically fired back to corresponding controls, the event driven design is much more central etc. etc.

Now, coming back to Java, we need to use an old school MVC. Let me begin with a very simple example: I am trying to have a panel with two combo boxes and one button. Selecting value in the first combo would drive the values of the second combo box, selecting a value in the second would call an external service, based on values in both combo boxes, and the button would reset values in the first combo, using external service as well. If I were to do it using "my" approach, I would proceed as follows:

public class TestGUI {
    private JComboBox<String> firstCombo;
    private JComboBox<String> secondCombo;
    private JButton button;

    private ExternalReloadService reloadService;
    private ExternalProcessingService processingService;

    public TestGUI(ExternalReloadService reloadService, ExternalProcessingService processingService) {
        initialise();
        this.reloadService = reloadService;
        this.processingService = processingService;
    }

    private void initialise() {
        firstCombo = new JComboBox<>();
        secondCombo = new JComboBox<>();
        button = new JButton("Refresh");

        firstCombo.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                String value = (String) ((JComboBox) e.getSource()).getSelectedItem();
                reloadSecondCombo(value);
            }
        });

        secondCombo.addPropertyChangeListener(new PropertyChangeListener() {
            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                if (evt.getPropertyName().equals("model")) {
                    ComboBoxModel model = (ComboBoxModel) evt.getNewValue();
                    if (model.getSize() == 0) {
                        String value = (String) model.getSelectedItem();
                        processValues((String) firstCombo.getSelectedItem(), value);
                    }
                }
            }
        });

        secondCombo.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                processValues((String) firstCombo.getSelectedItem(), (String) secondCombo.getSelectedItem());
            }
        });

        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                resetValues()
            }


        });
    }

    private void processValues(String selectedItem, String value) {
        processingService.process(selectedItem, value);
        //possibly do sth with result and update ui
    }

    private void reloadSecondCombo(String value) {
        secondCombo.setModel(new CustomModel(reloadService.reload(value)));
    }

    private void resetValues() {
        //Call other external service to pull default data, possibly from DB
    }
}

It's obvious that this is not a simple piece of code, although short. Now, if we were to do it using MVC my first step would be to use some sort of controller, that would do all the work, e.g.

public class TestGUI {
    private JComboBox<String> firstCombo;
    private JComboBox<String> secondCombo;
    private JButton button;

    private Constroller controller;

    public TestGUI(Controller controller) {
        this.controller = controller;
        initialise();
    }

    private void initialise() {
        firstCombo = new JComboBox<>();
        secondCombo = new JComboBox<>();
        button = new JButton("Refresh");

        firstCombo.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                String value = (String) ((JComboBox) e.getSource()).getSelectedItem();
               Data d = controller.getReloadedData(value);
               //assiign to combobox
            }
        });

Problem 1: View should not know anything about the Controller, but should rather respond to the updates from the model.

To overcome the above, we could as the model. Model would simply have two List, one for each combobox. So we have a model (completely uselsess), a view, and controller...

Problem 2 How should we wire this? There are at least 2 separate techniques: direct vs Observer pattern

Problem 3 Direct wiring - isn't that just rewriting everything in the starting setup into three separate classes? In this approach, View registers a model, and Controller has both view and Model. It would look as sth like:

public class TestGUI {
    private JComboBox<String> firstCombo;
    private JComboBox<String> secondCombo;
    private JButton button;

    private Model model;

    public TestGUI(Model m) {
        model = m;
    }

   public void updateSecondValues(){
       model.getSecondValues();
       //do sth
   }
}

public class Controller {

    private TestGUI view;
    private Model model;

    public reloadSecondValues(){
        firstValues = ...//reload using external service
        model.setSecondValues(firstValues);
        view.updateSecondValues();
    }

}

public class Model {

    private Set<String> firstValues;
    private Set<String> secondValues;

    public Set<String> getFirstValues() {
        return firstValues;
    }

    public void setFirstValues(Set<String> firstValues) {
        this.firstValues = firstValues;
    }

    public Set<String> getSecondValues() {
        return secondValues;
    }

    public void setSecondValues(Set<String> secondValues) {
        this.secondValues = secondValues;
    }
}

This is way more complicated than it needs to, IMHO, making model and controller call each other all the time: view -> (do sth) controller -> (update yourself) view

Problem 4 Observer pattern - this is even worse in my opinion, although it allows us to decouple view and model. View would be registered as a listener on the model, which would inform view about any changes. SO now, we need a method like:

public void addListener(ViewListener listener);

and we need a ViewListener. Now, we could potentially have one method with some event params, but we cannot cater for ALL scenarios with one method. For instance, how would View know that we are just updating the second combobox and not resetting all values, or not disabling something, or not removing an item froma table??? We would therefore, need a separate method for each update, (pretty much copy and paste the methods we would have on the gui into the listener) making a listener huge.

Main Problems

As I have raised a few issues here, I wanted to summarise that a bit.

Main Probelm 1 Splitting loginc into several objects: If you imagine you have multiple panels, with many controls, you would have a view, model and views for all of them, resulting in three times as many classes as you would have normally, by allowing to do the work on the UI class.

Main problem 2 No matter what wiring technique you use, you end up adding methods on all objects to allow the communication, which would be redundant, had you simply placed everything in the UI.

As "placing everything in the UI" is NOT a solution, I am trying to get your help and comments on this. Many thanks in advance for your ideas.

Rachel
  • 130,264
  • 66
  • 304
  • 490
Bober02
  • 15,034
  • 31
  • 92
  • 178
  • Not sure if this will help, but I had problems trying to learn MVC coming from a MVVM background too, and found [this answer](http://stackoverflow.com/a/8013385/302677) helped me quite a bit in understanding the differences between the two patterns. – Rachel Dec 05 '12 at 15:30
  • I don't agree with that post - Controller should not generate Models (ViewModels) - it should be exatly the ViewModel – Bober02 Dec 05 '12 at 15:34
  • 1
    I suggest you to check the MVP (Model-View_presenter) pattern for swing applications. [1](http://stackoverflow.com/questions/2105121/what-to-use-mvc-mvp-or-mvvm-or) and [2](http://stackoverflow.com/questions/2977317/mvc-mvp-mvvm-frameworks-for-java-gui-applications) –  Dec 05 '12 at 15:36
  • good comment - although some of this stuff is based around C# more than Java – Bober02 Dec 05 '12 at 15:49
  • The second link provide two examples with Java. JGoodies have also presentations about this pattern. –  Dec 05 '12 at 15:53

1 Answers1

6

I've personally gone with the observer pattern. I think you're overstating the complexity of an approach.

Your model should be "useless" in that it simply contains data and fires events to interested listeners. That's the whole advantage. You can encapsulate any business logic and requirements in one class and unit test it entirely separately of any particular view. You may even be able to reuse the same model with different views depending on how you want to display the data.

The controller is responsible for mutating the model. The view receives events from the model, but to make changes based on user input, it goes through the controller. The advantage here again is decoupling and testability. The controller is entirely separate from any GUI components; it has no knowledge of a particular view.

Your view represents a particular interface into data and provides certain operations on it. It's perfectly appropriate that constructing a View requires a Model and Controller. The View will register its listeners on the Model. Inside those listeners it will update its own representation. If you have a decent UI testing framework you can mock these events and assert that the view has updated successfully without using the real model, which may require some external service like a database or web service. When the UI components in the View receive their own events, they can call the Controller -- again, with a good testing framework you can assert that a mocked Controller receives these events without actually invoking any real operations, such as network calls.

As for your objections -- number of classes is a red herring. That's a much lower priority metric than decoupling. If you really wanted to optomize number of classes, put all your logic in a class named Main. Adding methods for communication -- again, you're decoupling things. That's one of the advantages to OOP.

Thorn G
  • 12,620
  • 2
  • 44
  • 56
  • OK, as far as I agree with decoupling, how would you deal with the fact that you might have 10 different panels, each with its own model and controller? would you have one listener interface per view? and a model to register this one type of listener on each model? – Bober02 Dec 05 '12 at 15:32
  • Yep. Java doesn't have the same strong data binding that C# and XAML provide. You could try hooking into PropertyChangeListener or similar, I suppose. What I would probably do is define the listener interface as an inner class of the model, and the implementation inside the View would be anonymous inner classes. Lambda support in 8 will make things easier there. – Thorn G Dec 05 '12 at 15:38
  • you mean Model would expose a public innter class, and View would implement that as View implements Model.Listener? – Bober02 Dec 05 '12 at 15:46
  • View doesn't necessarily have to implement it. Have you used anonymous inner classes before? You would do something in the constructor of View like: model.registerFooListener(new FooListener() { public void foo(Event e) { //do stuff with the view here, like update a ComboBox } }); – Thorn G Dec 05 '12 at 16:19
  • yeah, but the whole point of the observer patter is never to pass model to the view. It should be the controller that would do the registering in model.register(view) – Bober02 Dec 05 '12 at 18:29