Breaking Object Dependencies via String References, static variables and Dictionaries

Posted on April 14, 2011

0


When you run a project with many objects clustering and cross-referring, the trick is to keep it simple, to avoid a complex mess of objects and object references.

Here is the base-list of what I started doing for just such a project in Flex in the past year:

  1. Define the main identifiers – Preferably as String values
  2. Create a unique key using these identifiers – Preferably by concatenating, to avoid boilerplate code
  3. Register each of your objects in a Static Dictionary within that class – To allow for direct access using the key

What do you normally do?

When you want to refer to other objects, the classic approach is to either use:

  1. Event Listeners and Event Dispatchers – Advantage is that you capture and process the even where needed, deal with the data there.
  2. Object references – Telling any object that needs to know what the objects are it might want to refer to

What is the problem here?

  1. Event dispatching – Disadvantages of dispatching events are many. I mention two.
    1. Overhead – You are broadcasting to a lot of objects who will either ignore the event, or capture the event and discard of it, wasting your resources while doing it.
    2. Who? What? Why? – Events can be dispatched from anywhere and the reasons why you received an event are not always clear. Especially when some event dispatch was not cleaned up and is fired when you do not expect that. Also when dispatching it, who will receive it? When you want to see who might respond, you will have to find the places where the event is captured.
  2. Object references – Especially when you start to have multiple levels in your code, you will start to have dependencies that are harder and harder to maintain.

What is the goal?

To create a simplified way to refer to objects from other objects and get things done faster and simpler when you want interactions between objects.

As one of the side-effects, double-binding objects becomes a lot easier: allowing you to do stuff from “A” to “B” and from “B” to “A”.

WTF? Static variables and static methods!?

We use static variables, Dictionaries to be more specific, to solve this issue. “Using static variables” and Static Methods is regarded as suspect coding behavior for the following reasons:

  1. When a static variable is changed somewhere, it can fuck up the app somewhere else – Potentially fucking up Unit Testing as it is unclear who changed what when, when the value of a static variable is suddenly changed
  2. When creating new objects extending the class, static methods are crap – Potentially fucking up one of the glorious option of OOP: Polymorphism

When you look at the code below, you will see that the static variables and methods are used in a different type of way than what is described above. The Design Pattern used is closest to an Object Pool and a Multiton.

Let’s get down: Example

Lets assume:

  1. You have a WorldMapChart object you use at least twice in your project.
  2. WorldMapChart 1 is called “WorldMap1”, WorldMapChart 2 is called “WorldMap2”. We capture that in mapID
  3. In each of these worldmaps you have County Polygons with datasets stored in: CountryDatasetVO
  4. Each of these countries is represented with an item in a list. We represent countries with a countryID.
  5. Each item in the list gets highlighted when you mouse over a country and vice versa
  6. We use mapID and countryID as our main keys to avoid leaking events to other instances of the world map.

Code example

We only take one aspect to avoid overloading this post: the Country Dataset Value Object.

The code given only deals with registering the object in a Dictionary and offering a Static method to retrieve a specific VO.

class CountryDatasetVO
{
   private static var countryDictionary:Dictionary=new Dictionary()
   private var _mapID:String;
   private var _countryID:String;
   public function CountryDatasetVO(mapID:String):void
   {
       _mapID=mapID;
   }
   public function set countryID(id:String):void
   {
      // Set country ID
      _countryID=id;

      // Add Country Data Set to mapping
      var key:String=_mapID + "_" + _countryID;
      countryDictionary[key]=this;
   }
   public static function getCountryDatasetVO(countryID,mapID):void
   {
       var key:String=mapID + "_" + countryID
       return countryDictionary[key];
   }
}

By using a direct call on the class and using the proper keys, we can get any object we want.

var countryDatasetVO:CountryDataSetVO;
countryDatasetVO CountryDataSetVO.getCountryDatasetVO(mapID, countryID)

Simple as that.

When we are consistent in our classes, each class will at least have the basic keys to refer to any other object via these generic identifiers. And naturally we do everything the exact same way anywhere else. Leaving us with an incredibly simple and solid Object Reference Model across our entire application.

We do not have to worry about nesting objects N levels deep or when and where they are instantiated. We do not have to create extra classes and resolvers for object specific events. As long as we pass our keys and do a check on null values we are going for solid gold.

if (countryDatasetVO == null)
{
   // the object does not exist, take action or exit here
}

Moving it a step further

Let’s say that the CountryDataSetVO is updating the view when data is received and lets assume each Country is setup the same way.

// In countryDatasetVO
private function onDataUpdate(e:Event):void
{
   // Getting the country polygon
   var countryPolygon:CountryPolygon;

   // As they share the same key values, you can use that
   countryPolygon=CountryPolygon.getCountry(this._mapID, this._countryID);

   // Check if we have contact
   if(countryPolygon==null)
   {
      // No polygon, exit
      return;
   }

   // Reflect data
   countryPolygon.reflectData(this);

   // Also in the list item
   var countryListItem:CountryListItem
   countryListItem=CountryListItem.getListItem(this._mapID,this,_countryID)

   // as we create the object when not there, we always have result
   countryListItem.reflectData(this);
}

Moving it even further: BlazeDS

When you use BlazeDS, objects you send to the Server are not the same as the ones you receive. By using the String References to identify Data Objects you already use in the application, you can easily retrieve the Client Side object it represents and inject the values you received from the Server.

Result

As we store object references in the classes they are created with, available in a Static Dictionary and available via a Static Method, the need to pass specific Object References is removed.

We also simplify the application for anyone else who comes after us. When extending the application with new parts, knowing that passing- and using Key Referrer values is all you need to access specific objects is enough to make sense of it.

In the example above we can easily add more items in the onDataUpdate even handler. As we only need the key referrers and the objects we want to refer to resolve the actual object, we have no limits except our needs.

Comparison (no code)

When using events, we need to dispatch these over some kind of pipeline. As Flex and Flash uses the Display List for that by default, you might want to choose using a Static Dispatcher in a specific Class. So that your non-DisplayObjects can receive these too.

When you receive the event, you need to crack the contents to see if this event was for you, or if you can ignore it. With about 250 countries in a World Map that is doable.

Benefits

  1. Simplification of the project code – No need to inherit classes or class-references, no hard coded dependencies or chains of dependencies
  2. No Dependency Chains – When you pass object references to child objects, you create dependency chains. Dependency chains will break your balls at some point when you start refactoring or when
  3. More agile – Changes can be made easier due to the reduction of dependencies and dependency chains in the application
  4. Clear reference to key identifiers – Each project has a specific structure with primary and secondary objects
  5. Easier to do double binding – When you want objects to refer to each other and when they share the same key values, using these key values is all you need to find the object you want to manipulate
  6. Less headaches – Complex applications can become like advanced math: very complex and hard to crack down. Knowing that key values reflect the application structure and knowing which key values help you find the right objects is enough.

Things to take care of

The moment real performance becomes your game, you will need to optimize the code. You do not want to constantly have to resolve “object C” when you can do that once and save further overhead once done.

Double-binding is another tricky friend when optimizing. Especially when you do not know who will be there first. Each of the objects doing double binding will have to be able to receive and to create a binding.

This post does not cover that aspect.

Advertisements