8

The biggest issue I deal with when I develop desktop applications using Swing is the spaghetti that I result in when I complete the application.

I come from a PHP background with MVC where I had an easy way to implement dependency injection and I just had access to anywhere in the model layer when I need it, I just constructed the required classes.

But in Java it is a bit more complicated for me, because Swing components are basically little models that do things, the thing is, how do I deal with it to come with a nice and non-spaghetti design.

What I call spaghetti design?

For me, spaghetti design is where I sometimes get to a point that in my component, I need access to a another component, or I need access to somewhere that I don't have it constructed. For this, I need to pass these instances from component to component and that means I create access for for these object(s) for components that will never use it and are just being used as a man in the middle to pass the instance. Basically a point where I ask myself.

Let's look at this dummy example:

You have the component Map. This component should draw you a map of your country, and draw entities (An entity can be just some city label, a military base, an radar target data and anything that a map can display really). So in your model layer you probably have somewhere defined a class named EntitiyManager which contains a list of entities, each entity is something that inherits MapEntity. So what do you have to do in order to draw these entities?

Approach 1

Pass EntityManager to Map and retrieve data from EntityManager to draw:

public class Map extends JPanel {

    /**
     * Entity manager used to store and manipulate entities
     */
    private EntityManager manager;
    
    public Map(EntityManager manager) {
        this.manager = manager;
    }
    
    // SOME CODE HERE ALOT OF CODE
    
    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        
        Graphics2D g2d = (Graphics2D) g;
        
        for (Entity e : manager.getEntities()) {
            if (!e.isVisible()) {
                continue;
            }
            
            if (e instanceof CityLabel) {
                // TO DRAW FOR CITY LABEL
            } else if (e instanceof MilitaryBase) {
                // TO DRAW FOR MILITARY BASE
            }
        }
    }
}

Approach 2

Pass EntityManager to Map and each Entity will have its own drawing method:

public class Map extends JPanel {

    /**
     * Entity manager used to store and manipulate entities
     */
    private EntityManager manager;

    public Map(EntityManager manager) {
        this.manager = manager;
    }

    // SOME CODE HERE ALOT OF CODE

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);

        Graphics2D g2d = (Graphics2D) g;
        manager.render(g2d);
    }
}

Problem with approach 1: It's long and messy and is not organized at all, makes your paint method be too big and hard to go through. Even though you can split it into methods.

Problems with approach 2: EntityManager is not a view, it should not be drawing anything, it should only be used for operations such as algorithms, data management and such.

But in general, I don't like the fact that I passed my EntityManager like this to the view, it just doesn't look good for me.

Now let's say I have to create a function that makes me able to measure distance on the map. I need a mouseEventListener and that mouse event listener needs access to somewhere that it can pass the input to. I need a place to manage that distance measure at. What if I want to create a little window component that shows how many km I measured and on the other side I want to show on the map the dashed line of my measurement, that means I need to use measure distance in two components, these two are related to each other.

I plan to read about Spring this weekend and see how it can help me.

Another problem that comes to me every time is, I don't really know how I can make my controller pass input to the model correctly, I can't really find a good way to design a controller in Swing because how the components are designed. So for me Input management is also confusing and just makes me do a mess in my program.

So what are good approaches to design a perfect Swing application?

Update

My biggest problem comes when I have a view and I have to update another view when something is changed in the current view.

Let's say we have a Map view. The map view is the map of your country. When you move your mouse, you get an event called for that view mouseMoved or something like that, then you get the X, and Y of the mouse in that Map. Now, what we have to do is

  1. Convert X, Y to map latitude and longitude which is done in the model for that view,
  2. We have another view which is named BottomInformation which is basically a bar at the bottom of the frame, and it shows information. We need to update the BottomInformation view to show these longitude and latitude we got.

This means Map needs access to BottomInformation. How do I do it properly without spaghetti? Are there different approaches than trying MVP and MVC?

Community
  • 1
  • 1
