« Improving Cairngorm’s ModelLocator Part 1 - The problem | Improving Cairngorm’s ModelLocator Part 3 - Hiding the singleton II »

Improving Cairngorm’s ModelLocator Part 2 - Hiding the singleton

15th April 2008

Following on from yesterday’s post about Cairngorm’s ModelLocator, I’ve devised some improvements to the code. This doesn’t alter the technique, but it does remove most of the repetition in the code.

The Model

The model class can be generalised using this technique by Jacob Write to make it dynamic and bindable, so the actual data names and types can be dynamically added by the ModelLocators. The code looks like this.

package model
{
  import flash.events.Event;
  import flash.events.EventDispatcher;
  import flash.events.IEventDispatcher;
  import flash.utils.Proxy;
  import flash.utils.flash_proxy;
  import mx.events.PropertyChangeEvent;
  import mx.events.PropertyChangeEventKind;
  use namespace flash_proxy;
  
  [Bindable("propertyChange")]
  dynamic internal class Model extends Proxy
                                     implements IEventDispatcher
  {
    // do the singleton bit
    private static var _instance:Model;
    
    public static function getInstance() : Model
    {
      if (_instance == null)
      {
        _instance = new Model();
      }
      return _instance;
    }
    
    public function Model()
    {
      if ( _instance != null )
      {
        throw new Error( "The Model class is a singleton." );
      }
      _data = {};
      _eventDispatcher = new EventDispatcher(this);
    }
    
    // do the dynamic bunding bit
    protected var _data:Object;
    protected var _eventDispatcher:EventDispatcher;
    
    flash_proxy override function getProperty(name:*):*
    {
      return _data[name] || null;
    }
     
    flash_proxy override function setProperty(name:*, value:*):void
    {
      var oldValue:* = _data[name];
      _data[name] = value;
      var kind:String = PropertyChangeEventKind.UPDATE;
      dispatchEvent(new PropertyChangeEvent(
                       PropertyChangeEvent.PROPERTY_CHANGE,
                       false, false, kind, name, oldValue, 
                       value, this));
    }
    
    public function hasEventListener(type:String):Boolean
    {
      return _eventDispatcher.hasEventListener(type);
    }
     
    public function willTrigger(type:String):Boolean
    {
      return _eventDispatcher.willTrigger(type);
    }
     
    public function addEventListener(type:String, listener:Function, 
                       useCapture:Boolean=false, 
                       priority:int=0.0, 
                       useWeakReference:Boolean=false):void
    {
      _eventDispatcher.addEventListener(type, listener, 
                       useCapture, priority, useWeakReference);
    }
     
    public function removeEventListener(type:String, 
                       listener:Function,
                       useCapture:Boolean=false):void
    {
      _eventDispatcher.removeEventListener(type,listener,useCapture);
    }
     
    public function dispatchEvent(event:Event):Boolean
    {
      return _eventDispatcher.dispatchEvent(event);
    }
  }
}

I’ve made the class internal because it should only be accessed by the ModelLocator. the assumption is that the ModelLocator is in the same package - if it isn’t then the class should be public.

The ModelLocator

The ModelLocator then uses reflection to inspect its own properties and bind them to dynamically created properties in the Model singleton.

package model
{
  import mx.binding.utils.BindingUtils;
  import flash.utils.describeType;
  
  [Bindable]
  public class ModelLocator
  {
    private var _model:Model;
    
    // model data
    public var username:String


    public function ModelLocator()
    {
      _model = Model.getInstance();

      var description:XML = describeType( this );
      for each( var property:String in description.accessor.@name )
      {
        BindingUtils.bindProperty( this,property,_model,property );
        BindingUtils.bindProperty( _model,property,this,property );
      }
    }
  }
}

The reflection looks for accessors because making the class bindable causes the compiler to replace all the public properties with getter/setters.

The Result

Now, model data need only be declared once, as public properties in the ModelLocator class.

As before, we can make as many instances of the ModelLocator as we like. Setting a property on one will set the property on all. And binding to a property on any one will work as expected. Hence

var model:ModelLocator = new ModelLocator();
model.username = "fred";

and

<model:ModelLocator id="model" xmlns:model="model.*"/>
<mx:TextInput text="{model.username}"/>

See also:

Read more articles about Flex, Actionscript 3, Cairngorm 

3 Comments add your own

  • Do you worry about memory consumption much with this method? If you have a very large model locater (say 2000 properties), and every class that wants to access the model locater needs to make a new instance I can see memory consumption rising. I see that you are just pointing to the same instance of the model for each new instance of ModelLocator, but there is still a memory cost in creating all of the bindings for each new instance of ModelLocator.

    If I have a standard medium-sized Cairngorm app that has 50 views, and 100 events, commands, and delegates and every command needs to set a flag in the model and every delegate needs to set response data in the model, and every view needs a reference to the model obviously — that is 250 instances of ModelLocator - now you could clean up those instances and do due diligence with your memory management, but it seems like a candidate for a memory leak, or at least barrier to scaling.

    Jason  |  15th April 2008 at 5:15 pm

  • Hi Jason

    Memory may be a problem. This technique is something I only thought of yesterday, so it’s very possible that under testing it will have issues.

    I’d really prefer to get the monostate working from yesterday’s post, but can’t find the right solution. It probably requires delving deeper into the binding code and creating the bindings by hand.

    richard  |  15th April 2008 at 5:50 pm

  • There was line in the Model code above that said

    return _data[name] || name;

    This should say

    return _data[name] || null;

    I’ve changed it above but if you grabbed the code before now, you need to make this change or it won’t work with non-string data types.

    richard  |  17th April 2008 at 3:23 pm

Leave a Comment

required

required, hidden

XHTML: you can use these tags - <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>

Subscribe to the comments via RSS Feed