Feb 6, 2010
Thoughts on PureMVC Mediators
A common practice amongst PureMVC developers when creating PureMVC Mediator implementations is the "view getter", a getter method that returns the instance of the view object that the Mediator is coupled with. As an example I will use an imaginary media player with a playlist. In a PlaylistMediator class the method would be something like below.
ex. PlaylistMediator
package com.project.view { import org.puremvc.as3.patterns.mediator.Mediator; public class PlaylistMediator extends Mediator { static public const NAME:String = "PlaylistMediator"; public function PlaylistMediator( viewComponent:PlaylistView ) { super( NAME, viewComponent ); } public function get playlistView():PlaylistView { return getViewComponent() as PlaylistView; } } }
As I've worked on PureMVC projects with other developers I've had the opportunity to see different approaches at writing application code in a PureMVC setting. The view getter was a pretty common practice, but as I've seen the way its used, and abused, I started wondering to myself, why? Why is this method necessary? Well obviously, it is necessary to retrieve the view component, without having to cast the type each time its needed. The real question is, why is it public?
The purpose of the Mediator is to listen for events on the view and send notifications to the Controller, and to provide an API to manipulate the view. And to a lesser extent, to listen to notifications that should update the view. However, I am not a fan of this either, and I try to keep notification handling in Mediators to a minimum. But that is beside the point I am trying to make about the view getter. With an API to the view provided by the Mediator I feel like the view getter should actually be protected or private, instead of public.
ex. PlaylistMediator
package com.project.view { import org.puremvc.as3.patterns.mediator.Mediator; public class PlaylistMediator extends Mediator { static public const NAME:String = "PlaylistMediator"; public function PlaylistMediator( viewComponent:PlaylistView ) { super( NAME, viewComponent ); } protected function get playlistView():PlaylistView { return getViewComponent() as PlaylistView; } } }
Using protected or private enforces that you should manipulate the view via the Mediator's API, and also stops promoting code like this:
ex. PlaylistLoadedCommand
package com.project.controller.command { import com.project.view.PlaylistMediator; import org.puremvc.as3.interfaces.INotification; import org.puremvc.as3.patterns.command.SimpleCommand; public class PlaylistLoadedCommand extends SimpleCommand { override public function execute( notification:INotification ):void { var playlist:Array = notification.getBody() as Array; var playlistMediator:PlaylistMediator = facade.retrieveMediator( PlaylistMediator.NAME ) as PlaylistMediator; var playlistItemView:PlaylistItemView; for (var i:Number = 0; i < playlist.length; i++) { playlistItemView = new PlaylistItemView(); playlistItemView.data = playlist[ i ]; playlistItemView.x = 0; playlistItemView.y = playlistItemView.height * i; playlistMediator.playlistView.addChild( playlistItemView ); } } } }
And instead, promotes code like this:
ex. PlaylistLoadedCommand – Registered to a notification sent when a Proxy is done loading playlist data
package com.project.controller.command { import com.project.view.PlaylistMediator; import org.puremvc.as3.interfaces.INotification; import org.puremvc.as3.patterns.command.SimpleCommand; public class PlaylistLoadedCommand extends SimpleCommand { override public function execute( notification:INotification ):void { var playlist:Array = notification.getBody() as Array; var playlistMediator:PlaylistMediator = facade.retrieveMediator( PlaylistMediator.NAME ) as PlaylistMediator; playlistMediator.updatePlaylist( playlist ); } } }
In this example the Command simply calls the updatePlaylist() method on the PlaylistMediator, which encapsulates the logic required to actually update the playlist. The PlaylistMediator can now also offer this functionality to another application system if it is required later in development due to another action. Perhaps when the playlist is done and a refresh call is made, or if the user manually refreshes the playlist.
ex. In PlaylistMediator…
public function updatePlaylist( playlist:Array ):void { var playlistItemView:PlaylistItemView; for (var i:Number = 0; i < playlist.length; i++) { playlistItemView = new PlaylistItemView(); playlistItemView.data = playlist[ i ]; playlistItemView.x = 0; playlistItemView.y = playlistItemView.height * i; playlistView.addChild( playlistItemView ); } }
This was just a small example, where I only showed accessing one mediator. There are instances where a notification might need to update two or three views. Instead of writing 10-20 lines in a Command per Mediator that needs to have a view updated, its much neater and easier to read to simply call a method on each Mediator. This has other benefits other than short code. If you are following some code that you are trying to debug it is easier to understand what happens if a notification is handled directly by a Command, since you will be able to see all affected actors, Mediators, Proxies, in a single centralized location. Listening to the notifications within all the mediators makes it more difficult to find all of the places that a single notification effects, as the example illustrates below. The alternative would be to listen to the notifications in each mediator, and have a command that triggers the proxy method. I think its more maintainable and easier to debug if all notifications go directly to a command. It makes more commands, and its more tedious, but in the end its far more maintainable and easier to debug.
package com.project.controller.command { import com.project.view.PlaylistMediator; import com.project.view.MenuMediator; import com.project.model.proxy.ApplicationDataProxy; import org.puremvc.as3.interfaces.INotification; import org.puremvc.as3.patterns.command.SimpleCommand; public class PlaylistLoadedCommand extends SimpleCommand { override public function execute( notification:INotification ):void { var playlist:Array = notification.getBody() as Array; var playlistMediator:PlaylistMediator = facade.retrieveMediator( PlaylistMediator.NAME ) as PlaylistMediator; playlistMediator.updatePlaylist( playlist ); var menuMediator:MenuMediator = facade.retrieveMediator( MenuMediator.NAME ) as MenuMediator; menuMediator.resetMenu(); var applicationDataProxy:ApplicationDataProxy = facade.retrieveMediator( ApplicationDataProxy.NAME ) as ApplicationDataProxy; applicationDataProxy.getPlaylistInfo( playlist ); } } }

The view getter shouldn't be public. If you look at the best practices on page 25, you can see that it should be protected: http://puremvc.org/component/option,com_wrapper/Itemid,174/
That is correct, I was not saying that is should be public, I was saying that I have found this to be the case in code I've read while working with PMVC. The best practices do show an example with a protected implicit getter, however they do not go into detail on whether it is best practice to use protected, which is why I would suspect some people have taken to using public, resulting in the type of code I was pointing out in the post.
However, there is one thing in my post that does go directly against what is in the Best Practices document, and that is creating methods on the concrete Mediators that expose methods of manipulating the view from outside of the Mediator. The reason that I choose to write my implementations this way is that I can more quickly see what happens as a result of a notification if all the actions are invoked by the registered command. Which is what I was trying to convey in the last example, where I can see that the notification that triggered the PlaylistLoadedCommand invoked methods on two Mediators and one Proxy. This approach lets me debug my code easier, as well as lets me reuse methods of a Mediator or Proxy if they are needed to be called from other Commands, as well as keeps the mediator less coupled to the notifications that trigger certain Mediator actions. I've found this very useful in being able to move a Mediator to another application with very little effort in integrating its functionality, since I just have to register the mediator to a Facade and retrieve it in a command where I need to invoke one of its methods.
In the end, this is one of those things that I think is completely a matter of preference, this just happens to be what I prefer. We usually make these types of preference choices on how it effects the way in which we write and debug code, these approaches have helped me. Hopefully they help someone else, but I don't expect everyone to agree.