UI mediation sucks. Mediate behaviours, not views.

7 08 2011

Code for this post can be found on Github.

In this post, we’re going to look at how the variance utility for Robotlegs allows mediation against interfaces rather than concrete classes. Apart from the gains in decoupling, we can mediate purely against behaviours, rather than specific implementation. And, as we’re talking interfaces, a UI component can implement as many interfaces (behaviours) as it likes!

Libraries used in this post:

Why use mediation at all?

Mediation is a design pattern that performs the job of managing communication between parts to decouple logic. In terms of modern MVC frameworks, mediators are typically employed to monitor UI pieces from the outside in, so that the UI has no references to the framework whatsoever. The common alternative is the Presentation Model pattern (PM) that typically involves injecting in one or more presentation models to the UI component. As such, the UI component is thus coupled to the particular PMs it uses. That said, when mediating against classes (rather than interfaces, which we’ll get to), we couple the mediator to the UI, which is suboptimal.

Why Robotlegs?

Robotlegs (RL) is a lightweight (50KB) and prescriptive library for MVCS applications. Out of the box it provides us with Mediation, IOC container and Dependency Injection via the familiar [Inject] metadata (thanks to SwiftSuspenders).

Regular (invariant) mediation

Take some UI component: (SomeComponent.mxml)

<s:VGroup>
    <fx:Script>
        //the mediator will tell me when something happens
        public function asyncReturned():void
        {
            //something happened!
        }

        private function onClick(evt:MouseEvent):void
        {
            //tell whoever's listening to do something
            dispatchEvent(new ControlEvent(ControlEvent.START));
        }
    </fx:Script>

    <s:Button label="Start" click="onClick(event)" />
</s:VGroup>

A mediator for this component might look like (SomeComponentMediator.as):

public class SomeComponentMediator extends Mediator
{
    [Inject]
    public var view:SomeComponent;

    [Inject]
    public var service:ISomeService;

    private var viewHandler:Function;
    private var serviceHandler:Function;

    //called when UI component is added to stage and mediator assigned
    override public function onRegister():void
    {
        //handle control events responding
        viewHandler = function(evt:ControlEvent):void
        {
            serviceHandler = function(evt:ServiceEvent):void
            {
                //some where later on tell the view it is done...
                view.asyncReturned();
            }
            service.addEventListener(ServiceEvent.COMPLETE, serviceHandler);

            service.doSomething();
        }

        //attach the listener
        view.addEventListener(ControlEvent.DO_ASYNC, viewHandler);
    }

    //called when UI component is removed from stage, prior to mediator being destroyed
    override public function onRemove():void
    {
       service.removeEventListener(ServiceEvent.COMPLETE, serviceHandler);
       view.removeEventListener(ControlEvent.DO_ASYNC, viewHandler);
    }
}

Via the ADDED_TO_STAGE event, Robotlegs wires up an instance of a mediator for each UI component it finds. All that it requires is that you map in the mediator:

IMediatorMap.mapMediator(SomeComponent, SomeComponentMediator);

Don’t like extending base classes or want your own implementation of mediation? No problems just implement IMediator instead.

So why covariance?

Because there are some problems here with mediating directly to views:

  • A UI component can only have one mediator;
  • The mediator is tightly coupled to the UI control.

So, what if we wanted to map to an interface instead? We could include the Robotlegs Variance Utility (as a library to our project), and tweak our mediator mapping call to:

IVariantMediatorMap.mapMediator(ISomeComponent, SomeComponentMediator);

The above example becomes:

<s:VGroup implements="ISomeBehaviour">
	<fx:Script>
		//the mediator will tell me when async returns
		public function asyncReturned():void
		{
			//something happened!
		}

		private function onClick(evt:MouseEvent):void
		{
		     //tell whoever's listening to do something
		     dispatchEvent(new ControlEvent(ControlEvent.START));
		}
        </fx:Script>

    <s:Button label="Start" click="onClick(event)" />
</s:VGroup>

Using this interface:

[Event(name="startAsync",type="ControlEvent")]
public interface ISomeBehaviour
{
	function asyncReturned();
}

And the mediator becomes:

public class SomeComponentMediator extends Mediator
{
    [Inject]
    public var view:ISomeBehaviour;

    //... (as before)

}

And voila – we’ve solved both problems in one fell swoop! A UI control can implement as many interfaces as it needs, and our mediates now mediate against a behaviour rather than a concrete UI piece.

So now what?

There’s still room for improvement. Flash has no way to enforce that the class – SomeComponent – will actually dispatch the ControlEvent. If we’re writing these interfaces – these behaviours – we want a contract that explicitly states which events should be fired. Better yet, we’d like the option to state if these events are a functional level or a type level.

