Learning Cairngorm

baacloud免费翻墙vpn注册使用

Posted At : February 16, 2007 12:17 PM | Posted By : Jeffry Houser
Related Categories: Flex,Professional
In my previous post I talked about some of my experiences while trying to learn Cairngorm. This post extends on those experiences, as I continue to read Steve Webster's series of articles on the framework.
Getting the Cairngorm Store to work
Part three of Steven's series finally starts to get some meat. He goes through the example of how to use the Model Locator pattern within the Cairngorm store application. The Cairngorm store is a modification of The Flex store. The Flex store was designed to showcase features of Flex. The Cairngorm Store is just The Flex Store rebuilt to demonstrate how to build apps using Cairngorm. This article was a bit lost on me without reviewing the code. I think it's time to download the Cairngorm 2.1 Store and see if I can get it running.
I downloaded the store zip file, create a new Flex Builder project and pointed it to that directory. 8 errors exist right off the bat. After a look at a few of the errors, it seems the store download doesn't contain the Cairngorm framework. So, I grab the framework and unzip it into the same directory. The framework tries to overwrite some of my eclipse property files. I declined that.
Seven errors went away, a new one came up, and one stayed constant. This is progress.
com.adobe.cairngorm.IServiceLocator.as throws an error on line 92. 1046: Type was not found or was not a compile tome constant: DataService. A function's return type is set to DataService. mx.data.DataService is properly imported in this component. I am not the only one with these errors. I discovered the solution on Flex Coders. You must add the cairngorm.swc file to the class path.
I tried that and had no more errors, the Cairngorm store is able to compile and run. Another hurdle down. Unfortunately, we aren't out of the woods yet.
So, where are my Products?
When loading the app, the products are not found. I figure there is a path error somewhere. How do I Debug this?
I started with the main.mxml. OnCreationComplete, a Cairngorm event is fired named “EVENT_GET_PRODUCTS”). I forget how, but I ended up in com/adobe/cairngorm/samples/control/shopcontroller.as. I think this defines what happens when events get fired. When the EVENT_GET_PRODUCTS is fired the GetPRoductsCommand is … Well, I dunno, but I opened that file next. The execute method triggers a getProducts method in ProductDelegate. ProductDelegate calls “GetRemoteObject” for productService.
(Note: In Flex Builder a control click will open the source code on the object if it is available. I used this a lot to try to figure out what was being called and where )
I'm guessing that at this point that my remoteObject is incorrect and the productService is somehow pointed at the wrong place. I had Services.mxml opened (but I'm not sure why) and I think I found the problem there. All services are defined here as RemoteObjects. The RemoteObjects specify a destination. The RemoteObject class will only work with Flex Data Services. The destination attribute points to an entry in the services-config.xml.
I don't have Flex Data Services installed. This store is in serious need of some documentation, or at least a readme file. I'm thinking I can replace the RemoteObject with some other form of data retrieval, but, then I realize I don't have data anywhere. There is some shipping and state information in XML documents, but no products.
What Next?
Now I start doing some digging into other directories. I discover that the Java code is included in the application. Poking through, I discovered that the Java Code is not accessing a database or external datasource. It's just manually populating the Value Objects and sending them back.
I bet the backend could be rewritten in ColdFusion. And that is my plan for part 3.
In the last part of this series, I spoke about how I was going to see if I could get the Cairngorm store working from ColdFusion instead of Java. I was successful. The exercise, unfortunately, did not move along my understanding of Cairngorm. However, it did teach me a lot about ColdFusion and Flex interrelate. There will come a day where I'm glad for this experience.
Turning Java Classes into ColdFusion Components
The first thing I did was to convert all the java classes into ColdFusion components. The java components are in “WEB-INF\com\adobe\cairngorm\samples\store\*”.
I decided, for simplicity, to create my CFCs in the root directory of the Cairngorm store app, instead of in a subdirectory structure. You do not have to do that to use ColdFusion with Flex.
The first class I migrated was CreditCardDelegate. This contains a single function, validateCreditCard. It doesn't look like an credit card validation is done in this method, it just picks a random number. Half the time it will be validated, and half the time it won't. It's not real world, but for proof of principle purposes it suffices. The java class used Math.random to pick the random number. I used ColdFusion's function RandRange.
The next file was the ProductDelegate. It seems to me that a delegate is akin to service objects used commonly by ColdFusion developers. The ProductDelegate has a single method, getproducts. It returns a list type, which is a bit problematic. The Java List is an interface, with other types mapping to it. In the Data Access Object, the Linked List class is used. ColdFusion doesn't have a parallel. At first I was going to use a query, but eventually decided upon using an Array of value objects. The Java version of ProductDelegate does not create an instance of the ProductDAO inside it. This is something I changed in the CFC, although I'm not sure if it was an improvement, but I don't want to go back and change it.
The Java version had an abstract class named DAO. I didn't bother to convert this.
ColdFusion and Java Differences
Next there is a ProductDAO.java file. This is the data access object layer. The Java version creates a LinkedList of product value objects. The ColdFusion version creates an Array of product value objects. In the Java code, it looked like an instance variable was created (products) and then never used again. This was a result of my inexperience with Java. In ColdFusion, there is no distinction between variable definition and variable assignment. The variable is defined as a private instance variable:
private List products = new LinkedList();
And then later reinitialized in a method:
products = new LinkedList();
I mistook the second for creating an function local variable of the same name as the instance variable. It's not doing that, though. It is just re-initting it. The DAO does not access a database, local storage, an XML file, or web service to get it's data. It's all hard coded into the component. You wouldn't want to do this in the real world, but for the proof-of-principle-ness of this app, it fine.
I took each Java code block, like this:
ProductVO product1 = new ProductVO();
product1.setId( 1 );
product1.setName( “USB Watch” );
product1.setDescription( “So, you need to tell the time of…” );
product1.setPrice( 129.99f );
product1.setImage( “assets/products/usbwatch.jpg” );
product1.setThumbnail( “assets/products/usbwatch_sm.jpg” );
products.add( product1 );
And turned it into CF code, like this:


