Java MVC - Am I missing something?

Go ahead, I need to apologize for the small long mail, but it has been listening to me for quite some time. I recently read a lot about MVC and how it takes its place in the Swing Java world, and I still can’t understand why it is even remotely useful in any application that is a little more complicated than simple examples of toys that provide educational benefits. But let me start from the very beginning ...

I executed all my GUI programs in C # /. NET 4.0, which was not extensive enough but extensive enough to get a good idea of MVVM - this is the new version of MVC. This is a fairly simple concept: you define your GUI using XAML (XML, for example, a description of comnponents), defining the bindings between, for example, a table and its model, the string values ​​of text fields. These bindings correspond to the properties of the object, which you define completely separately. Thus, you have a complete denouement between the gaze and the rest of the world. In the upper part, all changes in the model β€œalmost” automatically return to the corresponding controls, the event-driven design is much more central, etc. Etc.

Now, back to Java, we need to use the old MVC school. Let me start with a very simple example: I'm trying to create a panel with two combo boxes and one button. Selecting a value in the first combo will drive the values ​​of the second combo box, selecting a value in the second, will call an external service based on the values ​​in both lists, and the button will have reset values ​​in the first combo using an external service. If I did this using the β€œmy” approach, I would do the following:

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 } } 

Obviously, this is not a simple piece of code, albeit a short one. Now, if we did this using MVC, the first step would be to use some kind of controller that would do all the work, for example.

 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: The view should not know anything about the controller, but it is better to respond to updates from the model.

To overcome the above, we could as a model. The model would simply have two lists, one for each list. So we have a model (completely useless) , a view and a controller ...

Problem 2 How do we do this? There are at least 2 separate methods: direct vs observer pattern

Problem 3 Direct wiring - Is it not just rewriting everything in the initial setup into three separate classes? In this approach, View registers the model, and the controller has both a view and a model. It would look the same as:

 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 much more complicated than necessary, IMHO, which allows you to simulate and control each other all the time: view β†’ (do sth) controller β†’ (update yourself) view

Problem 4 The observer pattern is even worse in my opinion, although this allows us to separate the view and the model. The view will be registered as a listener on the model, which will inform about any changes. So, we need a method like:

 public void addListener(ViewListener listener); 

and we need a ViewListener. Now we can have one method with some event parameters, but we cannot serve ALL scripts in one way. For example, how would View know that we are just updating the second combobox and not resetting all the values ​​or not disconnecting anything or not removing an item from the table ??? Therefore, we will need a separate method for each update (to a large extent, copy and paste the methods that we will use in gui in the listener), making the listener huge.

Main problems

As I raised a few questions here, I wanted to briefly summarize this.

Main Probelm 1 Splitting loginc into several objects: if you imagine that you have several panels with a large number of controls, you will have a view, model and views for all of them, which will lead to three times: many classes, as usual, allowing you to do work on a user interface class.

Main Problem 2 No matter what wiring technology you use, you end up adding methods to all objects to provide a connection that would be redundant if you just placed everything in the user interface.

Since "posting everything in the user interface" is NOT a solution, I am trying to get your help and comments on this. Thanks a lot in advance for your ideas.

+4
source share
1 answer

I personally went with the observer pattern. I think you exaggerate the complexity of the approach.

Your model should be "useless" because it simply contains data and fires events to interested listeners. This is all an advantage. You can encapsulate any business logic and requirements in one class and unit test completely separately from any particular type. You can even reuse the same model with different views depending on how you want to display the data.

The controller is responsible for the mutation of the model. The view accepts events from the model, but to make changes based on user input, it passes through the controller. The advantage here again lies in isolation and testability. The controller is completely separate from any GUI components; he does not know a certain look.

Your view represents a specific interface in the data and provides certain operations on it. Obviously, a model and a controller are required to build a view. The View will register its listeners in the Model. Inside these listeners, he will update his own performance. If you have the right environment for testing the user interface, you can scoff at these events and claim that the view was updated successfully without using a real model, which might require some external service, such as a database or web service. When the user interface components in the view receive their own events, they can call the controller - again, with a good testing base, you can argue that the ridiculed controller receives these events without actually causing any real operations, such as network calls.

As for your objections - the number of classes is a red herring. This is an indicator with a much lower priority than the denouement. If you really wanted to deflect the number of classes, put all your logic in a class called Main . Adding communication methods - again, you untie things. This is one of the benefits of OOP.

+6
source

All Articles