
I’ve been away for a while in the land of the Ruby Object model and Rails. Upon my return to some Flex development, I decided that, although Robotlegs MVC ‘Controllers’ are really nice, they require too much boilerplate. One thing I found in RoR, is that although they use static controller classes with many command methods, most folk follow convention and avoid introducing wads of business logic and tightly coupling their code.
RL-MVC Commands enforce loose coupling. It’s really hard to end up with complex, tightly coupled controllers when every ‘action’ is a short lived class.
Commands are dynamic. You can change the existing command set on the fly. I’m not sure where this is useful, I don’t take advantage of it, but that’s not to say that other people do not.
With awesome power comes diminished responsibility. Yeah, that decoupling that Commands give you, removes the need for you to pay as much attention to OO and MVC best practices. Instead, you pay the price by having quite a lot of boilerplate:
One command class per ‘action’ (read: method)
I never did mind this tradeoff. Now It’s wearing thin. Our app is big, and there is a lot of boilerplate. After spending some time learning to write Ruby DSL’s it’s really grating to see that a simple and sensible idea reads so badly in code. If I was in Ruby I would have something like:
# controller_map.rb
map RubbishReminderEvent, PutTheRubbishOutCommand
# put_the_rubbish_out_command.rb
class PutTheRubbishOutCommand
def initialize
house.nature_strip << house.rubbish_bin
end
end
Minimal boilerplate, just intention. Actually, with a decent DSL, you could do away with all of the boilerplate.
However if you look at Rails, events (http requests) are routed by “concept”. In the de-facto Rails convention a “concept” is a resource, but it does not need to be. The ‘routes’ tell Rails how to map incoming events to controllers.
In Rails, the controllers are classes, by “concept”, with action methods. This runs the risk of being abused, and it sometimes is. However, people who care do not make a mess of their controllers. So I decided to assume my developers are good people, and make a compromise to remove some boilerplate.
As a reminder, this is the boilerplate I’m talking about
import .... // boilerplate
public class PutTheRubbishOutCommand extends Command { // Boilerplate
// Boilerplate
[Inject]
public var event:TimerEvent;
// Often repeated, not 'DRY'
[Inject]
public var rubbishModel:RubbishModel;
// The only bit of actual 'intent'
override public function execute():void {
rubbishModel.putRubbishBinOnNatureStrip;
}
}
I propose using a Static Controller. Here, “static” is an antonym of “dynamic”, not the keyword
This is more like a Rails Controller which groups ‘action methods’ and reduces boilerplate:
public class RubbishController extends Controller {
[Inject]
public var rubbishModel:RubbishModel;
public function RubbishController() {
addMethod(TimerEvent.RUBBISH_NIGHT, putTheRubbishOut);
addMethod(HomeOwnerEvent.GO_TO_WORK, bringTheRubbishIn);
}
private function putTheRubbishOut(event:TimerEvent):void {
rubbishModel.putRubbishBinOnNatureStrip()
}
private function bringTheRubbishIn(event:HomeOwnerEvent):void {
if (!rubbishModel.rubishBinIsOnNatureStrip())
return;
rubbishModel.putRubbishBinRoundBack();
}
}
I think this looks good. At this point I should apologise to Jesse Warden for admonishing him when he suggested to a conference room at FATC that it was o.k. to do this.
I still think it’s not “o.k.” because if you don’t grasp the value of seperation of concerns, you will make a mockery of this approach to controllers. If you’re starting out with Robotlegs, do not use this approach!
Extra reading: How I aim to implement the static controller
The heart of a Static controller is Controller which looks a bit like this:
public class Controller extends Actor {
public function dispose():void {
eventMap.unmapListeners();
}
protected function addMethod(eventType:String, callback:Function, eventClass:Class = null):void {
eventMap.mapListener(eventDispatcher, eventType, callback, eventClass);
}
}
All it does is give you the ‘addMethod’ event and allows ControllerMap to tear it down via ‘dispose’ as the context dies.
addMethod is a similar to CommandMap#mapCommand, except it doesn’t map a class, it maps a method.
Also, I need to extend Context to provide/manage ControllerMap.
ControllerMap collects and disposes of Controllers. It won’t do much more than:
public function mapController(controllerClass:Class):void {
injector.mapSingleton(controllerClass);
controller:IController = injector.getInstance(controllerClass);
controllers[controllerClass] = controller; // for teardown later
}
I realise that some people may consider the ‘routing’ and the controller to be different responsibilities. As it stands, I cannot figure out a way of keeping routing seperate, because the Controller#actionMethods have to have a ‘thisObject’ of Controller. It would mean a really dopey looking router method like this:
addMethod(TimerEvent.RUBBISH_NIGHT, RubbishController, RubbishController.putTheRubbishOut);
Which would get worse when you need to enforce the event type:
addMethod(TimerEvent.RUBBISH_NIGHT, RubbishController, RubbishController.putTheRubbishOut, TimerEvent);
yick
© 2010 - VisFleet Ltd
No prawns were harmed in
the making of this website
Comments