product1.setId( 1 );
product1.setName( “USB Watch” );
product1.setDescription( “So, you need to tell the time of…” );
product1.setPrice( 129.99 );
product1.setImage( “assets/products/usbwatch.jpg” );
product1.setThumbnail( “assets/products/usbwatch_sm.jpg” );
ArrayAppend(products, product1 );

All the vars went at the top of the method, of course. Once the Value Object is created, we just call the set methods to populate it with data. I copied most of the code from Java to ColdFusion with few . I removed the 'f' at the end of price. This most likely it stood for 'float', AKA decimal numbers. The only change of the code block is in the last line. In Java, the code was being added to a link list. In ColdFusion I'm using ArrayAppend.
The Product Value Object
The product value object was simple to convert from a code standpoint. However, getting it to work with Flex was a bit tricker. Flex and ColdFusion are very finicky on how CFCs will be automatically converted to ActionScript objects. Before getting into ActionScript, first, I'll discuss the CF side of things.
There are six instance variables in the ProductVO component. An id, name, description, price, image, and thumbnail. I recreated all of them in ColdFusion. The Java code created the instance variables as private. With ColdFusion code, I had to put them in the this scope, and define the components using the cfproperty tag.
A bunch of get and set methods were in the Java version of the product value object. I wasn't sure how closely the CFC and AS files had to be related, so I converted them all from Java to CF.
The Java method had two methods, toString and equals that ColdFusion would not let me convert. You cannot create methods that have the same name as as reserved words in CF. toString and equals are the name of generic Java methods, and ColdFusion does not allow you to create methods that are similar to names of built in functions. Even though toString and equals are not built in functions, CF still throws the error. With no other alternative, I left those two methods out.
I tested my components with this code:

MyObject = CreateObject('component','productdelegate');


Once I got everything the CFCs working, I moved back to the Flex code.
Defining the Services
In theory, all you need to do at this point is to tell the remoteObject tags to point at the ColdFusion code, not the Java code. Open up com/adobe/cairngorm/samples/store/business/services.mxml. This is the file that defines the services your application uses. You'll see one remoteObject for the productService and one for the creditCardService.




