|
Jun 15
|
If anyone has ever used Cairngorm in larger applications you probably have come across the popular problem of “ModelLocator.getInstance()” being sprinkled all over your app. And if you have ever researched your problem you have probably come across, more then you care to count, of heated discussions over the overuse of Singletons in Cairngorm. If you really want find reasons NOT to use Cairngorm just ask Theo. But, fortunately there are plenty of frameworks popping up for you to choose from Mate, Swiz, and PureMVC. I have tried all of them and currently Mate seems to be the new kid on the block and becoming more and more popular.
First off let me say I don’t dislike Mate I think it is a very robust framework I really enjoy the dependancy injection approach and the simplicity of using the built-in event system of Flash. But I have found that when implementing larger applications the EventMap can get very bulky and extremely complicated (especially when another developer has to jump into your code) also I just am not a fame of the tag-based system. Yes that is it! It is not because of some “proper instatiation of design pattern xxx” or improper use of MVC.
Back to the problem, I like Cairngorm and use it, but I don’t like the fact of the strict coupling between the ModelLocator and the View. To solve this problem I looked at Mate and how they delt with some of the issues, namely dependancy injection. I started researching a little and came across a few articles that spiked my interest one by Allen Manning and another by Steve Brownlee.
So how to fix this issue?
- Interfaces
- Local Models(similiar to Managers in Mate)
- AbstractCommand
- Dependancy Injection
First the LoginView.as:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | <?xml version="1.0" encoding="utf-8"?> <mx:TitleWindow xmlns:mx="http://www.adobe.com/2006/mxml"> <mx:Script> <![CDATA[ private function submitUser():void { } private function validateName():void { } ]]> </mx:Script> <mx:FormItem label="First Name:"> <mx:TextInput id="ti_fname" change="validateName()"/> </mx:FormItem> <mx:FormItem label="Last Name:"> <mx:TextInput id="ti_lname" change="validateName()"/> </mx:FormItem> <mx:ControlBar> <mx:Button label="Submit" click="submitUser()"/> </mx:ControlBar> </mx:TitleWindow> |
A very simple view nothing too special, but first, we are not going to “look” for the model instead we will inject it. The ILoginModel is an interface which only has getters and we will see why it exends the IEventDispatcher.
1 2 3 4 5 6 7 8 9 10 | package com.activepoison.cairngorm.view.model { import flash.events.IEventDispatcher; public interface ILoginViewModel extends IEventDispatcher { function get isValid():Boolean; function get loggedIn():Boolean; } } |
Now to create the concrete LoginViewModel notice that we have two public getters without setters and only expose one function updateLoginStatus:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | package com.activepoison.cairngorm.view.model { import flash.events.Event; import flash.events.EventDispatcher; import mx.events.ValidationResultEvent; public class LoginViewModel extends EventDispatcher implements ILoginViewModel { private var _isValid:Boolean = false; private var _loggedIn:Boolean = false; public function LoginViewModel():void { addEventListener(ValidationResultEvent.VALID, checkValidationHandler); addEventListener(ValidationResultEvent.INVALID, checkValidationHandler); } public function updateLoginStatus(value:Boolean):void { _loggedIn = value; dispatchEvent(new Event("loggedInChanged")); } private function checkValidationHandler(event:ValidationResultEvent):void { _isValid = event.type == ValidationResultEvent.VALID ? true : false; dispatchEvent(new Event("isValidChanged")); } [Bindable(event="isValidChanged")] public function get isValid():Boolean { return _isValid; } [Bindable(event="loggedInChanged")] public function get loggedIn():Boolean { return _loggedIn; } } } |
Next lets create an UserVO:
1 2 3 4 5 6 7 8 9 10 | package com.activepoison.cairngorm.vo { import com.adobe.cairngorm.vo.IValueObject; public class UserVO implements IValueObject { public var firstName:String; public var lastName:String; } } |
And LoginEvent:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | package com.activepoison.cairngorm.control { import com.activepoison.cairngorm.view.model.ILoginViewModel; import com.activepoison.cairngorm.vo.UserVO; import com.adobe.cairngorm.control.CairngormEvent; import flash.events.Event; public class LoginEvent extends CairngormEvent { public static const LOGIN:String = "loginEvent_Login"; public var userVO:UserVO = null; public function LoginEvent(type:String) { super(type); } override public function clone():Event { var evt:LoginEvent = new LoginEvent(type); evt.userVO = userVO; return evt; } } } |
Again we haven’t done anything different from here except we have created an interface for our model and this will be injected into our LoginView:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | <?xml version="1.0" encoding="utf-8"?> <mx:TitleWindow xmlns:mx="http://www.adobe.com/2006/mxml" visible="{!loginModel.loggedIn}"> <mx:Script> <![CDATA[ import mx.events.ValidationResultEvent; import com.activepoison.cairngorm.control.LoginEvent; import com.activepoison.cairngorm.vo.UserVO; import com.activepoison.cairngorm.view.model.ILoginViewModel; [Bindable] public var loginModel:ILoginViewModel; private function submitUser():void { var userVO:UserVO = new UserVO(); userVO.firstName = ti_fname.text; userVO.lastName = ti_lname.text; var evt:LoginEvent = new LoginEvent(LoginEvent.LOGIN); evt.userVO = userVO; evt.dispatch(); } private function validateName():void { if( ti_fname.text != "" && ti_lname.text != "" ) loginModel.dispatchEvent(new ValidationResultEvent(ValidationResultEvent.VALID)); else loginModel.dispatchEvent(new ValidationResultEvent(ValidationResultEvent.INVALID)); } ]]> </mx:Script> <mx:FormItem label="First Name:"> <mx:TextInput id="ti_fname" change="validateName()"/> </mx:FormItem> <mx:FormItem label="Last Name:"> <mx:TextInput id="ti_lname" change="validateName()"/> </mx:FormItem> <mx:ControlBar> <mx:Button label="Submit" click="submitUser()" enabled="{loginModel.isValid}"/> </mx:ControlBar> </mx:TitleWindow> |
As we can see we expose the ILoginViewModel to the LoginView and since we only expose the interface and not the concrete LoginViewModel the view only has the getters available. Now to me this seems to makes sense that the view can only get properties and not be able to set anything directly. But because our interface also extends IEventDispatcher we have to mediate any changes through to the concrete model first by dispatching events. Futhermore the view is completely independant and has no direct knowledge of the concrete model and on a sidenote – we got rid of the ugly AppModelLocator.getInstance().
On to how we inject the model into our view. Let’s create first the AppModelLocator:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | package com.activepoison.cairngorm.model { import com.activepoison.cairngorm.view.model.ILoginViewModel; import com.adobe.cairngorm.CairngormError; import com.adobe.cairngorm.CairngormMessageCodes; [Bindable] public class AppModelLocator { private static var instance:AppModelLocator; public function AppModelLocator() { if(instance!=null) { throw new CairngormError(CairngormMessageCodes.SINGLETON_EXCEPTION, "AppModelLocator"); } } public static function getInstance():AppModelLocator { if(instance==null) { instance = new AppModelLocator(); } return instance; } public var loginViewModel:ILoginViewModel; } } |
Again nothing special and the only difference is we use the interface over the concrete model for the loginViewModel. Now we just have to set our loginViewModel and inject that into our view.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:view="com.activepoison.cairngorm.view.*" layout="vertical" verticalAlign="middle" applicationComplete="init()"> <mx:Script> <![CDATA[ import com.activepoison.cairngorm.view.model.LoginViewModel; import com.activepoison.cairngorm.model.AppModelLocator; [Bindable] private var appModel:AppModelLocator = AppModelLocator.getInstance(); private function init():void { appModel.loginViewModel = new LoginViewModel(); } ]]> </mx:Script> <view:LoginView loginModel="{appModel.loginViewModel}" /> </mx:Application> |
So there we have it our view is finally injected with the model. But we are not done yet! Lets set up the LoginCommand, LoginDedelegate and AppController classes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | package com.activepoison.cairngorm.commands { import com.activepoison.cairngorm.business.LoginDelegate; import com.activepoison.cairngorm.control.LoginEvent; import com.activepoison.cairngorm.view.model.LoginViewModel; import com.adobe.cairngorm.commands.ICommand; import com.adobe.cairngorm.control.CairngormEvent; import mx.rpc.IResponder; public class LoginCommand implements IResponder, ICommand { private var model:LoginViewModel = AppModelLocator.getInstance().loginViewModel; public function execute(event:CairngormEvent):void { var delegate:LoginDelegate = new LoginDelegate(this); var evt:LoginEvent = event as LoginEvent; delegate.login(evt.userVO.firstName, evt.userVO.lastName); } public function result(data:Object):void { model.updateLoginStatus(true); } public function fault(info:Object):void { model.updateLoginStatus(false); } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | package com.activepoison.cairngorm.business { import mx.rpc.IResponder; public class LoginDelegate { private var responder:IResponder; public function LoginDelegate(responder:IResponder) { this.responder = responder; } public function login(firstName:String, lastName:String):void { if( firstName == "" || lastName == "" ) responder.fault("Invalid first or last name."); else responder.result(true); } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | package com.activepoison.cairngorm.control { import com.activepoison.cairngorm.commands.LoginCommand; import com.activepoison.cairngorm.model.AppModelLocator; import com.adobe.cairngorm.control.FrontController; public class AppController extends FrontController { public function AppController() { initializeCommands(); } private function initializeCommands():void { addCommand(LoginEvent.LOGIN, LoginCommand); } } } |
If you take a look at LoginCommand we have the ugly AppModelLocator.getInstance() senerio. At this point if you read the articles on using callback functions in the event you can get rid of the AppModelLocator.getInstance() in the LoginCommand but as I stated at the beginning of this article it doesn’t seem right to use callback functions but instead pass the model into the event. That is perfectly fine and I don’t see a real problem to this issue but I would rather have the command already know about what model it needs to update. The question becomes how do we do that? In the next article we will set up our AbstractCommand and monkey patch Cairngorm’s FrontController.
One Ping to “Cairngorm with Dependency Injection (Part 1)”
3 Responses to “Cairngorm with Dependency Injection (Part 1)”
-
1. John Says:
June 19th, 2009 at 10:27 amHi Poison,
I recently reviewed all four frameworks you mentioned. I’ve found Cairngorm lacking because its design seems to encourage developers to strongly couple application components. I have decided to use the Swiz framework for the first Flex project for my team. I see that you have used out Swiz before. Swiz does a good job at allowing code to be loosely coupled via autowiring and dependency injection. Is it possible that the use of Swiz framework, plus a MVC pattern similar to Cairngorm, will do a better job at simplifying your code?
Looking at your code, I’m wondering what you mean by dependency injection. The dependencies are still embedded in the code (in this case, the view). The dependency injection pattern means that the glue code (or code that specifies dependencies of each object) are moved to a central location and that the dependencies of the object graph is automatically maintained for you.
To be brief, the technique used by you is more like the Factory pattern, where a method defines the relationship of the objects in your framework.
Specifically, following code more or less defines your factory object/method :
private var appModel:AppModelLocator = AppModelLocator.getInstance()
…
appModel.loginViewModel = new LoginViewModel();Note that these few lines strongly couples your view (or Factory object) to the AppModel and the ILoginViewModel implementation.
I noticed your code has a very simple model, and your use of the init() method easily sets up everything you need.
ModelLocator -> ILoginViewModel
Contrast to your example, a dependency injection framework typically deals with more complex object graphs, for example:
ModelLocator
+ — ILoginViewModel
+ —- IUserModel
+ —- IUserPasswordModel
+ — IUserPreferenceModel
+ —- IUserAddressModel
+ —- CountryImpl
+ —- StateOrProvinceImpl
+ —- StreetAddressImpl
+ —- IUserBlogPerefencesModel
+ —- …
+ —- IUserOpenLoginPerefencesModel
+ —- …Imagine what your init() method will have to do to set up something like that!
I know, this is an extremely contrive example; the intend is to show you how complex the glue code can get. A dependency injection framework aims to simplify that kind of glue code, and allows you to define the dependencies in a central location. Swiz, Guice, and the Spring framework can give you an idea how dependencies are managed by the framework, instead of managed by code.
Not that there is anything wrong with the Factory pattern. However, just be aware the typical cons of the Factory pattern can apply to your technique. For example, if the Model has a complex object graph, your init() can get very complicated, and the developers working on this application may be tempted to have similar init() in other views (modules or other .mxml files), which puts the glue code in multiple places, making the code harder to organize.
A second opinion from me is that, based on my experience, the Model can typically be concrete; Models do not need dependency injection. This is because the _implementation_ of the Model typically doesn’t need to change. Where dependency injection comes in handy is at the business logic layer, where you may have multiple implementation of the same interface, or an interface where the implementation can change frequently. In a Flex application, what is more likely to change is how events are dispatched. As example, a complicated command with dependencies to multiple services (HttpService, RemoteObject, maybe a BlazeDS consumer/producer) is something where the dependencies should be managed by a framework, since the services associated with the command may change frequently due to changes in business rules.
-
2. Poison Says:
June 20th, 2009 at 5:51 pm@John
Yes Cairngorm does seem to “encourage” strong coupling between components. And I agree Swiz is a great framework and I enjoy it but I wanted to point out that with Cairngorm it is pretty simple to alleviate the pains of strongly typed coupling.
I guess I was moreso talking about using the dependency pattern and not necessarily setting up a whole dependency injection framework so “when a class satisfies its own dependencies it becomes inflexible with regards to these dependencies.” Meaning that by having a view look for its model by:
private var loginModel:LoginModel = AppModelLocator.getInstance().loginModel;
The view is now tightly coupled to the the “AppModelLocator” and is inflexible so if you have to change the dependencies you will have to change the code. But with some simple refactoring:
public var loginModel:ILoginModel;
Now the view no longer satisfies these dependencies itself and becomes completely independent and reuseable just by implementing an interface.( I didn’t mention it in my post but this is also extremely helpful when it comes to unit testing.)
As regards to using the Factory pattern and the “init()” function, in this particular post I just wanted to make it simple but it would be easy enough to set up an abstract factory pattern implementation.
And yes I agree the model can typically be concrete but I wanted to show that with interfaces you can use different parts of your model according to where it is implemented. Unfortunately Actionscript does not inherently support Abstract classes and we can only extend 1 class at a time, but we can implement multiple interfaces and interfaces can extend multiple interfaces.
-
3. John Says:
June 24th, 2009 at 2:06 pmHi Poison,
I see what you mean, your use of ILoginModel is simply an illustration that the use of interfaces is a good thing. I can’t disagree.
My main point was that your approach is not what most people would call “dependency injection”. When I came here, I was hoping to see a simple dependency injection framework – where I don’t have to implement a factory or abstract factory (or a “factory factory”) to manage dependencies.
June 15th, 2009 at 12:05 pm
[...] Cairngorm with Dependency Injection (Part 1) [...]