Artemkller545
  • 979
  • 3
  • 21
  • 55
  • You need to think more abstractly, I would recommend reading up on the [MVP](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93presenter) pattern. There should never be an `EntityManager` in anything that extends a Swing `class`. Your Model should deal with handling data, and your View should deal with showing the data. The the Presenter should contain the logic to bind the two together. Views should not _pull_ they should be _pushed_ to. – Boris the Spider Aug 16 '16 at 17:09
  • But how can you push something to the paintComponent method? you need something initialized in the Map class that gets updated by the model with new data? – Artemkller545 Aug 16 '16 at 17:16
  • I mean your View should be an `interface`, and this `interface` should have a `showManager` method. This is all the Presenter knows; it will get the `Manager` from the Model and pass it to this method. Again, the Model is an `interface`. Your DI framework will decide on the implementation of the Model and View by configuration. So, you can have a `SwingView` and a `FXView` - these will implement `showManager` very differently... As above, you need to think more _abstractly_. – Boris the Spider Aug 16 '16 at 17:38
  • @BoristheSpider But the view should not have direct access to model? EntityManager is a model – Artemkller545 Aug 16 '16 at 19:23
  • Never. Never ever. This is a pollution of concerns. Always have in mind how you would re-implement the View if you needed to switch from Swing to JavaFX or even GWT. Always have in mind how you would re-implement the Model to switch from JPA to JDO or to something entirely different like getting data from a REST API. – Boris the Spider Aug 16 '16 at 19:24
  • @BorisTheSpider What is the difference from a model and data mapper pattern? is there a relation? – Artemkller545 Aug 16 '16 at 19:50
  • This more concrete [example](http://stackoverflow.com/a/3072979/230513) follows this [outline](http://stackoverflow.com/questions/2687871); this related [example](http://stackoverflow.com/a/11259671/230513) uses a manager to enforce exclusion amount a column of radio buttons in a table. – trashgod Aug 16 '16 at 22:23
  • Take a look at my article, [Kakurasu Using Java Swing](http://java-articles.info/articles/?p=795), to see how I use MVC with a Swing GUI. In general, the model is ignorant of the view and controller. The view reads from the model. The controller updates the model and the view. – Gilbert Le Blanc Aug 17 '16 at 11:24
  • 1
    I've been using swing for 3 years with no idea what MVC meant. When I swapped to angularJS recently I wondered the _exact_ same thing about swing. This is a very good question. – byxor Aug 19 '16 at 11:10
  • your model should not be the entity manager but the entity itself. Better: a wrapper or similair object like your entity. What I extensively used when writing swing-applications was the observer pattern. nearly all components were tied with models and were redrawn, when a model-property changed. That is how swing works with most models internally, like with the Vector object for tables. – SCI Aug 19 '16 at 13:44
  • @SCI I can''t really agree with this. A model is not a single class or two classes. It's a whole layer that handles logic and data according to this answer http://stackoverflow.com/a/5864000/3123545. Having a list of entities and functions in that class really looks like something that would lay in the model side. correct me if I am wrong – Artemkller545 Aug 19 '16 at 14:27
  • Well, you can have a container for many entities, which will be a model, too. How you want to define your model depends on your usecase. You can define an entire external application as your model and communicate with it. At the end, you will have an interface for accessing you data you want to display. You can have an Interface with getValue(x,y) or you can have an interface with getEntry(x).getY(). Depending on your needs of abstraction the first one will be more abstract and encapsulate the management of your data more. – SCI Aug 19 '16 at 14:42
  • Yeah i agree but i really want to focus on the update i made in my bounty because thats what confuses me – Artemkller545 Aug 19 '16 at 15:54
  • Definitely don't code all the wiring by hand unless you have a very small code base. Look at PropertyChangeSupport, read [this link][1] and let me know if it helps. [1]: http://www.oracle.com/technetwork/articles/javase/index-142890.html – MahdeTo Aug 19 '16 at 16:24

1 Answers1

7

I also encounter those "spaghetti" when I start a complex MVC Swing application. Here is how I manage it. But this is my own way to do, maybe you'll not agree with me ! If anyone disagree with any of my words, just let me know. I'm not a professional, just trying to help. :)


Reminder

First, 'Boris The Spider' already introduced you good MVC/MVP principles. I'll just remember you the basis :

MVC Schema

This means that your controller need a direct access to model, and view objects. The view has access to the model object. The model works alone. It means that if you change your model (changing the API used, changing algorithms,...), the view doesn't care, it just show it up by a few methods.

The controller has to listen for user actions (buttons, fields, etc.) and to change the model data according to this.

The view is only able to show your data from the model. Nothing else.


How to implement

Instancing the objects

First, I create the model and the view objects.

public static void main(String[] args) {
    EntityManager entManager = new EntityManager();
    Map mapView = new Map(entManager);
}

You create your objects, passing through constructor parameters the references needed by each of them. The model is always the first to be created because it is independent.

Then, you create the view, which needs the model to get the data values.

I create the controller inside the view, after creating all the GUI objects. This way, I attach them to their listener in a method attachListeners().

Drawing entities

My approach is that the view needs a method for each Entity type. For example a drawCity() method, then drawRoad() ordrawOilStation(). Because the view is the only object which has to know how to draw them. The Model doesn't have to deal with it.

Because your example need to paint all of the components, the paintComponents() method will be quite a mess, yes. In my opinion, your "approach 1" is the best way to do that.

@Override
public void paintComponent(Graphics g) {
   super.paintComponent(g);

  Graphics2D g2d = (Graphics2D) g;

   for (Entity e : manager.getEntities()) {
       if (!e.isVisible()) {
           continue;
       }

       if (e instanceof CityLabel) {
           drawCity();
       } else if (e instanceof MilitaryBase) {
           drawMilitaryBase();
       }
   }
}

Let's imagine you have a GUI with some JList and JPanel. Your load methods (to read the data to show from the model) will be called separately, each one getting the data they need from the model.

For example :

public void createInterface(){
    //Instanciate some Swing components

    fillPhoneList();
    updateNamePanel();
}

private void fillPhoneList(){
    List<PhoneNumber> phoneList = model.getPhoneList();
    for(PhoneNumber phone : phoneList){
         phoneListView.add(phone);
    }
}

Updating the view

1) Changing views from user actions