We could create those destinations in ColdFusion's services-config.xml file, but I instead chose to use the default ColdFusion destination. Comment out the previous lines and replace them with these:




You'll have to modify the source attribute to with the path to your remote objects. I was hoping that after this, I'd be good to go. Unfortunately that was not the case. It would work with only this change. I was wrong. First, I forgot to add the services-config.xml to my compiler settings. In Flex Builder, bring up properties on your project, select Flex Compiler, and add this compiler argument:
-services C:\CFusionMX7\wwwroot\WEB-INF\flex\services-config.xml
Everything still wasn't working kosher. Next, open up the ActionScript value object (com/adobe/cairngorm/samples/vo/ProductVO.as). Find this line:
[RemoteClass(alias=”com.adobe.cairngorm.samples.store.vo.ProductVO”)]
And change it to point to your CFC:
[RemoteClass(alias=”htdocs.Experiments.CairngormStore.ProductVO”)]
If you built your CFC object in the same place as the Java object, you may not need to change this line.
I tested the code and it still wasn't working. Ben Forta has a great post about gotchas when doing this. make sure that the instance variables are identical in the AS file and ColdFusion file. They must also be in identical order. Check! Use full paths to the CFCs. Check! Specify RemoteClass in the AS file. Check! What's left? The answer is case sensitivity.
You want to be very careful about the case sensitivity in your paths. My CF code worked fine without case sensitivity, but Flex was being problematic. Once I addressed the case sensitive issue, I was able to get a proof of principle template working.
But within the context of the cairgorm store, Something was still wrong. I opened up the com/adobe/cairngorm/samples/store/command/GetProductsCommand.as file. I believe the framework must look for this file when invoking the GetProductsEvent.EVENT_GET_PRODUCTS event, since I could not find it explicitly called anywhere. If the fault event is getting called, you'll see the message “Products could not be retrieved” when loading the file. This means something is wrong with your remoteObject configuration.
However, if you get no errors, but nothing is happening then something else must be wrong. or if you see other error messages, something else must be wrong. During the course of my development I experienced both situations. First I added a quick Alert to the result function to be sure that the results are being returned properly:
Alert.show( “Products retrieved!” );
That helped me verify that the result method was indeed firing. There was one line of ActionScript in the result method that I didn't understand:
var products : ICollectionView = ICollectionView( event.result );
This line creates a product variable of ICollectionView, and calls the ICollectionView constructor with the 'event.result' as an argument. Unfortunately, ICollectionView is an interface and does not appear to have a constructor. This line was throwing errors.
After some digging I found that an ArrayCollection inherits the ICollectionView methods through the ListCollectionView class. All code written against the ICollectionView interface should also work against an ArrayCollection. I replaced the ICollectionView line above with these two:
var products : ArrayCollection = new ArrayCollection ();
products.source = event.result as Array;
And bingo, everything started working.
Getting it set up
I'm not sure how much of this code I'm allowed to distribute, but my code changes, along with the Cairngorm Store and Cairngorm framework are attached to this post.
This is what you have to do to get it working:
1. Download the file and unzip it to a web accessible directory. Create a Flex builder project pointed at that directory.
2. Add bin/Cairngorm.swc to your Library path.
3. Make sure that Services compiler argument is pointed to the services-config.xml for Flex.
4. Open up services.mxml and change the attributes to point to the CFCs. Watch your case sensitivity
5. Open up ProductVO.as and make sure that the RemoteClass directive properly points to the location of ProductVO. Watch your case sensitivity here too.
6. Build and run your project.
Where to Next
I did notice that I couldn't complete an order in this system because the credit card validation was failing. But, it looks like CC Validation is implemented as a crap-shoot. I either didn't try enough times or have a bug in the CreditCardDelegate CFC.
I'm wondering how the value objects that Cairngorm uses will work with ColdFusion Queries, or if it's better to run a query in CF, populate an array of Value Objects, and return that array. I'll have to do tests with this in the future.
To date, I have an understanding of some parts of Cairngorm, but I do not understand how it all comes together. It's time for me to go back to reading through Steven's articles, so the next article of this series will lean back towards them.
I can't believe it has almost 8 months since my last installment in the Learning Cairngorm series. Life got in the way, and I have a lot of experience with Cairngorm now compared to when I wrote the first three parts. In the last part, I spoke a bit about getting the Cairngorm Store to work with ColdFusion. In this part, I'm going to switch back to Steve Webster's collection of Cairngorm articles and talk a bit more about some of the additional design patterns related to Cairngorm. I'll summarize Part 4 of Steven's Article and intersperse it with some of my own experience.
User Gestures have Changed
Applications have grown in complexity these days and there are many different ways that the user will need to interact with your application. Every opportunity that a user has to interact with your applications is a gesture. In the traditional HTML interface world, each gesture requires the loading of a new page. AJAX has changed this somewhat, and Flex has changed it a lot. Sorting a data grid or filtering a group of data can all be done without a round trip to the server? This is part of what leads to a rich experience for the user. A benefit of Cairngorm is that all gestures, whether or not a round trip to the server is needed, can be treated exactly the same. No matter what is going on, we have a standard set approach, and can use that to more easily Debug potential problems.
Events, Commands, Controllers, oh my
First, we create a CairngormEvent class for each user gesture. The event class extends a CairngormEvent. It will contain a static variable with the name of the event. It can also contain instance variables, if those are needed to complete the event actions. This might be a sample CairngormEvent class:
package com.tempApp.event{
import flash.events.Event;
import com.adobe.cairngorm.control.CairngormEvent;
public class MyCustomEvent extends CairngormEvent{
public static var EVENT_ACTION : String = “actionname”
[Instance data here]
public function MyCustomEvent ([Instance data as arguments]){
[Set Instance Data Here]
super(EVENT_ACTION);
}
/**
* Override the inherited clone() method, but don't return any state.
*/
override public function clone() : Event{
return new MyCustomEvent ( );
}
}
}
This is a simple sample event class, but very powerful. The event class ties into a command class is the one that does the work. A command is a pattern which always has a single point of entry: an execute method. By creating a generic execute method on every command, the Cairngorm framework can generically run any command in response to an event. This might be a sample command class:
package com.tempApp.command{
import com.adobe.cairngorm.control.CairngormEvent;
import com.adobe.cairngorm.commands.ICommand;
import com.tempApp.event. MyCustomEvent
public class MyCustomCommand implements ICommand{
public function execute(event:CairngormEvent):void{
var e : MyCustomEvent = event as MyCustomEvent;
[Do stuff here]
}
}
}
When Cairngorm “sees” the Event, how does it know to relate it to a command class? A third class, a front controller, ties the two together:
package com.tempApp.control{
import com.adobe.cairngorm.control.FrontController;
import com.tempApp.event.*;
import com.tempApp.command.*;
public class AssemblerController extends FrontController{
public function AssemblerController(){
initialiseCommands();
}
public function initialiseCommands() : void{
addCommand(MyCustomEvent. EVENT_ACTION, MyCustomCommand );
}
}
}
I had one of those “hallway conversations” with some folks at 360Flex and a few people claim that a front controller has no place in a Cairngorm and is just overkill. I haven't had time to think about that, or look into alternatives to the approach, but I thought it'd be something worth throwing out there for those listening. If you have thoughts on this, comment and tell me why a controller has no place in Cairngorm.
Tying them all together
We have three elements at play, here:
* Start with an event (AKA User Gesture).
* The Event goes to a controller and figures out what command to execute.
* Execute the Command
There are two missing bookends to our list list. First, what exactly does “Start with an event mean”? How do we tell the Cairngorm Framework or Flex that an event has taken place? Cairngorm has a special event dispatcher that tells the framework something happened that it needs to respond to. A sample line of code would be something like this:
import com.adobe.cairngorm.control.CairngormEventDispatcher;
import com.tempApp.event.MyCustomEvent;
CairngormEventDispatcher.getInstance().dispatchEvent( new MyCustomEvent ([optional arguments]) );
That is our first bookend. We dispatch our custom event, which looks at the controller, and executes the command. Holding up the stack, the command will then update the ModelLocator, which through the miracle of data binding will update relevant areas of the application. Our updated path list is like this:
* Dispatch a custom event (AKA User Gesture).
* The Event goes to a controller and figures out what command to execute.
* Execute the Command
* Command updates Model data, which updates the user views
Conclusions
This took a look at the architecture of a Cairngorm system events. We did not deal with retrieving data from a remote source, though. I'll look into that in the next part of the series. Now that I'm in a routine of blogging twice a week (Tuesday and Thursday morning) the next part of the series should come much quicker than this one.
Note: The alias link to this post is now working. I had to refresh the blog cache for the date to update. Not sure how this will affect aggregators
In the fifth part of this series on Cairngorm, I'm going to talk about how Cairngorm handles remote data calls. This, once again, is following along with the fifth part of Steven Webster's Adobe Center Articles.
A Recap
In the last article I wrote in this series I spoke about how Cairngorm handles user gestures. A user gesture is anything that the user will do to interact with your application. This is the general procedure:
* Dispatch a custom event (AKA User Gesture).
* The Event goes to a controller and figures out what command to execute.
* Execute the Command
* Command updates Model data, which updates the user views
We are missing is getting live data, probably from a database, using some intermediary software like ColdFusion (my choice) or PHP. (Or Java or Ruby, or .NET, or whatever).
3 Types of Services, one Location
There are three ways to get data from within Flex. You can use an HTTPService, a WebService, or a RemoteObject. Based on their names, it is probably pretty obvious what each tag does (Right?). My preferred method is RemoteObject because it ties into ColdFusion so nicely, but I've also done some work with HTTPService for integrating with remote systems beyond my control. Cairngorm uses a ServiceLocator to help you find services. This is, conceptually, not much different than a Model Locator. It is a singleton class that allows you to store your services in a central location, and access them anywhere from the application. This would be a sample ServiceLocator:





I have three (fake) services, one of each type. If you've used any of these tags before, you'll notice that I left out the 'result' and 'fault' events. Result and Fault methods are known as responder methods. They get called when the remote service sends data back to Flex. Cairngorm puts the responder methods in the Command, and calls the Service using a Delegate.
Delegates
A Delegate is a class that does nothing but call the service. Here is a simple example:
package com.tempApp.fooDelegate{
public class MyCustomDelegate{
private var responder : IResponder;
private var service : Object;
public function MyCustomDelegate ( responder : IResponder )
{
this.service = ServiceLocator.getInstance().getRemoteObject( ” sampleRemoteObject ” );
this.responder = responder;
}
}
public function getFoo ([foo arguments]):void{
var call : Object = service.getFoo([foo arguments]);
call.addResponder(responder);
}
}
Cairngorm keeps the delegates and the services file in a business folder. I guess this ties into “Business Logic”. Really this is just a layer to reach the backend business logic, though. I don't do a lot of work in the delegates. They just make calls. To me it makes sense to do data parsing in the responder, and as such in the command. ( Although this approach has been contested ). We can modify our command, Part 4, to handle make the delegate call.
package com.tempApp.command{
import com.adobe.cairngorm.control.CairngormEvent;
import com.adobe.cairngorm.commands.ICommand;
import com.tempApp.event. MyCustomEvent
import com.tempApp.business. MyCustomDelegate
import mx.rpc.events.ResultEvent;
import mx.rpc.events.FaultEvent;
public class MyCustomCommand implements ICommand
, IResponder{
public function execute(event:CairngormEvent):void{
var e : MyCustomEvent = event as MyCustomEvent;
var delegate : MyCustomDelegate = new MyCustomDelegate ( this );
delegate.getFoo([instance variables from event]);
}
public function result( event : ResultEvent) : void
{
[do stuff with results]
}
public function fault( event : FaultEvent ) : void
{
[Do error processing]
}
}
}
The first change is that the code implements an IResponder interface in addition to the ICommand. The second change is that the execute method actually creates an instance of the delegate (passing in the instance of this object), and calls a method on the delegate. This modified class also includes a result event and a fault event. These are the responder methods called by the service when results are returned. Most likely the result event will be performing some actions on the ModelLocator, which will change some other aspect of the app via data binding.
A recap of Today
So, our updated process is this:
* Dispatch a custom event (AKA User Gesture).
* The Event goes to a controller and figures out what command to execute.
* Execute the Command
* Command creates instance of Delegate.
* Delegate gets Service Object from Service Locator
* Delegate calls the Service Object Method
* Remote Service does stuff and sends data back to the respond methods in the Command
* Command updates Model data, which updates the user views
A few final thoughts
I've spoken to some people who claim that Cairngorm must use a one-to-one ratio with Events, Commands, and Delegates. However, I do not implement code in such a manner. It seems to add an extra layer of complexity. I often have one delegate per service, and that delegate is used by many commands. Since the delegate does not do anything but call the remote service, it's a really small file. I can easily deal with multiple remote method calls in the same file.
It's a bit late, so if this is weird, it's because I'm proof-reading while tired.
On Friday (Tomorrow when you read this), I plan to start releasing the collection of articles from my CF101 column over at sys-con. They should be easy lunch time reading.
There was a presentation a while ago talking about the different frameworks in Flex. The presenters speak ill of Cairngorm's use of Singletons (It's about 20 minutes in, but I strongly recommend watching the whole thing if you haven't seen it yet). I thought it would be a worthy discussion to discuss the complaints with Singletons, how they relate to Cairngorm, and how I deal with this when building Cairngorm applications.
What is a Singleton
Singleton is a design pattern. It is a class that is only instantiated once. Only a single instance of the singleton object will exist. The most common use of singleton that I've seen is as a location for global data. Sometimes Singletons are used in frameworks as a single point of entry into the framework APIs.
In the ColdFusion world, the application or session scope might be considered a singleton. In Cairngorm, the ModelLocator is a singleton. Using the Cairngorm paradigm, most components refer to the ModelLocator for global application data, figuring out what the state of the application is. I really like the way that the Cairngorm ModelLocator can be used in conjunction with ViewStacks and binding to display screens to the user.
Problems with Singletons
So, a Singleton is a class that is a global data store. Why is this bad? In no particular order:
* In most Cairngorm applications I've seen, all components are referencing the ModelLocator. That means these components are not portable to other projects because they are specifically tied to the application. You are creating dependencies and there is no loose coupling.
* If the components you're building are not stand alone, then it becomes complicated, if not impossible, to perform unit tests on the component. This can lead to more time in development.
* Flex, or more appropriately, ActionScript 3, does not have private constructors. As such workarounds are used to prevent multiple instances of the class from being created. In the grand scheme of thing, this is a minor issue.
* Unless you write code to “Destroy” / get rid of the stuff you are storing in the ModelLocator, it will hang out there for as long as your application is in use. In some cases this is fine. In others it can be wasteful. Loading and storing too much data could lead to memory issues.
How can I code avoid singleton problems in Cairngorm?
So, how can you write Cairngorm applications in such a way that they avoid the problems associated with singletons? Well, in reality, you can't. but, you can try to organize your application to minimize the use of the ModelLocator; which is what I Try to do:
* Top Level Application: The Top level application is my main.mxml file; the one with the Application (or WindowedApplication in AIR) root. I think it's a pretty safe bet that this will be tightly coupled with the application, and reuse on this will be rare. I'm completely fine with that. As such, it's perfectly fine for my main.mxml to reference the ModelLocator. The Main.mxml often contains a ViewStack of the Application screens, but not much else.
* Second Tier Components: The second tier components are the main screen containers. I'll generally have one of these components for each 'viewstack' index in the main application. I figure it is okay if these are tied tightly to the application, and therefore they can access the ModelLocator.
* Everything Else: The second tier components will be made up of plenty of other components (Most likely using composition). Any level below the second tier, I try to make as loosely coupled as possible. Instead to referencing global data in the ModelLocator, I'll exchange data using parameters and events. In an ideal world, I'd only have 3 layers of components, but realistically these can trickle down much further just 3 layers.
I've found that implementing this use of the ModelLocator has given me a good balance of using “Global Variables” with loosely coupled components.
I wasn't ever planning on adding another entry into my Cairngorm series, but this has been stewing in my idea pool for a while, and tonight I felt inspired. While you're reading this, don't forget about the upcoming DotComIt focus group. Flex Developer's of all levels are welcome to attend.

赞(0) 打赏
分享到: 更多 (0)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