Swing has it backwards

One of the questions that I have been asked (and have asked myself) is why I’m even doing this project when Swing seems to give you a viable alternative. For example, for a JList, you can grab the ListModel and use it to register for event changes on the JList. At first, this all seems well and good, except for a really important issue: this is backwards.

In a purely theoretical (semantic, maybe?) sense, having your application listen to your view for changes in the model seems, well, silly. It’s half of the problem, of how your controller responds to updates in the GUI. If you think about it, there are really two types of changes that can occur in your program state. The first are changes that originate from the user (in the view) and are just simple updates of the model. For these, the controller needs some way of knowing when the view changes in order to do its thang.

The second type are secondary changes that occur without obvious, direct interaction from the user. For example, in the MediaPipes database, when you add a folder to listen to, the walker daemon immediately looks through it for new media files to add to the database. Now, technically, this change is a result of the user interaction of adding a folder (through a file chooser) to a JList, but it is much more complicated than simply adding a string.

The Swing events are meant to be used for the first, which gives an implication that the designers of Swing felt that the important work to make easier was listening to GUI components. This is all well and good, but these guys missed the point, when placed together with the designers of the collections framework. The really difficult part is making your GUI components listen to your model.

In MediaPipes, there is a GUI component for the database, which is just a table with some predefined columns for common metadata elements (filename, track name, artist, album, length, etc.). Now, this component really doesn’t (and shouldn’t, might I add) have any internal representation of its data. Usually, Swing components have their own internal representations of data, and it is the job of the controller to make sure that this internal representation is always in sync with the model’s representation. So, to make the interaction incredibly simple, this table just listens to an ObservableList of rows for the changes.

That’s right. To hook up the table with the ObservableList takes a line of code to register for changes and a single function, called when there is a change, that just tells the table to update. Also, if you count the code to make the table go to the list for updates instead of some other internal structure, you have about 8 more lines of code (functions such as size() and and internal version of get for getting rows and column names). In total, this new TableModel overrides 5 functions with one-liners that hook everything up. The best part? I never have to worry about keeping the two in sync. The table and the list just work together.

So, the whole point is that the Swing solution is helpful for one direction, which is the controller figuring out what the user is doing. Unfortunately for people writing in Java, this is general the easiest part anyways, and not where we spend most of our time. We end up writing tons of code for modeling relationships between different data structures (e.g. in MediaPipes, when the ObservableList of watched folders changes [which affects 3 different GUI components], the walker daemon then can modify the ObservableList of the database [which affects a different GUI component]). Now, we can’t automate all of this code, since the relationships have to be made explicit. But what we can do is provide a simple hook for where to write this code.

The idea is simple: I don’t want to have to write code to keep everything in sync. I want to be able to ask my data structures to let me know when something changes, and I can place the logic there. I never have to write any plumbing code, and I can concentrate solely on the actual, functional logic.

I can imagine how annoying it would have been to figure out the watched folder list without observable collections. To do it as simple as possible, the controller would respond to the changes in folders (in the JList, or immediately from the file chooser dialog) by telling the walker directly that stuff has changed. The walker would then find new files, and every time it did, it would inform the controller directly that the files had been added. The controller would then simply pass the message on directly to the table component that modeled the data.

See that? You now have two problems. First, you are writing meaningless code, simple plumbing that could (and should) be done once (i.e. eventing). Second, you are creating really strong ties between different layers of your application. Granted, this scenario is better than, say, the walker daemon telling the GUI directly what has happened, since you can change out the controller (in whole or in part) either for testing purposes or to replace the view. However, you are still left with stronger ties than if you use the eventing. The media walker just changes the database. It doesn’t care (and shouldn’t) that the view will eventually be affected. Same with adding the folder to the watch list. The general GUI interaction code shouldn’t care who all is interested in the new folder list. Especially since there are multiple GUI components and functional components that all respond to this event occurring.

Let’s say that I wanted to change out the GUI, at some point. In MediaPipes, if I pull out the GUI entirely, the program will still work (besides the code that creates the GUI, of course). Everything will still run, the media walker daemon will still do its thing, nothing will break. The reason this happens is because nothing is strongly linked to the existing view. If I wanted, say, a web-based view, then I could just replace the GUI with the web-based view, without worrying about rewriting pieces of the controller.

This type of programming could be called a number of things. In a sense, it is data-centric, since everything really listens to the data for changes and, in response, only modifies other pieces of data. In essence, then, instead of thinking of the world as a model-view-controller type of thing, we think of the world as data, with supporting things around it. These things can be visualizations (Swing, web, other GUI, text-based, etc.), tools used for logging, modification tools (e.g. the walker daemon responds to changes in the watched folders by changing the database), and anything else you can think of. You can, if you want, build tools on top of other tools (model-view-controller essentially builds the controller on the model, and the view on top of the controller [or, in Swing, the other way around]), but you end up with stronger coupling.

So, in another sense, this model is non-hierarchical in the best case. Everything is loosely coupled along the same level, and the only strong coupling is between any piece and the data model itself. So the only thing you cannot really replace is the data model.

You’ll notice that this leads to its own slew of problems. It may be less common, but you can imagine, in certain circumstances, wanting to change out the data model for something else. In this case, for example, the list of lists that serves as the “database” is not going to be the final product. We are hoping to use something like SQLite to store everything. The problem is that we now have about 5 things tightly coupled to the ObservableList, and this makes changes annoying.

So how do you get around this? Well, all of the people listening to this ObservableList are getting it from the same source (some object that has references to all of the data structures, basically, although there is a bit of a hierarchy within the data), and so, on some level, we only have one point that we need to change. The easiest way to do this would be to change this from an ObservableList to some type of Observable Database object. The same types of events would be sent along as add/remove/change events, and the added rows could even be packaged as Lists themselves for the sake of convenience, or it could be its own Row class.

In the end, you get the same type of interaction. Instead of listening to an ObservableList, you listen to something, some type of database object that will let you know when things happen. The problem is just that we had to build stuff quickly for demo purposes, and now we have to go back and fix. And this is our own problem, not a problem with this methodology. In short, you still need to create a level of abstraction for each piece of the model (e.g. this is a database and it is a type of ObservableCollection [and not just an ObservableList], which itself hides the details of the real storage mechanism.

If you are keeping track, here are the benefits that I talked about today:

  • Less code – you don’t need to write the plumbing code
  • Less tightly coupled
  • Non-hierarchical – adding/removing components is easy
  • Simpler interactions – the table GUI for the database doesn’t care how the database changes, or how many different people can change the database; it just cares about the database itself. This goes both ways – nobody cares, when they update the database, how many other things need to be changed (GUI or otherwise); they just change the database.
  • More elegant – I can’t really “prove” this, or measure this, but there is something about coding this way that just seems so much easier. I hook up the components manually, but then I just forget about it. Create a visualization for a piece of data and forget about it. Change the data? The visualization changes automatically.

Of course, there are some drawbacks. Just yesterday, for example, I found (and assumed) that using the ObservableList version of retainAll() on very large sets (10,000 element list, retaining all of the even elements in (1) descending and (2) ascending order) is about 20x slower than the regular ArrayList implementation (1.5s : 30s). So you wouldn’t want to use one of these bad boys for any large mathematical operation. It really doesn’t make sense. But you would want to use it for things for which this type of slowdown isn’t a huge problem (e.g removeAll() on a 20 element list, removing a list of 4 things).

  • m.gambrell
    Are you by any chance the musician from ohio who composed triumph, meanma, etc? If so, I would like to speak to you!
blog comments powered by Disqus