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
- Convert
X
,Y
to map latitude and longitude which is done in the model for that view, - 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 theBottomInformation
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?