Often, the controller reacts when an interface event appeared. The controller's work is to tell the model to change, and, in the same time, to update the view calling the "load" methods.

Let's say you have 2 views, Map and EntityDetails. You have a controller for each of them, called MapListener and EntityDetailsController. They both have, as attribute, the corresponding view object. They catch all user action from there views.

public class MapListener implements ActionListener{
    private Map mapView;

    @Override
    public void actionPerformed(ActionEvent e){
        //Doing some things
    }
}

public class EntityDetailsListener implements ActionListener{
    private EntityDetails entityView;

    @Override
    public void actionPerformed(ActionEvent e){
        //Doing some things
    }
}

Something I'm doing often is to add a reference to the main view inside the secondary view. I mean that Map is the main frame, and it sometimes shows the second view EntityDetails. So I add a reference to Map in the EntityDetailsListener, if necessary of course.

public class EntityDetailsListener implements ActionListener{
    private EntityDetails entityView;
    private Map mapView;
    @Override
    public void actionPerformed(ActionEvent e){
        //Doing some things
        if (e.getSource() == changeNameButton){
            //For exemple, if you change the name of the entity on the EntityDetails view.
            mapView.updateEntity(this); //calling the update method in the Map class.
        }
    }
}

2) Model autochanging

When your model is changing by its own, your view has to be notified. Your controller, lying between them, is the object receiving notifications from the model to say "Hey, I changed, let's update the view please !"

That's why you should code some fireXXXChange() methods in the Model objects. They will tell listeners they have to do some works.

So you need to pass your listener to the model object. Because you create it before anything else, you have to pass it through a method like addMapListener(MapListener listener). Then, you can notify this controller when data changed.

Because it is quite complicated to expose, I just let you this Oracle tutorial on how to manage MVC with Swing.


I really hope it is not confuse and that I helped you a little bit ! The best way for you to solution your spaghetti code is to document and see what is the best for each application you need to code, because each case is different.

Documentation : - javaworld.com : MVC meets Swing - link-intersystems : MVC pattern implemented with Swing - oracle.com : Java application design with MVC

Kapcash
  • 6,377
  • 2
  • 18
  • 40
  • Hey thanks for the answer. But people say that view should not have access to the model directly? Also what if i have to update another view from current view? E.g click on entity on the map will show data of that entity on different view e.g EntityInfo panel – Artemkller545 Aug 17 '16 at 16:22
  • 1
    In my opinion, the view have to access the model, from getters. But the model have to work alone and independently. They don't interact, but they have to exchange information :) For all updates, it comes with the controllers ! Your click goes in the listeners which update / shows the others views. – Kapcash Aug 19 '16 at 12:51
  • Kapsac is completely right here. the view must have some kind of access to the data to represent it. So reading access is legit. Modification may only be done via controller, as there may be some checks and logic for modification. Sometimes enterprise developers use data transfer / view objects, that copy content from the entity and go to view. Also adds complexity. To avoid other devs from using setters of a model in a view, you can define a read-only interface with getters only and give that to swing component, while controller gets a read-write interface of your model. – SCI Aug 19 '16 at 13:53
  • 1
    Thanks SCI. I edited my answers, giving you an exemple on how I manage views updates from user actions (from a view to another). I hope this help :) – Kapcash Aug 19 '16 at 19:53
  • If I don't want to change the view via actions and just because something in my program automatically decides too, I just call some method in the controller that does it, right? – Artemkller545 Aug 20 '16 at 01:40
  • Right, you have to implements some `fireDataChanged()` in the model class. Those will tell the controler something changed =) But I advice you to check out the document I gave you about it, maybe it is clearer ! – Kapcash Aug 21 '16 at 08:33