Home > aen's Blog

Model-View-Controller

smee, build 10

Build 10 implements custom events and event listeners for performing map refreshes.

Since its first incarnation, MapPanel has grown to shoulder quite a lot of responsibilities. It renders the map, acts on user input, manages the undo history, and some other miscellaneous data.

The downside to this approach is that all of these different responsibilities are lumped into this single class. Over time, this kind of design can become prohibitive to maintain. There's so much going on that modifications intended for one area of responsibility may inadvertently cause side effects in another area.

When all of these responsibilities live in the same place, it's easy to fall into the habit of introducing unnecessary interdependencies. All the member variables are right there in front of you, after all. "Why not just..." This inevitably leads to trouble for somebody later on who was not fully aware of the dependency. It can become much like a row of dominoes. Knock one down, and the rest will follow.

Good formatting and commenting can help, but these are no substitue for a design that literally enforces the separation of responsibilities. This separation of responsibilities is what the MVC pattern is all about.

What Are Patterns?

Don't let the word "pattern" or the fancy acronym "MVC" intimidate you. You don't need years of study and a diploma to really "get" design patterns.

You may need years of experience to understand the subtleties involved in advanced systems which happen to implement design patterns, sure. But that is due to the advanced nature of those systems, not the advanced nature of design patterns. Patterns are nothing more than general guidelines for solving common problems.

Simple problems have simple answers, and anybody can benefit from design patterns, whatever their level of experience. You can get from point A to point B on foot as surely as you can do so in a souped up Ferarri.

A design pattern is not a specific series of steps you must follow to the letter. It is a merely a tried-and-true strategy that others have developed, having faced many similarly related programming problems in the past.

Patterns relating to all areas of life, not just programming, have been around for ages. Sun Tzu's Art of War is the perfect example. Examine verses 18 through 24 of the first chapter, for example. The advice is strategic, not specific.

22. If your opponent is of choleric temper, seek to
irritate him.  Pretend to be weak, that he may grow arrogant.

The advice is apparent, but the means of carrying out this advice are not provided. The same is true of design patterns.

Because patterns are not specific in nature, you can apply the same pattern over and over again across many projects and never implement in the same way.

Model-View-Controller

The Model-View-Controller pattern, or MVC, is a strategy for breaking up user interfaces into three parts: the management of your data (the model,) the rendering of it (the view,) and influencing it through user input (the controller.)

For example, my Map class is a good example of a model. It concerns itself with managing a map's dimensions and its tiles. You can query its state with methods such as get(), wide(), and tall(). You can modify its state with set().

Instead of merely modelling the state of your data, as I have with Map up until this point, the MVC pattern gives us guidelines for how to communicate between its three areas.

The Model

In MVC, the model is responsible for notifying the view when it changes, so that the view can update itself to reflect those changes.

Up until now, I had been accepting user input in MapPanel, such as a mouse press, and then taking action immediately, by changing a tile on the map at the current location, and then repainting the viewport. All of this took place in the MapPanel class directly.

An undesirable trait of this kind of design is that any action that requires a map refresh needs a separate call to repaint() directly from the code that performs the modifying action. For example, I also have to call repaint() when undoing tile plots.

This becomes more involved when the refresh algorithm becomes more specific, such as refreshing only the tile which was updated. At that point I must also manage passing in the coordinates to be refreshed at each location.

I can minimize the amount of red tape with some effort, but if I later decide that I would like to use a new and improved method of refresh, which may take different parameters or some other unforeseen wrinkle, I will have to go back and change the refresh code at all these "refresh points."

If you know your "problem domain" well enough and have all the angles covered, it's entirely possible to implement in this fashion without ever having to go back and modify all of these refresh points. But for the majority of us who are exploring new territory daily, where the unknown is expected and even welcomed, a more fluid design is desirable.

What would be nice is if I could handle all of these map refreshes without having to perform explicit calls for every action that requires a refresh.

"But how else can you update the viewport without calling the refresh code?"

The answer is, I still do. I just arrange the code in such a way that repainting the map is bound directly to the action which creates the need for the refresh.

In more explicit terms, when modifying tiles on a Map object with set(), I want my Map to be directly responsible for initiating the call to repaint() (or a single tile refresh, or whatever else.) In this way I move the desired effect from many places into one place.

However, it is not very wise to call repaint() on my component directly inside the set() method of my Map class. It would solve the problem of multiple calls from multiple locations, but it also introduces a dependency where one should not be: the Map class becomes dependent on the MapPanel class — I need to refresh it with a call to repaint().

The dilemma, then, is how to tie the desired effect to set() without explicitly performing the action that will produce the desired effect there. One solution to this problem is event listeners.

Event Listeners

An event listener is little more than a go-between for your model and some other class interested in your model's state. The idea is that you alter your model class to generate "events." Any parties interested in those events are to inform the model of their interest. They "subscribe" to what the model has to say.

In a previous entry on Composite Undo, I setup the Plot class to encapsulate undo and redo actions. The events a model generates do the same thing. They encapsulate whatever action it is that has taken place.

For example, I want my Map to generate map refresh events so my MapPanel can subscribe to these events and thus be informed when map refreshes occur. And so, I created the MapRefreshEvent class.

The MapRefreshEvent class encapsulates two key bits of information about what was refreshed on the map: the X and Y coordinate of the tile which was modified. This is all I really need to know, and will allow me to refresh a single tile at a time when plotting.

The View

The MapPanel is my view. That is, it is the component in my interface which allows me to, well, view my map. Views are aptly named, aren't they!

In order to be able to inform MapPanel that the map has been refreshed, I need some way for MapPanel to subscribe to my map's refresh events. This can be accomplished with an "event listener."

An event listener is a class that implements an interface specific to the event it is interested in. That interface would define some method which is called when the event of interest occurs.

For example, when my Map class (my model) has updated a tile with the set() method, I want it to trigger some sort of notification for all subscribers that are interested in map refresh events. I want it to tell them all "Hey, I just finished updating the tile at (32,48)!" They would then be able to act accordingly in their event listener.

The event listener interface defines a method that describes the action which just took place, is about to take place, is taking place, or will soon take place.

In my case, I want a notification be sent after I have modified a tile. The notification will indicate the map was refreshed, and so I define my event listener interface with a mapRefreshed() method.

public interface MapRefreshEventListener {
    void mapRefreshed(MapRefreshEvent e);
}

The MapRefreshEvent object it gets passed will contain the specific information about the refresh event, ie. the X and Y coordinates of the tile which was changed.

My MapPanel class would implement this interface. But how would the mapRefreshed() method get called from inside my Map's set() method?

Firing Off Events

This can be achieved by maintaining a list of map refresh event listeners inside the Map class.

List <MapRefreshEventListener> listener_list;

The MapRefreshEventListener class has the method I want to call from set(), so all I need is a list of objects that implement that interface. This would allow me to call mapRefreshed() on all of them every time I use set() to change a tile!

There may eventually be other methods in Map that I'll want to generate map refresh events from, so I create a method specifically for firing off calls to all the map refresh event listeners.

void fireMapRefreshed(int x, int y) {
    MapRefreshEvent e = new MapRefreshEvent(this, x, y);
    for (MapRefreshEventListener listener : listener_list) {
        listener.mapRefreshed(e);
    }
}

I pass in the X and Y coordinates so that I can create the MapRefreshEvent that gets passed into the mapRefreshed() method of all the map refresh event listeners.

I place a call to fireMapRefreshed() at the end of my set() method. Now I can rest easy in the knowledge that all of the subscribed map refresh event listeners will be informed whenever a map tile is changed! And I do it in a way that isn't dependent on MapPanel. Very nice!

Subscribing & Unsubscribing To Events

All I need now is a way for interested parties to subscribe to map refresh events! I create two methods for this: one to add listeners, and one to remove them.

void addMapRefreshEventListener(MapRefreshEventListener listener) {
    listener_list.add(listener);
}

void removeMapRefreshEventListener(MapRefreshEventListener listener) {
    listener_list.remove(listener);
}

With these methods, my MapPanel class can now subscribe or unsubscribe to map refresh events.

For example, I can have my MapPanel directly implement the MapRefreshEventListener interface. Then I can set up the mapRefreshed() method to refresh a single tile based on the knowledge it will receive via the MapRefreshEvent argument.

void mapRefreshed(MapRefreshEvent e) {
    ...
    int x = e.x()  z - z;
    int y = e.y()  z - z;

    // refresh the tile at this location
    ...
}

Subscribing to map refresh events is just as easy.

MapPanel(Map map) {
    ...
    map.addMapRefreshEventListener(this);
    ...
}

After having subscribed to map refresh events, I'm good to go! I can remove all of the direct calls to repaint() from MapPanel after each call to the map's set() method.

set() is now generating map refresh events every time it is called, and I don't have to worry about calling all of these refreshes manually. They all get routed into my mapRefreshed() method.

Tidy!

The Controller

So what about the controller? What kind of magic spells does it cast?

Pretty low-level ones! When you think about controlling a user interface, you probably think about dragging your mouse around to plot tiles, checking and unchecking checkboxes, and clicking buttons. And that's exactly right.

These are all control mechanisms by which you, the user, can influence the model (eg., plotting tiles) and the view (eg., scrolling the viewport.)

It's fairly popular to just merge the view and the controller concepts into one class, just because it's pretty hard to deny that the two have (and very often need to have) a high level of interdependency.

The scroll bars in the map viewport, for example, could be part of a controller class. However, in order to properly scroll the map which the view is displaying, the controller needs to know both the size of the map (from the model) and the current zoom factor (from the view.)

Fortunately, this interdependence isn't an unhealthy one. There are clear and logical reasons for the interdependence.

The Super Amazing Grand Finale

I hope you've found my coverage of the MVC pattern useful! Poke around in the source to see how I finally decided to arrange everything.

One of the nifty byproducts of all this is I not only get fast tile plots no matter how large the viewport is, but *drumroll*... I also get faster undoing of tile plots! *oooo* *ahhh*

It's not always instantaneous, but the end product is kinda cool: you get to see all the plots you performed en masse vanish in the order in which they were plotted. Kinda like a rewind effect!

Undoing tile plots ultimately involves calls to the map's set() method. And mapRefreshed() will be dutifully called for those just like with any other tile altering operation that involves set()! This allowed me to remove all of the calls to repaint() during undo actions, and rely on mapRefreshed() to handle all the specifics.

It's all pretty dang cool! Hopefully you can see that there are substantial benefits to be had from the MVC pattern. And more specifically, from event listeners.

You'll notice I actually did create a separate controller class, MapPanelController. I also split off the rendering code into a MapRenderer class. I even separated the listener for map refresh events into its own class, MapPanelRefreshManager.

Just because the MVC pattern specifies three areas of responsibility doesn't mean you have to restrict yourself to three classes, one for each responsibility.

Do what makes sense. Do what keeps things clear for you.

And enjoy yourself!