My Stuff
Love
Respect
Admiration
My Amps
Links
Archives


Add this feed to a running copy of BottomFeeder

Linux World 2006 Speaker

Sunday, September 18, 2005

Dolphin's Command Framework

 
When I first started to play with Dolphin's MVP framework for building GUIs, I thought it was interesting. It forced you to keep all of the controller code in the presenter. The view was only responsible for view things, the presenter was the glue, and the model was where all of the magic occured. But, there was one thing that bothered me and that's how you used the command framework. From the examples, you override the #queryCommand: method in your Shell. Well, you ended up with code like this which I absolutely hate (BTW, this is taken from my first MVP application):
queryCommand: aCommandQuery

| command |
command:=aCommandQuery command.
(#(File Agents Help about) includes: command)
ifTrue: [^aCommandQuery beEnabled].
command == #saveAgentTape
ifTrue: [^aCommandQuery isEnabled: self tape name isEmpty not].
command == #recordPressed
ifTrue: [^aCommandQuery isEnabled: self tape canRecord].
command == #stopPressed
ifTrue: [^aCommandQuery isEnabled: self tape canStop].
command == #playPressed
ifTrue: [^aCommandQuery isEnabled: self tape canPlay].
command == #pausePressed
ifTrue: [^aCommandQuery isEnabled: self tape canPause].
command == #generateJavaScript
ifTrue: [^aCommandQuery isEnabled: self tape canConvertToScript].
^super queryCommand: aCommandQuery.

YUCK! If you go searching through the code, you find CommandPolicy, Command, CommandQuery, etc and I thought to myself, "Why did they create so many objects if the code turns out like the above?" The answer is the framework is smart and has a lot of parts to it. So, I browsed the implementers of #queryCommand: and I found an interesting class that implemented it: AbstractMessageSend! Well, the interesting thing is that normally you add commands as symbols to the CommandDescription for things like Buttons and MenuItems. But, you don't have to. You can put any object that understands #forwardTo:! And if you add #queryCommand: to your object to use as your command, then you can allow the command to decide when to enable/disable itself! So, I whipped up a little CommandMessageSend that disables/enables itself by calling a canCommandName method if it exists to know when to disable/enable the command in the menu.

MessageSend subclass: #CommandMessageSend
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
classInstanceVariableNames: ''

CommandMessageSend>>queryCommand: query
| canSelector |
canSelector := ('can' , query command selector capitalized) asSymbol.
^(self receiver respondsTo: canSelector)
ifTrue:
[query
isEnabled: (self receiver perform: canSelector);
receiver: self receiver.
true]
ifFalse: [super queryCommand: query]
CommandMessageSend>>command

^self

Now, instead of adding a symbol, I add this object. I now no longer override the #queryCommand: method in my Shell class! And the code from above no longer exists! No more case like if statements! It turns out that the Dolphin guys came up with really nice Command framework that works its way from the Command object itself and works it's way asking at each level of Presenters until it reaches the Shell. I can understand doing the simplest possible thing from the examples, but it doesn't show the power that they have given you. There's also a undo/redo framework for your code to take advantage of. Just browse references to the Command class. Fire up those browsers and start looking at that code! Dolphin has a lot of cool frameworks inside (look at DeferredValue for more fun).

Comments
  • how about something along the lines of
    [self reciever perform: canSelector]on: MessageNotUnderStood
    do:[:ex | ^ super queryCommand:query]
    Since you are doing a #respondsTo: anyways, this is not any worse off performance wise. Will help eliminate the ifTrue:ifFalse: block ...

    By Anand, at 9:18 AM   

  • I should have used this. The only issue might be what if you get a MessageNotUnderstood that you were not expecting (like in your canDoSomething method). But, you could easily check the receiver to be yourself and the right selector in another method...Hmmm, that sounds like another blog entry. Thanks for the suggestion! I'm putting it in my code right now!

    By Blaine, at 9:40 AM   

  • reminds me of a challenge someone once threw out. What is the largest body of code you can write without using ifTrue:ifFalse: ? He had a particular disdain for the construct - procedural in his opinion.
    Thoughts ? does "extreme" polymorphism + using exceptions as would result with the elimination of ifTrue:ifFalse : and other such a good thing ? under what circumstances ?.
    this guy was really good. another very good seat of the pants metric he had - if i need to scroll to read your method - you have a problem

    By Anand, at 10:06 AM   

  • I like to be pragmatic. I try not to use ifTrue:ifFalse:, but in a lot of cases you have to. It all depends on the case.

    As for scrolling your method and you have a problem, well, I think that's a great goal to achieve. I agree that if your method is long enough to scroll, then you're probably doing too much in it. And that goes for any language.

    By Blaine, at 12:13 PM   

  • Personally, I've always hated the notion of constructing selectors on the fly. You have to explicitly manage the methods when packaging, or they'll get stripped, and if you happen to refactor the need for them out of existence, there's no easy way to tell that the code isn't being used anymore. Anyone else looking at the code won't easily know where they're being used, either.

    By Tom K, at 7:38 PM   

  • Generally, I would agree with constructing selectors on the fly. But, if it's by convention, then I'm all for it. I could have given a block to the CommandMessageSend to ask isEnabled instead. Both have their merits and drawbacks. I like convention because it makes reflection nice.

    By Blaine, at 8:11 PM   




Metalheads Against Racism



This page is powered by Blogger. Isn't yours?