As you wander around the net and look at what MVC means to different implementors, perhaps the most loosely cast definition is that of "Controller". Many moons ago, when the idea came to ObjectWorks, it was given the role of the object that managed interaction between the user and the view. In those days, there was a control loop that it participated in. Other than the Smaltalk implementation, I've never found another implementation where Controller played the same role (to be fair here, I've actually never seen any implementation of the Controller role where I could liken it to another).
At one point, the control loop was phased out in favor of a new asychronous event architecture. In fact, in recollection, this was a pretty impressive feat, how they evolved an existing architecture and kept all of our apps pretty much working.
Sometimes, it's interesting to look at these old implementations and try and read between the lines where they were headed. In that particular case, that's many generations of architects ago. But I have a hypothesis. I wonder if Controller was on its way out. If one looks closely, they will note that the event stuff all passes through the view tree (the arrangement of VisualParts). The APIs are few:
- getEventHandler
- handlerForMouseEvent:
- handleEvent:
All events pass up and down the tree via these three methods, or rather they should. There are some flaws that have crept into the design over the years, but at its heart, this is the basics of what we need. For historical reasons, in almost all cases, this just defers these APIs to a View's Controller. In theory, this is a fun thing to do. It demos well. Because we can show that Controller's are pluggable. We can put a NoController on any widget and have it become "dead" to the user. In reality though, this is rarely used. The View-Controller couplets are almost always married 1:1 and the richer a widget becomes the more chatty these two objects become. It becomes difficult to separate their roles as we bounce back and forth, because user interaction and display can be very "integrated." And it's not that one would get rid of the approach completely. One can still build a View and set things up so the event handling code was routed through a pluggable/changeable delegate, but that by default, Views would become the all in one ViewController couplet. This is actually how many other widget frameworks do things.
So I decided to play with this hypothesis when I was doing the SUnitToo(ls) interactive overlay. The class of interest is SUnitTooProgressOverlay. It is a subclass of VisualPart. Rather than return a Controller from getEventHandler, it just returns self. Same for handlerForMouseEvent: (normally a VisualPart would check to see if the mouse event is in it's bounds, but in this case, I can exploit the fact that I know the view will be "full window", but if you go look at how Controller normally does this, you'll see even more evidence that the View is the right place for this behavior, because the Controller defers everything back to the View).
The final override is interesting. This view has different "states" it can be in. Normally, either the View or the Controller would hold the state, then the other'd constantly refer to the holder to tune its behavior. If they're folded into one object, we don't have to go back and forth. I took a page from some of the StateMachine ideas Vassili had played with. The state is represented as a symbol. It has 4 states: #inactive, #visible, #overHalt, #haltPending. I can use this state and the names of the Event classes coming in to handleEvent: to concoct a message based state machine. The handleEvent: code just looks like this:
handleEvent: anEvent
| selector |
selector := (state , anEvent class name , ':') asSymbol.
^(self respondsTo: selector)
ifTrue: [self perform: selector with: anEvent]
ifFalse: [nil]
This is very fun, because then I can just fill in the state based event transitions as I go. Because of the way the logic flows, I only needed:
- haltPendingRedButtonReleasedEvent: anEvent
- overHaltMouseMovedEvent: anEvent
- overHaltRedButtonPressedEvent: anEvent
- visibleMouseMovedEvent: anEvent
None of these methods is over 5 lines long. And I define the whole behavior including "activating" the halt area when you hover over it. The view code is privy to the same state variable, so it can adjust the display accordingly. Overall, I found this to be very effective. And easier to model the states of the View, both it's visual behavior and it's interaction. So whether they meant to or not, I certainly think taking control into the views has merits in most, if not all cases.