Enter Signals, stage right

Signals provide an alternative to events. They are designed to be used within APIs and class libraries, rather than replacing events altogether (Flash events are well suited to UI hierarchies). Where events fire and are handled at a type (class) level, signals live at the variable level. Not only can we pass them to and return them from methods, we can also enforce their presence in types that implement our interfaces. Check out an earlier post on Signals.

By including the lightweight Signals SWC, we have access to the ISignal contract and some common implementations.

Our interface from before now becomes:

public interface ISomeBehaviour
{
    function asyncReturned();

    function get start():ISignal;
}

Our view becomes:

<s:VGroup implements="ISomeBehaviour">
    <fx:Declarations>
        <signals:Signal id="startSignal" />
    </fx:Declarations>

    <fx:Script>
        //the mediator will tell me when something happens
        public function asyncReturned():void
        {
            //something happened!
        }

        //provide access to the type-level signal
        public function get start():ISignal
        {
            return startSignal;
        }
    </fx:Script>

    <!-- Here we actually send the message to the mediator -->
    <s:Button label="Start" click="start.dispatch()" />
</s:VGroup>

We actually now have a property start, exposed from the view that implements the interface, that we can attach and remove handlers to in our mediator.

And so our mediator finally becomes:

public class SomeComponentMediator extends Mediator
{
    [Inject]
    public var view:ISomeComponent;

    [Inject]
    public var service:ISomeService;

    private var serviceSignal:ISignal;

    override public function onRegister():void
    {
        //handle control events responding
        view.start.add(function():void
        {
            serviceSignal = service.doSomething();

            serviceSignal.add(function():void
            {
                //when the service returns, notify the view
                view.asyncReturned();
            });
        });
    }

    override public function onRemove():void
    {
        serviceSignal.removeAll();
        view.start.removeAll(); //clean up any listeners
    }
}

So what now?

Grab the code on Github and have a play around. For ease of use, I’ve included the dependencies along with the source.

About these ads

Actions

Information

8 responses

7 08 2011
Rasheed Abdul-Aziz (@squeedee)

We use covariant ‘behaviours’ in our app. Our extension is simply called ‘Multimediator”. However:

We don’t map to interfaces as it’s an entirely not-utilised approach (in our app).

We don’t use multimediaton by default either, because we work hard to seperate behaviours into components. In fact one-mediator-per-view has helped us to remember to seperate view concerns more.

While I completely agree with “Mediate behaviours” and I love this post, you can still easily skin this cat without **covariance** in most cases.

8 08 2011
patrick (@Patrick_Kulling)

I like the idea and agree with you that behavior mediation is sometimes more useful than mediate a concrete UI implementation, but when the UI implements more than one interface my mediator classes will raise quickly, just to mediate one specific view.
That is in most cases not necessary, depends on encapsulating responsibilities vs class overview.

8 08 2011
Justin J. Moses

@Patrick – agree that it can be overused, but i think the technique deserves a rethink of how you’d structure your views in the first place.

8 08 2011
patrick (@Patrick_Kulling)

That definitely. Thanks for your reply. :)

23 09 2011
Robert Penner (@robpenner)

Nice article; I’ve added it to the list of Community Examples in the AS3 Signals wiki:

https://github.com/robertpenner/as3-signals/wiki/Community-Examples

9 12 2011
compile4fun

I think its very good example of a famous aphorism of David Wheeler: All problems in computer science can be solved by another level of indirection. Kevlin Henney’s corollary to this is, “…except for the problem of too many layers of indirection.”

Nice article, i guess the best solution should exactly fit problem. Responsibilities vs class overview is a good point too.

9 02 2012
Espen Skogen

Hi Justin,
I’d be interested to know how one can use this technique without relying on Robotlegs reflecting over the views to determine if they implement the correct behaviour? Reflection over UIComponents in Flex is a nightmare, as the performance of the app suffers. Is there any way of doing this without explicitely mapping the interface to implementation; whilst not relying on reflection?

9 02 2012
Justin J. Moses

Hi @Espen,

Robotlegs doesn’t reflect over the component structure. It doesn’t need to. When the component is added to stage, it simply intercepts that and says `if (component is type)` then create a mediator (`type` is a variable of type Class – meaning either a concrete class or an interface).

Technically using `is` is reflection, but it is a trivial operation – it’s simply type reflection. It is not the kind of expensive operation traversing the entire object to see what properties and methods it declares.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s




Follow

Get every new post delivered to your Inbox.

%d bloggers like this: