Speaking at the Dutch Flex Usergroup

ActionScript, Conferences, Flex, Prana, Talks No Comments »

FlugrJust a quick note to let you know that I’ll be giving a presentation on Prana at the Dutch Flex Usergroup called Flugr on the 13th of June. So if you’re in the neighborhood (Utrecht) and would like to meet, feel free to visit the event.




The following sessions will be given:

- Benjamin Dobbler & Stephan Janssen: The creation of Parleys
- Nicolas Lierman: Advanced Flex
- myself: The Prana Framework

For more info, please visit the Flugr website at http://www.flugr.nl/


Add to Bloglines - Digg This! - del.icio.us - Stumble It! - Twit This! - Technorati links - Share on Facebook - Feedburner
 

Combining Factory and Strategy patterns

Design Patterns, Inversion of Control, Prana 5 Comments »

We often run into a scenario at work where we have to create an object from xml. More specifically we are parsing xml files with exercise (or assessment item) data to a concrete exercise type like a MultipleChoice, FillGaps, HotSpot, etc. The thing is that we are supporting several xml dialects for the same type of exercise and we end up with a lot of parsers. On top of that we also need to be able to decide on a per project basis what xml dialects are supported (a customer may only be allowed to use dialext X for exercise Y). In the next lines I will try to explain the way we manage and configure the objects that are involved in this process.

The basic idea is that we create a factory, let's call it ExerciseFactory, and define a createFromXML() method on it that takes an xml object as its input and "creates" a concrete exercise as its output. The factory is basically an xml parser. It works as follows:

Actionscript:
  1. var exerciseFactory:ExerciseFactory = new ExerciseFactory();
  2. var exercise:Exercise = exerciseFactory.createFromXML(xml);

Exercise is the base class for all exercise types like MultipleChoice, FillGaps, etc. The given xml object will be in a format that corresponds to such a concrete type. The factory however needs to know what the given xml is and what concrete exercise type it should return. The easiest way to handle these different xml dialects is with a switch on some property of the xml data. This results in code similar to this (in the ExerciseFactory class):

Actionscript:
  1. public function createFromXML(xml:XML):Exercise {
  2.   var result:Exercise;
  3.   switch (xml.someProperty) {
  4.     case multiplechoice-dialect-a:
  5.       // parse type MultipleChoice with dialect a
  6.       break;
  7.     case multiplechoice-dialect-b:
  8.       // parse type MultipleChoice with dialect b
  9.       break;
  10.     case multiplechoice-dialect-c:
  11.       // parse type MultipleChoice with dialect c
  12.       break;
  13.     case fillgaps-dialect-a:
  14.       // parse type FillGaps with dialect a
  15.       break;
  16.     case fillgaps-dialect-b:
  17.       // parse type FillGaps with dialect b
  18.       break;
  19.     default:
  20.       throw new Error("Cannot create exercise for the given xml object.");
  21.   }
  22.   return result;
  23. }

Although this works, it is certainly bad design since it is code that is hard to maintain and manage. If we need to add or remove some parsers, we have to modify this code which is buried deep inside some package hierarchy. We can't change this behavior at runtime since the parsers are hardcoded into the switch/case.

We could solve this with a mapping configured inside the ExerciseFactory. As its key, it contains the value of the property in the xml data we did the switch on. As its value it contains the class or an instance of the parser. This results in something like the following:

Actionscript:
  1. public function ExerciseFactory() {
  2.   this.map = {};
  3.   this.map[multiplechoice-dialect-a] = new MultipleChoiceDialectAParser();
  4.   this.map[multiplechoice-dialect-b] = new MultipleChoiceDialectBParser();
  5.   this.map[fillgaps-dialect-a] = new FillGapsDialectAParser();
  6. }
  7.  
  8. public function createFromXML(xml:XML):Exercise {
  9.   var parser:ExerciseParser = this.map[xml.someProperty];
  10.   return parser.parse(xml);
  11. }

Ok this seems better, but we can't configure the parsers externally since they are coded in the contructor. Let's externalize this by passing in the map to the constructor and additionally create a method for adding a parser.

Actionscript:
  1. public function ExerciseFactory(map:Object) {
  2.   this.map = map;
  3. }
  4.  
  5. public function addParser(key:String, parser:ExerciseParser):void {
  6.   this.map[key] = parser;
  7. }
  8.  
  9. public function createFromXML(xml:XML):Exercise {
  10.   var parser:ExerciseParser = this.map[xml.someProperty];
  11.   return parser.parse(xml);
  12. }

This is a neat implementation. We're setting a behavior or strategy for the parser in the factory at runtime, based on some configuration. But it has a drawback: the name of the property (xml.someProperty) in the xml data we use as a key in our map always needs to be the same for the different xml dialects. Unfortunately, we can't really control this because a new dialect might be used one day that does not contain the property.

To solve this, let's add another method to our parsers: canCreate(xml:XML):Boolean. As a parameter, it takes the xml data and then checks if it can parse the data by looking into the xml structure. Finally, it returns a boolean indicating whether or not the xml can be parsed. This way, the parser decides for itself what portions of the xml data to check instead of just the one property mentioned earlier.

To force this behavior upon the parsers, we can create an interface, IExerciseParser, and then let every parser implement it:

Actionscript:
  1. public interface IExerciseParser {
  2.   function canParse(xml:XML):Boolean;
  3.   function parse(xml:XML):Exercise;
  4. }
  5.  
  6. public class MultipleChoiceDialectAParser implements IExerciseParser {
  7.   public function canParse(xml:XML):Boolean {
  8.     // check the xml structure
  9.   }
  10.   function parse(xml:XML):Exercise {
  11.     // parse the xml to a MultipleChoice
  12.   }
  13. }
  14.  
  15. public class MultipleChoiceDialectBParser implements IExerciseParser {
  16.   public function canParse(xml:XML):Boolean {
  17.     // check the xml structure
  18.   }
  19.   function parse(xml:XML):Exercise {
  20.     // parse the xml to a MultipleChoice
  21.   }
  22. }
  23.  
  24. public class FillGapsDialectAParser implements IExerciseParser {
  25.   public function canParse(xml:XML):Boolean {
  26.     // check the xml structure
  27.   }
  28.   function parse(xml:XML):Exercise {
  29.     // parse the xml to a FillGaps
  30.   }
  31. }

We can now change the implementation of the ExerciseParser. We will remove the map because we will no longer be checking on a key to get a parser, but the parsers will check for themselves.

Actionscript:
  1. public function ExerciseFactory(parsers:Array) {
  2.   this.parsers = parsers;
  3. }
  4.  
  5. public function addParser(parser:IExerciseParser):void {
  6.   this.parsers.push(parser);
  7. }
  8.  
  9. public function createFromXML(xml:XML):Exercise {
  10.   var result:Exercise;
  11.   for (var i:int = 0; i<this.parsers.length; i++) {
  12.     var parser:IExerciseParser = this.parsers[i];
  13.     if (parser.canParse(xml)) {
  14.       result = parser.parse(xml);
  15.       break;
  16.     }
  17.   }
  18.   if (!result) {
  19.     throw new Error("Cannot create exercise for the given xml object.");
  20.   }
  21.   return result;
  22. }

... and configure our exercise factory before using it:

Actionscript:
  1. var exerciseFactory:ExerciseFactory = new ExerciseFactory();
  2. exerciseFactory.addParser(new MultipleChoiceDialectAParser());
  3. exerciseFactory.addParser(new MultipleChoiceDialectBParser());
  4. exerciseFactory.addParser(new FillGapsDialectAParser());

What we have achieved now is that we can add new parsers that support new xml dialects without the need to alter the code of the ExerciseFactory. All we need to is create a new parser, let it implement the IExerciseParser interface and it to the ExerciseFactory.

Additionaly, configuring the ExerciseFactory in a Prana application context is a piece of cake:

XML:
  1. <objects>
  2.   <object id="exerciseFactory" class="ExerciseFactory">
  3.     <constructor-arg>
  4.       <array>
  5.         <object class="MultipleChoiceDialectAParser"/>
  6.         <object class="MultipleChoiceDialectBParser"/>
  7.         <object class="FillGapsDialectAParser"/>
  8.       </array>
  9.     </constructor-arg>
  10.   </object>
  11. </objects>

Hope you enjoyed this. Don't hesitate to leave questions or comments. Have fun coding!


Add to Bloglines - Digg This! - del.icio.us - Stumble It! - Twit This! - Technorati links - Share on Facebook - Feedburner
 

Prana enabled Cairngorm Store

ActionScript, Cairngorm, Flex, Inversion of Control, Prana 11 Comments »

Following up on Douglas McCarroll's modified Cairngorm Store update, I thought I'd take this a step further and add a Prana application context to configure the business delegates and the service locator. This example has support for mock delegates that contain hardcoded local data and is also able to connect to php services using Remote Objects and AMFPHP.

This examples replaces the older Cairngorm Store example that was configured to work with AMF0 and Renaun Erickson's RemoteObjectAMF0. It now uses the latest beta version of AMFPHP and the AMF3 protocol. The services are hosted on this domain so you don't need to setup AMFPHP in order to see this in action.

The code is currently in svn and can be checked out at http://prana.svn.sourceforge.net/viewvc/prana/trunk/samples/PranaCairngormStore/. This sample will also be included in the next release.

Here's a snippet from the readme file:

***

The following files have been added/changed in order to add Prana configuration support for business delegates and service locator:

* Main.mxml: added application context loader and forced compilation of configured classes

package com.adobe.cairngorm.samples.store.business
* BaseBusinessDelegateMock.as: implements IResponderAware, removed responder constructor argument
* CreditCardDelegate.as: extends AbstractRemoteObjectDelegaten, implements ICreditCardDelegate
* CreditCardDelegateMock.as: implements ICreditCardDelegate
* ICreditCardDelegate.as: interface for credit card delegates
* IProductDelegate.as: interface for product delegates
* ProductDelegate.as: extends AbstractRemoteObjectDelegaten, implements IProductDelegate
* ProductDelegateMock.as: implements IProductDelegate

package com.adobe.cairngorm.samples.store.commands
* GetProductsCommand.as: added delegate lookup via ShopModelLocator
* ValidateCreditCardCommand.as: added delegate lookup via ShopModelLocator

package com.adobe.cairngorm.samples.store.model
* ShopModelLocator.as: added creditCardDelegate and productDelegate variables to configure business delegates

***

As a result, we can now lookup the implementation of our business delegates in the commands instead of instantiating new ones. The commands are completely unaware of the implementation of the business delegates. One note though: since we are not creating new delegate instances, we need to set the responder as a property of the delegate before calling its methods.

Actionscript:
  1. var delegate:IProductDelegate = ShopModelLocator.getInstance().productDelegate;
  2. delegate.responder = this;
  3. delegate.getProducts();

Here's how the application is configured in the application context to use the mock delegates:

XML:
  1. <object id="shopModelLocator" class="com.adobe.cairngorm.samples.store.model.ShopModelLocator" factory-method="getInstance">
  2.     <property name="creditCardDelegate">
  3.         <object class="com.adobe.cairngorm.samples.store.business.CreditCardDelegateMock"/>
  4.     </property>
  5.     <property name="productDelegate">
  6.         <object class="com.adobe.cairngorm.samples.store.business.ProductDelegateMock"/>
  7.     </property>
  8. </object>

And here's how the remote object delegates and the service locator are configured:

XML:
  1. <object id="endPoint" class="String">
  2.     <constructor-arg value="http://www.herrodius.com/amfphp/gateway.php"/>
  3. </object>
  4.  
  5. <object id="serviceLocator" class="org.pranaframework.cairngorm.CairngormServiceLocator" factory-method="getInstance">
  6.     <property name="productService">
  7.         <object class="mx.rpc.remoting.mxml.RemoteObject">
  8.             <property name="destination" value="GenericDestination"/>
  9.             <property name="endpoint">
  10.                 <ref>endPoint</ref>
  11.             </property>
  12.             <property name="source" value="com.adobe.cairngorm.samples.store.business.ProductService"/>
  13.         </object>
  14.     </property>
  15.     <property name="creditCardService">
  16.         <object class="mx.rpc.remoting.mxml.RemoteObject">
  17.             <property name="destination" value="GenericDestination"/>
  18.             <property name="endpoint">
  19.                 <ref>endPoint</ref>
  20.             </property>
  21.             <property name="source" value="com.adobe.cairngorm.samples.store.business.CreditCardService"/>
  22.         </object>
  23.     </property>
  24. </object>
  25.  
  26. <object id="shopModelLocator" class="com.adobe.cairngorm.samples.store.model.ShopModelLocator" factory-method="getInstance">
  27.     <property name="creditCardDelegate">
  28.         <object class="com.adobe.cairngorm.samples.store.business.CreditCardDelegate"/>
  29.     </property>
  30.     <property name="productDelegate">
  31.         <object class="com.adobe.cairngorm.samples.store.business.ProductDelegate"/>
  32.     </property>
  33. </object>


Add to Bloglines - Digg This! - del.icio.us - Stumble It! - Twit This! - Technorati links - Share on Facebook - Feedburner
 

Prana Presentation Slides

ActionScript, Conferences, Inversion of Control, Prana 1 Comment »

I put up the slides of the Prana presentation I did on the Adobe Usergroup - AIR Pre-Release Tour. You can download them at the Talks section or here (direct download).

Further, a number of people have blogged about the meeting:
- Ward de Langhe
- Gilles Vandenoostende
- Wim Vanhenden


Add to Bloglines - Digg This! - del.icio.us - Stumble It! - Twit This! - Technorati links - Share on Facebook - Feedburner
 

Prana 0.4 Released!

ActionScript, Flex, Prana 5 Comments »

I’m proud to announce that Prana 0.4 has been released.

Some of the key features and updates:

  • major update to the core IoC container
  • support for PureMVC
  • several bugfixes and minor improvements

General info: http://www.pranaframework.org
Download: SourceForge Download Page

Changes in version 0.4 (26.01.2008)
-----------------------------------

General
* introduction of PureMVC support
* added PureMVC sample application
* nightly builds available at http://prana.herrodius.com
* fixed config.xsl to ignore _svn folders

Package org.pranaframework.cairngorm
* fixed early dispatching of events in EventSequence

Package org.pranaframework.collections
* added "remove" method to IMap and Map
* changed "size" and "values" methods to getters
* Map now extends Dictionary instead of Proxy

Package org.pranaframework.config
* AppSettings now extends Proxy instead of Map

Package org.pranaframework.ioc
* added check for valid IList before creating cursor
* added "isLazyInit" and "initMethod" properties to IObjectDefinition and ObjectDefinition
* added support for init method in ObjectContainer
* added "removeObjectFromInternalCache" method
* enhancements to "getObject"
* added post processing capabilities to ObjectContainer

Package org.pranaframework.ioc.factory
* added IObjectContainerAware

Package org.pranaframework.ioc.factory.config
* added ObjectContainerAwarePostProcessor
* added LoggingTargetFactoryObject

Package org.pranaframework.ioc.parser
* added support for lazy init and init method in XmlObjectDefinitionsParser
* fixed "parseProperties" because of Map refactoring, keys were not strings in XmlObjectDefinitionsParser

Package org.pranaframework.puremvc
* initial release

Package org.pranaframework.utils
* added Parse port from the Fit framework
* added HtmlUtils utility methods for working with html
* added "isExplicitInstanceOf" method to ObjectUtils


Add to Bloglines - Digg This! - del.icio.us - Stumble It! - Twit This! - Technorati links - Share on Facebook - Feedburner
 
WP Theme & Icons by N.Design Studio
Entries RSS Comments RSS Log in