The architectural framework Cairngorm was created by Alistair McLeod and Steven Webster while they were working at the company iteration::two (they are presently employed by Adobe Consulting). Cairngorm implements several design patterns such as MVC, Command, and Delegate. It was open sourced in the summer of 2008.
Cairngorm was designed to ensure that UI components do not need to know where data is located. The business layer retrieves data from the servers and stores it in the memory objects that represent the data model, which use binding to notify the UI components about data arrival or changes. On the same note, changes in the UI are propagated to the server side through this business layer.
The Cairngorm framework promotes the use of the MVC design pattern in the client portion of your RIA. It offers a number of classes implementing Model, View, and Controller tiers, and interaction between them.
The Model tier is represented by the class ModelLocator
, which stores the
application-specific data (these are often collections of value
objects, a.k.a. data transfer objects).
ModelLocator
’s data is bound to the
View controls.
The View portion contains visual components required by your application, value objects, and Cairngorm-specific event classes used for communication with the Model and Controller tiers.
The Controller tier is responsible for invoking appropriate code
containing the business logic of your application, which is implemented by
using global FrontController
and
ServiceLocator
classes as well as
additional Command
and Delegate
classes.
The Cairngorm framework’s documentation and sample applications are located at http://www.cairngormdocs.org.
Note
As this chapter was being written, Adobe decided to rebrand Cairngorm; instead of a mere framework, Adobe is promoting it as a set of tools and methodologies containing various frameworks, including what has been earlier known as the “Cairngorm framework.” You can read about this Cairngorm 3 initiative at http://opensource.adobe.com/wiki/display/cairngorm/Cairngorm+3. In this chapter, we refer to Cairngorm 2, which is an MVC Flex framework and nothing else.
The “pure Flex” code shown in Example 1-1 includes
representatives of each MVC tier. The code knows that the data will be
loaded into an ArrayCollection
(the
Model) by the HTTP service pointing at the Employees.xml file by calling a send()
method on the creationComplete
event (the Controller) of the
application. The List
component (the
View) knows about its model and is bound to it directly via its dataProvider
property.
The data flow between Cairngorm components while displaying a list of Café employees is depicted in Figure 1-4.
The Cairngorm version of this application has the following six major participants:
- Services
The UI portion does not know about implementation of services and can’t call them directly, so you must move the
HTTPService
object into a special file called Services.mxml.FrontController
The View and the service layer can’t send events to each other directly, but rather have to be registered with a singleton
FrontController
that maps all application events to appropriate actions (commands).Command
When a
View
component fires an event,FrontController
finds theCommand
class that was registered with this event and calls its methodexecute()
.Delegate
The method
execute()
of theCommand
class creates an instance of theDelegate
class that knows which service to call (HTTPService
,RemoteObject
,WebService
) and returns the result or fault to theCommand
class.ModelLocator
The
Command
class updates the data in the model (typically, a collection of value objects) defined in the globalModelLocator
.View
Because each model located inside the
ModelLocator
is bound to a UI control, its content gets updated automatically.
Use the source code of the Café Townsend Multi-View Contact Management application that was converted to Cairngorm 2 by Darren Houle and is available under the Creative Commons license. You can download the source code of this application at http://cairngormdocs.org/blog/?p=19.
Figure 1-5 is a
screenshot of the Café Townsend Flash Builder project. Please note that
the code for the six participants mentioned earlier is organized in
separate packages (folders). The business folder is
for delegates and service components. The command folder is for
Command
classes; control is for events and FrontController
; the ModelLocator
is located in the
model folder; and the view folder has visual components as shown in
Figures 1-1 through 1-3. The value objects of the application
have been placed in the folder called vo. Regardless of what framework you are
going to use, separating various application components in project
subfolders helps make the project more organized.
To make Cairngorm classes available to your application, just download Cairngorm’s compiled version (binary) and add cairngorm.swc to the Library path of your Flex project (use the Flex Build Path menu under your project’s properties).
Let’s get familiar with the Cairngorm workflow by tracing the data
and events starting from the main application object of Café Townsend,
shown in Example 1-2.
Please note the use of four global objects: AppModelLocator
, Services
, AppController
, and CairngormEventDispatcher
.
Example 1-2. The application file of Café Townsend
<?xml version="1.0" encoding="utf-8" standalone="no"?> <!-- Cafe Townsend MVC Tutorial © 2006 Adobe Converted to Cairngorm 2 by Darren Houle lokka_@hotmail.com http://www.digimmersion.com This is released under a Creative Commons license. http://creativecommons.org/licenses/by/2.5/ --> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:business="com.adobe.cafetownsend.business.*" xmlns:control="com.adobe.cafetownsend.control.*" xmlns:view="com.adobe.cafetownsend.view.*" backgroundColor="#000000" creationComplete="loadEmployees();" layout="vertical viewSourceURL="srcview/index.html"> <mx:Script> <![CDATA[ import com.adobe.cairngorm.control.CairngormEventDispatcher; import com.adobe.cafetownsend.control.LoadEmployeesEvent; import com.adobe.cafetownsend.model.AppModelLocator; [Bindable] private var model: AppModelLocator = AppModelLocator.getInstance(); private function loadEmployees() : void { var cgEvent : LoadEmployeesEvent = new LoadEmployeesEvent(); CairngormEventDispatcher.getInstance().dispatchEvent(cgEvent); } ]]> </mx:Script> <business:Services id="services"/> <control:AppController id="appController"/> <mx:Style source="assets/main.css"/> <mx:Image source="assets/header.jpg" width="700"/> <mx:HBox backgroundColor="#ffffff" paddingBottom="10" paddingLeft="10" paddingRight="10" paddingTop="10" width="700"> <mx:VBox paddingRight="10" verticalScrollPolicy="off" width="100%"> <mx:ViewStack paddingBottom="10" paddingTop="10" resizeToContent="true" selectedIndex="{model.viewing}" width="100%"> <view:EmployeeLogin/> <view:EmployeeList/> <view:EmployeeDetail/> </mx:ViewStack> </mx:VBox> </mx:HBox> </mx:Application>
In the example code, CairngormEventDispatcher
dispatches the
cgEvent
:
CairngormEventDispatcher.getInstance().dispatchEvent(cgEvent);
Cairngorm’s front controller (AppController
) creates an instance of a
command class that was registered to process this event (see Example 1-4 later).
To eliminate the need to import CairngormEventDispatcher
in every view,
starting from Cairngorm 2.2 you can call the dispatch()
method on the event itself, which
uses CairngormEventDispatcher
internally, that is:
cgEvent.dispatch();
The three views of the Café Townsend application object are
implemented as components located in the ViewStack
container.
On the application startup, the code dispatches LoadEmployeesEvent
and, as if by magic, the
EmployeeList
gets populated from
Employees.xml. How did it happen?
LoadEmployeesEvent
is a subclass of
CairngormEvent
(Example 1-3).
Example 1-3. The class LoadEmployeesEvent
package com.adobe.cafetownsend.control { import com.adobe.cairngorm.control.CairngormEvent; import com.adobe.cafetownsend.control.AppController; public class LoadEmployeesEvent extends CairngormEvent { public function LoadEmployeesEvent() { super( AppController.LOAD_EMPLOYEES_EVENT ); } } }
This class creates an event with an ID AppController.LOAD_EMPLOYEES_EVENT
, which
among other events has been registered and mapped to the command
LoadEmployeesCommand
in the global
AppController
implementation shown in
Example 1-4.
Example 1-4. The AppController implementation
package com.adobe.cafetownsend.control { import com.adobe.cairngorm.control.FrontController; import com.adobe.cafetownsend.command.*; public class AppController extends FrontController { public static const LOAD_EMPLOYEES_EVENT : String = "LOAD_EMPLOYEES_EVENT"; public static const LOGIN_EMPLOYEE_EVENT : String = "LOGIN_EMPLOYEE_EVENT"; public static const ADD_NEW_EMPLOYEE_EVENT : String = "ADD_NEW_EMPLOYEE_EVENT"; public static const UPDATE_EMPLOYEE_EVENT : String = "UPDATE_EMPLOYEE_EVENT"; public static const LOGOUT_EVENT : String = "LOGOUT_EVENT"; public static const CANCEL_EMPLOYEE_EDITS_EVENT : String = "CANCEL_EMPLOYEE_EDITS_EVENT"; public static const DELETE_EMPLOYEE_EVENT : String = "DELETE_EMPLOYEE_EVENT"; public static const SAVE_EMPLOYEE_EDITS_EVENT : String = "SAVE_EMPLOYEE_EDITS_EVENT"; public function AppController() { addCommand( AppController.LOAD_EMPLOYEES_EVENT, LoadEmployeesCommand ); addCommand( AppController.LOGIN_EMPLOYEE_EVENT, LoginEmployeeCommand ); addCommand( AppController.ADD_NEW_EMPLOYEE_EVENT, AddNewEmployeeCommand ); addCommand( AppController.UPDATE_EMPLOYEE_EVENT, UpdateEmployeeCommand ); addCommand( AppController.LOGOUT_EVENT, LogoutCommand ); addCommand( AppController.CANCEL_EMPLOYEE_EDITS_EVENT, CancelEmployeeEditsCommand ); addCommand( AppController.DELETE_EMPLOYEE_EVENT, DeleteEmployeeCommand ); addCommand( AppController.SAVE_EMPLOYEE_EDITS_EVENT, SaveEmployeeEditsCommand ); } } }
The next point of interest is the class LoadEmployeesCommand
. This command class
implements the Command
implementation (Example 1-5), which forces you to implement
the method execute()
, which can
invoke the right delegate class that has the knowledge of “who to talk
to” when a specific command has been received. The method execute()
must have an argument—the
instance of the CairngormEvent
object
that may or may not encapsulate some application data (for example, some
value object that is not used in our scenario).
It also implements the interface IResponder
, which requires you to add the
result()
and fault()
methods. By using these callbacks the
delegate will return to the command class the result (or error
information) of the execution of the command in question.
Example 1-5. The Command implementation
package com.adobe.cafetownsend.command { import mx.rpc.IResponder; import com.adobe.cairngorm.commands.Command; import com.adobe.cairngorm.control.CairngormEvent; import com.adobe.cafetownsend.business.LoadEmployeesDelegate; import com.adobe.cafetownsend.model.AppModelLocator; public class LoadEmployeesCommand implements Command, IResponder { private var model : AppModelLocator = AppModelLocator.getInstance(); public function execute( cgEvent:CairngormEvent ) : void {// create a worker who will go get some data
// pass it a reference to this command so the delegate
// knows where to return the data
var delegate : LoadEmployeesDelegate = new LoadEmployeesDelegate(this);// make the delegate do some work
delegate.loadEmployeesService(); }// this is called when the delegate receives a result from the service
public function result( rpcEvent : Object ) : void {// populate the employee list in the model locator with
// the results from the service call
model.employeeListDP = rpcEvent.result.employees.employee; }// this is called when the delegate receives a fault from the service
public function fault( rpcEvent : Object ) : void {// store an error message in the model locator
// labels, alerts, etc. can bind to this to notify the user of errors
model.errorStatus = "Fault occured in LoadEmployeesCommand."; } } }
Because this version of the Café Townsend application uses the
HTTPService
request for retrieval,
Flex automatically converts Employees.xml into ArrayCollection
and does not use the value
object Employee.as. This leads to the need for
additional coding to convert the data to appropriate types. For example,
employee startDate
will be stored as
a string and will require code to convert it to Date
if any date manipulations will be needed.
If you’ll be using Cairngorm in your projects, consider
simplifying the application design by eliminating the delegate classes.
Just move the business logic from the delegate right into the execute()
method of the command class
itself.
Create a common ancestor to all your commands and define the fault method there to avoid repeating the same code in each command class.
To load the employees, the Command
class
creates an instance of the proper delegate passing the reference to
itself (this is how the delegate knows where to return the data) and
calls the method loadEmployeesService()
:
var delegate : LoadEmployeesDelegate = new LoadEmployeesDelegate(this); delegate.loadEmployeesService();
Have you noticed that the Command
class has
also reached for the AppModelLocator
to be able to update the model?
private var model : AppModelLocator = AppModelLocator.getInstance(); ... model.employeeListDP = rpcEvent.result.employees.employee; ... model.errorStatus = "Fault occured in LoadEmployeesCommand.";
Now, let’s take a peek into the Delegate
class
from Example 1-6. It gets a hold of
the global ServiceLocator
class, the
only player who knows about who’s hiding behind the mysterious name
loadEmployeesService
. The method
loadEmployeesService()
sends the
request to the execution and assigns the responder (the instance of
LoadEmployees
Command
), engaging the AsyncToken
design pattern described in Chapter 2.
Example 1-6. The Delegate implementation
package com.adobe.cafetownsend.business { import mx.rpc.AsyncToken; import mx.rpc.IResponder; import com.adobe.cairngorm.business.ServiceLocator; public class LoadEmployeesDelegate { private var command : IResponder; private var service : Object; public function LoadEmployeesDelegate( command : IResponder ) {//constructor will store a reference to the service we're going to call
this.service = ServiceLocator.getInstance().getHTTPService( 'loadEmployeesService' );// and store a reference to the command that created this delegate
this.command = command; } public function loadEmployeesService() : void {// call the service
var token:AsyncToken = service.send();// notify this command when the service call completes
token.addResponder( command ); } } }
As mentioned previously, each Cairngorm application has a central registry that knows about each and every service that may be used by the application (Example 1-7).
Example 1-7. The Services implementation
<?xml version="1.0" encoding="utf-8"?> <cairngorm:ServiceLocator xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:cairngorm="com.adobe.cairngorm.business.*"> <mx:HTTPService id="loadEmployeesService" url="assets/Employees.xml" /> </cairngorm:ServiceLocator>
In our case it’s just one HTTPService
, but in a real-world scenario, the
Services.mxml file may list dozens
of services. As every service must have a unique ID (in our case, it’s
loadEmployeesService
), the delegate
class was able to find it by using the following line:
this.service = ServiceLocator.getInstance().getHTTPService( 'loadEmployeesService' );
If you’d need to call a service implemented as RemoteObject
, the delegate would be calling
the method getRemoteObject()
instead
of getHTTPService()
. For web
services, call the method getWebService()
.
Those who work with Data Management Services can use Cairngorm’s
Enterprise
ServiceLocator
and its method
getDataService()
.
ServiceLocator
can be used not
only as a repository of all services, but also as an authorization
mechanism that restricts access to certain application services based on
specified credentials. See its methods setCredentials()
and setRemoteCredentials()
for details.
The final portion of the loading employees process goes as follows:
The
loadEmployeesService
class reads Employees.xmlThe delegate gets the result and passes it to the
result()
method of theCommand
class (see Example 1-5)The
Command
class updates themodel.employeeListDP
viaModelLocator
The
List
component on the View gets automatically updated, because it’s bound tomodel.employeeListDP
(see Example 1-8)
Example 1-8. The View: EmployeesList.mxml
<?xml version="1.0" encoding="utf-8"?> <mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml" xmlns="*" width="100%" horizontalAlign="center"> <mx:Script> <![CDATA[ import com.adobe.cairngorm.control.CairngormEventDispatcher; import com.adobe.cafetownsend.control.AddNewEmployeeEvent; import com.adobe.cafetownsend.control.UpdateEmployeeEvent; import com.adobe.cafetownsend.control.LogoutEvent; import com.adobe.cafetownsend.model.AppModelLocator; [Bindable] private var model : AppModelLocator = AppModelLocator.getInstance();// mutate the add new employee button's click event
public function addNewEmployee() : void {// broadcast a cairngorm event
var cgEvent : AddNewEmployeeEvent = new AddNewEmployeeEvent(); CairngormEventDispatcher.getInstance().dispatchEvent( cgEvent );//de-select the list item
clearSelectedEmployee(); }// mutate the List's change event
public function updateEmployee() : void {//broadcast a cairngorm event that contains selectedItem from the List
var cgEvent : UpdateEmployeeEvent = new UpdateEmployeeEvent( employees_li.selectedItem ); CairngormEventDispatcher.getInstance().dispatchEvent( cgEvent );// de-select the list item
clearSelectedEmployee(); }// mutate the logout button's click event
private function logout() : void {// broadcast a cairngorm event
var cgEvent : LogoutEvent = new LogoutEvent(); CairngormEventDispatcher.getInstance().dispatchEvent( cgEvent ); }// format the names that are displayed in the List
public function properName( dpItem : Object ) : String { return dpItem.lastname + ", " + dpItem.firstname; }// de-select any selected List items
private function clearSelectedEmployee() : void { employees_li.selectedIndex = -1; } ]]> </mx:Script> <mx:Panel title="Employee List" horizontalCenter="0"> <mx:HBox paddingTop="25"> <mx:Button label="Add New Employee" click="addNewEmployee()" /> <mx:Spacer width="100%" /> <mx:Button label="Logout" click="logout()" /> <mx:Spacer width="100%" height="20" /> </mx:HBox> <!-- data provider for the list is an ArrayCollection stored in the centralized model locator --> <mx:List id="employees_li" dataProvider="{ model.employeeListDP }" labelFunction="properName" change="updateEmployee()" width="100%" verticalScrollPolicy="auto"/> </mx:Panel> </mx:VBox>
We’re almost there, but let’s not forget about the ModelLocator
, the storage of your
application’s data. At the time of this writing, the code of the Café
Townsend application published at http://cairngormdocs.org still implements the ModelLocator
interface, but recently has been renamed
IModelLocator
.
In Example 1-9 the
class AppModelLocator
implements
IModelLocator
.
Example 1-9. The ModelLocator of Café Townsend Cairngorm
package com.adobe.cafetownsend.model { import mx.collections.ArrayCollection; import com.adobe.cairngorm.model.ModelLocator; import com.adobe.cafetownsend.vo.Employee; import com.adobe.cafetownsend.vo.User; [Bindable] public class AppModelLocator implements ModelLocator {// this instance stores a static reference to itself
private static var model : AppModelLocator;// available values for the main viewstack
// defined as constants to help uncover errors at compile time
public static const EMPLOYEE_LOGIN : Number = 0; public static const EMPLOYEE_LIST : Number = 1; public static const EMPLOYEE_DETAIL : Number = 2;// viewstack starts out on the login screen
public var viewing : Number = EMPLOYEE_LOGIN;// user object contains uid/passwd
// its value gets set at login and cleared at logout but nothing
// binds to it or uses it retained since it was used in the
// original Adobe CafeTownsend example app
public var user : User;// variable to store error messages from the httpservice
// nothing currently binds to it, but an Alert or the login box
// could to show startup errors
public var errorStatus : String;// contains the main employee list, which is populated on startup
// mx:application's creationComplete event is mutated into a
// cairngorm event that calls the httpservice for the data
public var employeeListDP : ArrayCollection;// temp holding space for employees we're creating or editing
// this gets copied into or added onto the main employee list
public var employeeTemp : Employee;// singleton: constructor only allows one model locator
public function AppLocator(){ if ( AppModelLocator.model != null ) throw new Error( "Only one ModelLocator instance should be instantiated" ); }// singleton always returns the only existing instance to itself
public static function getInstance() : AppModelLocator { if ( model == null ) model = new AppModelLocator(); return model; } } }
This model locator stores the data and the state of this
application—in particular, the variable employeeListDP
, which is the place where the
list of employees is being stored.
Please note that as ActionScript 3 does not support private
constructors, the public constructor of this class throws an error if
someone tries to improperly instantiate it (i.e., using the
new
command) but the instance of this object already
exists.
We went through the entire process of displaying the initial list of employees, but just to ensure that the Cairngorm data flow is clear, we’ll include a brief explanation of yet another use case from Café Townsend.
The user presses the Add New Employee button (see Figure 1-2), enters the detail info for a new employee on the View component shown in Figure 1-3, and presses the Submit button. This is what’s happening between this button click and the moment when the new employee appears in the employee list:
Note
If you want to follow along, please download the source code of Café Townsend and start from EmployeeDetail.mxml on the following line:
<mx:Button label="Submit" click="saveEmployeeEdits()" id="submit" />
The
SaveEmployeeEditsEvent
event is dispatched:var cgEvent : SaveEmployeeEditsEvent = new SaveEmployeeEditsEvent(model.employeeTemp.emp_id, firstname.text, lastname.text,startdate.selectedDate, email.text ); CairngormEventDispatcher.getInstance().dispatchEvent( cgEvent );
For some reason, the author of this code decided not to use
EmployeeVO
here and stores eachEmployee
attribute separately inSaveEmployeeEvent
. This is not the best way of encapsulating data inside a custom event, but let’s keep the original code intact.The
FrontController
receives this event and passes it to the registered commandSaveEmployeeEditsCommand
(see Example 1-4 earlier) for execution.The
execute()
method ofSaveEmployeeEditsCommand
does not use any delegates, as it just needs to add a newly insertedEmployee
to the model. Because this application does not save modified data anywhere other than in memory, no other service calls are made to pass the changed data to the server side for persistence.The View portion of the employee list gets updated automatically as a result of data binding.
While planning for your application with Cairngorm, think of all events, services, value objects, and business services and then create appropriate classes similarly to the way it was done in the Café Townsend example.
Online, you may encounter lots of debate regarding whether Cairngorm should be used in Flex projects. With all due respect to the creators of Cairngorm, we don’t believe that Cairngorm makes a Flex team more productive and that most enterprise projects would not benefit from it. We prefer working with frameworks that offer enhanced Flex components rather than just separation of work among team members. If you have to develop a project without experienced Flex developers on your team, however, Cairngorm can give your project a structure that will prevent it from failing.
So, is Cairngorm right for your project? Read Chapters 2, and 6, and then decide whether you prefer working with the components described there or one of the architectural MVC frameworks. Meanwhile, keep these observations about Cairngorm in mind:
Cairngorm’s architecture is based on components dispatching events to a global event handler without knowing what the latter will do with them. The problem with this approach is in the global nature of such an event handler. The
Front
Controller
object serves as a central registry of all Cairngorm events. Although keeping all application events in one place simplifies their maintenance, it leads to tighter coupling of the application components.Using a centralized
ModelLocator
also makes multiple components dependent on the knowledge of the properties of the model. If your project will start growing, theModelLocator
may not scale well.Modularizing Flex applications is one of the major ways of minimizing the size of the downloadable Shockwave Flash (SWF) files. The other benefit is reusability of the modules. Now imagine a midsize web application that consists of 10 modules. If this application has been built using Cairngorm, each of these modules becomes dependent on the central
FrontController
located in the main .swf file.Application developers have to write lots of boilerplate code. For example, you have to create additional event and command classes for every event that can be dispatched in your application. Even in a midsize application this can translate to a hundred or more additional Cairngorm-specific classes. To minimize the amount of manually written code, consider using Cairngen, an open source code generator for Cairngorm. It’s available at http://code.google.com/p/cairngen/.
FrontController
allows you to map only one command per event, yet your application may need to have several event listeners per command.Even though data binding can help in writing less code, because Cairngorm enforces data binding as the only mechanism of updating the views, it makes them nonreusable. For example, you can’t just simply reuse the EmployeeList.mxml from Example 1-8 in another application, because it has an intimate knowledge of the internals of the model and relies on the fact that the model has a public variable
employeeListDP
. Just simply renaming this variable in theModelLocator
will require changes in one or more views that are bound to it.Having no other choice but data binding for updating the UI may cause performance problems. The global
ModelLocator
object defines multiple bindable variables representing different models, and the Flex compiler may generate additionalEventDispatcher
objects on the class level (this depends on the types of the variables). Suppose you have 10[Bindable] String
variables in theModelLocator
. If one of them will get updated, not only will its listener get notified to update the view, but the other 9 will get this event, too.The fact that Cairngorm is built around a Command pattern with a centrally located command repository can be very convenient for some projects that require audit or undo functionality. Every command arrives at the same place, and you can conditionally hook up, say, an undo module that remembers old/new states of some data or logs every user request (this can be a must in some financial trading applications).
Cairngorm has been around longer than any other Flex framework. As of today, it’s the most popular framework, and many Flex developers around the world already know it, which may be an important factor for development managers who put together large project teams, especially when the teams consist of a large number of junior Flex developers.
Cairngorm separates business- and UI-related work into different layers, which means that the work of the project team can be split between developers responsible for the visual portion and those who are coding just the business logic of the application. The fact that all services are located in a central place allows us to quickly reconfigure the data sources, i.e., switch to quality assurance (QA) or production servers.
Development managers who have to work with distributed teams of beginner or mid-level Flex developers and need a safety net to split the project work into smaller controllable tasks (e.g., John works on the server side and Srinivas works only on the views) may consider using Cairngorm. Here’s the report card followed by more detailed explanations.
The pros are:
It’s a popular framework—many Flex developers know it.
It allows separate responsibilities of developers.
It lowers the requirements for developers’ skillsets.
The cons are:
It requires developers to write lots of additional classes, which adds to project timeline.
It’s built on global singletons, which complicates modularization.
It allows only one-to-one mapping between events and commands.
The framework design is based on singletons, which leads to tight object coupling.
Get Agile Enterprise Application Development with Flex now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.