My original post on Cairngorm and IResponder had accidentally been deleted while updating my moderations queue, and many of you have contacted me stating that the post is no longer available so I will re-iterate what I mentioned in that post.
For some time now I have been entertaining the notion of abstracting IResponder implementations from Command classes into separate, discreet classes which are intended to handle asynchronous service invocation responses. There has been some talk in the Cairngorm community recently regarding Cairngorm Commands and IResponder implementations so I thought I would share my thoughts on the subject.
Typically most Cairngorm Events are handled by an associated Command. The Command handles the Event by updating a model on the ModelLocator, and / or instantiating a Business Delegate to manage invoking a middle-tier service.
At this point one could argue that the Command has finished doing it’s job – handling the Event. Let’s clarify by taking a look at a formal definition of the Command Pattern:
“The Command pattern is a design pattern in which objects are used to represent actions. A command object encapsulates an action and its parameters.”
From this we can deduce (in the context of a Cairngorm Event) that the Event is the “action” and the Command is the object which encapsulates the handling of the Event (action). The actual handling of the Service response could be considered a separate concern which is outside of the direct concern of the Event and Command, thus requiring an additional object to handle the service response.
However for many developers it is (by design) typical to simply have the Command implement IResponder and also handle the response from the service in addition to the actual Event. This makes sense from a convenience perspective, but not necessarily from a design perspective.
What I have been experimenting with is pretty simple and straightforward. It involves having a completely separate object implement IResponder and handle the service response directly.
Consider the following example in which a specific use-case requires an account log in (could the Login Example have replaced Hello World?). The following classes would be required: LoginEvent, LoginCommand, LoginResponder and LoginDelegate. Utilizing a separate class as the responder is very simple and straightforward and would be implemented as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
package events { import com.adobe.cairngorm.events.CairngormEvent; import vo.LoginVO; public class LoginEvent extends CairngormEvent { public static const LOGIN_EVENT:String="events.LoginEvent"; public var loginVO:LoginVO; public function LoginEvent(loginVO:LoginVO) { super ( LOGIN_EVENT ); this.loginVO= loginVO; } } } |
So far nothing different here, the above Event is just like any other CairngormEvent. Now let’s take a look at the Command implementation.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
package commands { import com.adobe.cairngorm.commands.ICommand; import com.adobe.cairngorm.events.CairngormEvent; import events.LoginEvent; public class LoginCommand implements ICommand { public function execute(event:CairngormEvent) { var evt:LoginEvent = event as LoginEvent; var re:IResponder = new LoginResponder(); var delegate:LoginDelegate = new LoginDelegate(re); delegate.login(evt.loginVO.username, evt.loginVO.password); } } } |
Based on the above example, the only method which must be implemented is execute(), as defined by ICommand. The implementation of execute() instantiates an instance of LoginResponder and LoginDelegate, the LoginResponder instance is passed to the LoginDelegate as the IResponder instance (as opposed to the traditional approach of the Command being passed).
As can be seen in the following example, the Business Delegate implementation is the same as any other typical Cairngorm Delegate:
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
|
package business { import com.adobe.cairngorm.business.ServiceLocator; import mx.rpc.AsyncToken; import mx.rpc.IResponder; import mx.rpc.http.HTTPService; import vo.LoginVO; public class LoginDelegate { private var responder:IResponder; private var service:HTTPService; public function LoginDelegate(responder:IResponder) { this.responder = responder; var sl:ServiceLocator = ServiceLocator.getInstance(); service = sl.getHTTPService( Services.LOGIN_SERVICE ); } public function Login(vo:LoginVO) : void { var call:AsyncToken=service.send(vo.username,vo.password); call.addResponder( responder ); } public function Logout() : void { //... } } } |
The IResponder implementation would be as follows:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
package responders { import mx.rpc.IResponder; public class LoginResponder implements IResponder { public function result(data:Object) { // result implementation... } public function fault(info:Object) { // fault implementation... } } } |
That’s pretty much it. Clean, simple, and yes, more code, however this design supports a clear separation of concerns and promotes encapsulation and code reusability. This example is intentionally cut and dry, however if you consider the many other possible implementations such as utilizing Dependency Injection to inject both delegates and responders, than I believe the benefits become quite clear.
At the end of the day it really comes down to personal preference. For me, I always prefer to have more classes which encapsulate very specific concerns and responsibilities. As long as you have a clear and concise, but most of all, consistent design you usually can’t go wrong.