├── best-practices-en.textile ├── best-practices-en ├── 0_toc.textile ├── 1_what_is_robotlegs.textile ├── 2_dependency_injection.textile ├── 3_0_injection.textile ├── 3_1_injecting_with_the_injector.textile ├── 3_2_injecting_via_MediatorMap.textile ├── 3_3_injecting_with_CommandMap.textile ├── 4_context.textile ├── 5_mvcs_overview.textile ├── 6_commands.textile ├── 7_mediators.textile ├── 8_models.textile └── 9_services.textile ├── best-practices-zh-cn.textile ├── best-practices-zh-cn ├── 0_toc.textile ├── 1_what_is_robotlegs.textile ├── 2_dependency_injection.textile ├── 3_0_injection.textile ├── 3_1_injecting_with_the_injector.textile ├── 3_2_injecting_via_MediatorMap.textile ├── 3_3_injecting_with_CommandMap.textile ├── 4_context.textile ├── 5_mvcs_overview.textile ├── 6_commands.textile ├── 7_mediators.textile ├── 8_models.textile └── 9_services.textile └── scripts └── build.sh /best-practices-en.textile: -------------------------------------------------------------------------------- 1 | 2 |
!=http://joelhooks.com/wp-content/uploads/2009/07/robotlegssketchsmall.gif!

Documentation for Robotlegs v1.0RC1

3 | h2. Table of Contents 4 | 5 | # *"What is Robotlegs":#whatisrobotlegs* 6 | # "Dependency Injection":#dependencyinjection 7 | # "Using Injectors":#usingtheinjectors 8 | ** "Injection Syntax for the SwiftSuspenders Adapter":#injectionsyntax 9 | ** "Injection Mapping with the Injector Class":#mappingwithinjector 10 | ** "Injection Mapping with the MediatorMap Class":#mappingwithmediatormap 11 | ** "Injection Mapping with the CommandMap Class":#mappingwithcommandmap 12 | # "The Context":#thecontext 13 | # *"MVCS Reference Implementation":#mvcs* 14 | ## "Context":#context 15 | ## "Controller & Commands":#controllersandcommands 16 | ## "View & Mediators":#viewandmediators 17 | ## "Model, Service and the Actor":#modelandservice 18 | ## "Model":#modelandmodels 19 | ## "Service":#serviceandservices 20 | ## "Framework Events":#frameworkevents 21 | ## *"Commands":#commands* 22 | ### "Command Responsibilities":#commandresponsibilities 23 | ### "Triggering Commands":#triggeringcommands 24 | ### "Chaining Commands":#chainingcommands 25 | ### "Decoupling Application Tiers":#decouplingtiers 26 | ## *"Mediators":#mediators* 27 | ### "Mediator Responsibilities":#mediatorresponsibilities 28 | ### "Mapping a Mediator":#mappingamediator 29 | ### "Automatic Mediation of View Components":#automaticmediation 30 | ### "Manually Mediation of View Components":#manualmediation 31 | ### "Mapping the Main Application (_contextView_) Mediator":#mappingthecontextview 32 | ### "Accessing a Mediator's View Component":#accessingmediatorviewcomponent 33 | ### "Adding Event Listeners to a Mediator":#addingeventlistenerstomediators 34 | ### "Listening for Framework Events":#mediatorslisteningfroframeworkevents 35 | ### "Dispatching Framework Events":#Mediatorsdispatchingframeworkevents 36 | ### "Listening for View Component Events":#mediatorslisteningforcomponentevents 37 | ### "Accessing Models and Services via a Mediator":#accessingmodelsandservicesfrommediators 38 | ### "Accessing Other Mediators":#accessingothermediatorsfromamediator 39 | ## *"Models":#models* 40 | ### "Model Responsibilities":#modelresponsibilities 41 | ### "Mapping a Model":#mappingamodel 42 | ### "Dispatching Events from a Model":#dispatchingeventsfrommodel 43 | ### "Listening for Framework Events in a Model":#listeningforeventsinmodel 44 | ## *"Services":#services* 45 | ### "Service Responsibilities":#serviceresponsibilities 46 | ### "Mapping a Service":#mappingservice 47 | ### "Listening for Framework Events in a Service":#servicelisteningforframeworkevents 48 | ### "Dispatching Framework Events":#servicedispatchingframeworkevents 49 | ### "Example Service":#serviceexample 50 | **** "Services Should Implement an Interface":#serviceimplementsinterface 51 | **** "Parsing Data in a Service":#parsingresultsinservice 52 | **** "Service Events":#serviceevents 53 | 54 | 55 | h2(#whatisrobotlegs). What is Robotlegs 56 | 57 | Robotlegs is a pure AS3 micro-architecture (framework) for developing Flash, Flex, and AIR applications. Robotlegs is narrowly focused on wiring application tiers together and providing a mechanism by which they communicate. Robotlegs seeks to speed up development while providing a time tested architectural solution to common development problems. Robotlegs is not interested in locking you into the framework, your classes are just that, your classes, and should be easily transferable to other frameworks should the need or desire to do so arise in the future. 58 | 59 | The framework supplies a default implementation based on the "Model-View-Controller":http://en.wikipedia.org/wiki/Model–view–controller meta-design pattern. This implementation provides a strong suggestion as to application structure and design. While it does make your application slightly less portable, it still aims to be as minimally invasive as possible in your concrete classes. By extending the "MVCS":#mvcs implementation classes, you are supplied with numerous methods and properties for the sake of convenience. 60 | 61 | You are never obligated to use the standard "MVCS":#mvcs implementation with Robotlegs. You can use any part of it, none of it, or freely roll your own implementation to suit your needs. It is included to provide a proper reference implementation and a jump start to using Robotlegs. 62 | 63 | 64 | h2(#dependencyinjection). Dependency Injection 65 | 66 | Robotlegs revolves around the "Dependency Injection":http://www.insideria.com/2009/09/as3-dependency-injection-demys.html design pattern. 67 | 68 | bq. At the simplest, Dependency Injection is that act of supplying objects with their instance variables or properties. When you pass a variable to the constructor of a class, you are using Dependency Injection. When you set a property on a class, you are using Dependency Injection. If you aren't coding your AS3 in a strictly procedural or linear fashion, the odds are that you are making use of Dependency Injection right now. 69 | 70 | Robotlegs uses automated, metadata based Dependency Injection. This is provided as a convenience for the developer and has the advantage of greatly reducing the amount of code needed to wire together an application and provide classes with their necessary dependencies. While it is fully possible to supply these dependencies to your classes manually, allowing the framework to perform these duties reduces the chances for error and generally speeds up the coding process. 71 | 72 | 73 | h2(#usingtheinjectors). Using Injectors 74 | 75 | Robotlegs provides an adapter mechanism for providing a dependency injection mechanism to the framework. By default, the framework is equipped with the "SwiftSuspenders":http://github.com/tschneidereit/SwiftSuspenders injection/reflection library to serve this purpose. Additional adapters are available for SmartyPants-IoC and Spring Actionscript. There can potentially be specific reasons to use another dependency injection adapter, but if you don't have a specific reason for doing so, it is recommended that you use the default "SwiftSuspenders":http://github.com/tschneidereit/SwiftSuspenders as it is performance tuned specifically for Robotlegs. 76 | 77 | h3(#injectionsyntax). Injection Syntax for the SwiftSuspenders Adapter 78 | 79 | SwiftSuspenders supports three types of dependency injection. 80 | 81 | * Property (field) Injection 82 | * Parameter (method/setter) Injection 83 | * Constructor Injection 84 | 85 | For the purposes of this document, we are going to examine Property injection, and how this is used within Robotlegs. There are two options for injecting properties into your class. You can use unnamed, or named injection: 86 | 87 |
[Inject]
 88 | public var myDependency:Depedency; //unnamed injection
89 | 90 |
[Inject(name="myNamedDependency")]
 91 | public var myNamedDependency:NamedDepedency; //named injection
92 | 93 | Injection mappings are supplied to Robotlegs in three places. The MediatorMap, the CommandMap, and through the Injector directly. Both the MediatorMap and the CommandMap are making use of the Injector as well, but they are doing additional work required by these tiers. As the names imply, MediatorMap is used for mapping Mediators, CommandMap is used for mapping Commands, and anything else that needs to be injected (including but not limited to Models) is mapped directly with the Injector. 94 | 95 | 96 | h3(#mappingwithinjector). Injection Mapping with the Injector Class 97 | 98 | The adapters for concrete Injector classes conform to the IInjector interface. This interface provides a consistent API for injection, irrespective of the dependency injection solution provided. This document focuses on SwiftSuspenders, but this syntax is true for any Injector that conforms to the IInjector interface. 99 | 100 | The _injector_ is the workhorse of all of the dependency injection that happens in your application. It is used for injecting framework actors, but can also be used for any other injections that your application might need. This includes, but is not limited to RemoteObjects, HTTPServices, factory classes, or virtually ANY class/instance that you might need as dependencies for your your application objects. 101 | 102 | Below are the four mapping methods that are provided with classes that implement IInjector: 103 | 104 | h4. mapValue 105 | 106 | mapValue is used to map a specific instance of an object to an injector. When asked for a specific class, use this specific instance of the class for injection. 107 | 108 |
//someplace in your application where mapping/configuration occurs
109 | var myClassInstance:MyClass = new MyClass();
110 | injector.mapValue(MyClass, myClassInstance);
111 | 112 | 113 |
//in the class to receive injections
114 | [Inject]
115 | public var myClassInstance:MyClass
116 | 117 |
mapValue(whenAskedFor:Class, instantiateClass:Class, named:String = null)
118 | 119 | The instance of MyClass is created and is held waiting to be injected when requested. When it is requested, that instance is used to fill the injection request. It is important to note that since you have manually created a class instance and mapped it with mapValue, the instance will not have it's dependencies injected automatically. You will need to inject the dependencies manually or via the injector: 120 | 121 |
injector.injectInto(myClassInstance);
122 | 123 | This will provide the instance with its mapped injectable properties immediately. 124 | 125 | h4. mapClass 126 | 127 | mapClass provides a _unique_ instance of the mapped class for each injection request. 128 | 129 |
//someplace in your application where mapping/configuration occurs
130 | injector.mapClass(MyClass);
131 | 132 | 133 |
//in the first class to receive injections
134 | [Inject]
135 | public var myClassInstance:MyClass
136 | 137 | 138 |
//in the second class to receive injections
139 | [Inject]
140 | public var myClassInstance:MyClass
141 | 142 | Each of the injections above will provide a _unique_ instance of MyClass to fulfill the request. 143 | 144 |
mapClass(whenAskedFor:Class, named:String = null)
145 | 146 | The injector provides a method for instantiating mapped objects: 147 | 148 |
injector.mapClass(MyClass);
149 | var myClassInstance:MyClass = injector.instantiate(MyClass);
150 | 151 | This provides an instance of your object and all mapped injection points contained in the object are filled. 152 | 153 | h4. mapSingleton 154 | 155 | mapSingleton provides a _single_ instance of the requested class for every injection. Providing a single instance of a class across all injections ensures that you maintain a consistent state and don't create unnecessary instances of the injected class. This is a managed single instance, enforced by the framework, and not a Singleton enforced within the class itself. 156 | 157 |
//someplace in your application where mapping/configuration occurs
158 | injector.mapSingleton(MyClass);
159 | 160 | 161 |
//in the first class to receive injections
162 | [Inject]
163 | public var myClassInstance:MyClass
164 | 165 | 166 |
//in the second class to receive injections
167 | [Inject]
168 | public var myClassInstance:MyClass
169 | 170 | In the above example, both injections requests will be filled with the same instance of the requested class. _This injection is deferred_, meaning the object is not instantiated until it is first requested. 171 | 172 |
mapSingletonOf(whenAskedFor:Class, useSingletonOf:Class, named:String = null)
173 | 174 | h4. mapSingletonOf 175 | 176 | mapSingletonOf is much like mapSingleton in functionality. It is useful for mapping abstract classes and interfaces, where mapSingleton is for mapping concrete class implementations. 177 | 178 |
//someplace in your application where mapping/configuration occurs
179 | injector.mapSingletonOf(IMyClass, MyClass); //MyClass implements IMyClass
180 | 181 |
//in the first class to receive injections
182 | [Inject]
183 | public var myClassInstance:IMyClass
184 | 185 | 186 |
//in the second class to receive injections
187 | [Inject]
188 | public var myClassInstance:IMyClass
189 | 190 | This injection method is useful for creating classes that are more testable and can take advantage of polymorphism. An example of this can be found below in the "Example Service":#serviceimplementsinterface section. 191 | 192 | 193 | h3(#mappingwithmediatormap). Injection Mapping with the MediatorMap Class 194 | 195 | The MediatorMap class implements IMediatorMap, which provides two methods for mapping your mediators to views and registering them for injection. 196 | 197 |
mapView(viewClassOrName:*, mediatorClass:Class, injectViewAs:Class = null, autoCreate:Boolean = true, autoRemove:Boolean = true):void
198 | 199 | *mapView* accepts a view class, MyAwesomeWidget, or a fully qualified class name for a view, _com.me.app.view.components::MyAwesomeWidget_ as the first parameter. The second parameter is the Mediator class that will mediate the view component. *[NEED TO PUT IN injectAsView]* The last two parameters autoCreate and autoRemove are boolean switches that provide convenient automatic mediator management. 200 | 201 |
//someplace in your application where mapping/configuration occurs
202 | mediatorMap.mapView(MyAwesomeWidget, MyAwesomeWidgetMediator); 
203 | 204 |
//somewhere inside of the contextView's display list
205 | var myAwesomeWidget:MyAwesomeWidget = new MyAwesomeWidget();
206 | this.addChild(myAwesomeWidget); //the ADDED_TO_STAGE event is dispatched, which triggers the view component to be mediated
207 | 208 | This approach utilizes the automated mediation. Manual mediation, and a more in-depth look at this process will be covered later in the "Mediators":#mediators section. 209 | 210 | 211 | 212 | h3(#mappingwithcommandmap). Injection Mapping with the CommandMap Class 213 | 214 | The CommandMap class implements ICommandMap, which provides one method for mapping commands to framework events that trigger them. 215 | 216 |
mapEvent(eventType:String, commandClass:Class, eventClass:Class = null, oneshot:Boolean = false)
217 | 218 | You will provide the commandMap with a class to execute, the type of event that executes it, and optionally a strong typing for the event and a boolean switch if the command should be executed only a single time and then be unmapped. 219 | 220 | The strongly typed event class optional parameter is used as extra protection against the Flash platforms "magic string" event type system. This will prevent any conflict between events that might have the same String type, but are actually types of different event classes. 221 | 222 |
//someplace in your application where mapping/configuration occurs
223 | commandMap.mapEvent(MyAppDataEvent.DATA_WAS_RECEIVED, MyCoolCommand, MyAppDataEvent); 
224 | 225 | 226 |
//in another framework actor an event is dispatched
227 | //this triggers the mapped command which is subsequently executed
228 | dispatch(new MyAppDataEvent(MyAppDataEvent.DATA_WAS_RECEIVED, someTypedPayload))
229 | 230 | 231 | h2(#thecontext). The Context 232 | 233 | At the heart of any Robotlegs implementation lies the Context. The Context, or Contexts as the case may be, provides the mechanism by which any given implementation's tiers will communicate. An application is by no means limited to a single Context, but for many use cases one Context is sufficient. With the ability to build modular applications on the Flash platform, you will see circumstances where multiple Contexts are necessary. The Context has three functions within an application: provide initialization, provide de-initialization, and provide the central event bus for communication. 234 | 235 |
package org.robotlegs.examples.bootstrap
236 | {
237 | 	import flash.display.DisplayObjectContainer;
238 | 	
239 | 	import org.robotlegs.base.ContextEvent;
240 | 	import org.robotlegs.core.IContext;
241 | 	import org.robotlegs.mvcs.Context;
242 | 	
243 | 	public class ExampleContext extends Context implements IContext
244 | 	{
245 | 		public function UnionChatContext(contextView:DisplayObjectContainer)
246 | 		{
247 | 			super(contextView);
248 | 		}
249 | 		
250 | 		override public function startup():void
251 | 		{
252 | 			//This Context is mapping a single command to the ContextEvent.STARTUP
253 | 			//The StartupCommand will map additional commands, mediators, services,
254 | 			//and models for use in the application.
255 | 			commandMap.mapEvent( ContextEvent.STARTUP, StartupCommand, ContextEvent, true );
256 | 						
257 | 			//Start the Application (triggers the StartupCommand)
258 | 			dispatch(new ContextEvent(ContextEvent.STARTUP));
259 | 		}
260 | 	}
261 | }
262 | 263 | 264 | h2(#mvcs). MVCS Reference Implementation 265 | 266 | Robotlegs is equipped with a reference implementation. This implementation follows the classic meta-design pattern known as Model-View-Controller (MVC), with the addition of a fourth actor called Service. These tiers, throughout this document, are referred to as the "Core actors," or simply "actors." 267 | 268 | MVCS provides an architectural overview of an application. By combining several time tested design patterns into a concrete implementation, the Robotlegs MVCS implementation can be used as a consistent approach for building your applications. By approaching an application with these architectural concepts you are able to have many common obstacles removed prior to even starting your design: 269 | 270 | * Separation 271 | * Organization 272 | * Decoupling 273 | 274 | h4. Separation 275 | 276 | MVCS provides a natural way for separating your application into discrete layers that provide specific functionality. The view layer handles interaction with the user. The model layer handles the data that is retrieved from external sources or created by the user. The controller tier provides a mechanism for encapsulating complex interaction between the tiers. Finally, the service layer provides an isolated mechanism for communicating with entities outside of the application such as remote service APIs or the file system. 277 | 278 | h4. Organization 279 | 280 | Through this separation we naturally achieve a level of organization. Every project requires some level of organization. Yes, one could toss all of their classes into the root package and call it a day, but this is unrealistic on even the smallest project. When a project is of any non-trivial size it becomes necessary to start organizing the structure of the class files. This need becomes even more acute as a project adds team members contributing to the same application. The Robotlegs MVCS implementation describes an organizational structure for projects neatly divided into the four tiers. 281 | 282 | h4. Decoupling 283 | 284 | The Robotlegs MVCS implementation promotes the decoupling of the four application tiers. Each tier is isolated from the rest, making it much easier to isolate classes and components for testing. In addition to easing the testing process, this also frequently allows for portable classes that can be reused in additional projects. For example, a Service class that connects to a remote API might b useful in several applications. By decoupling this class, it can potentially be moved from project to project with little to no refactoring required. 285 | 286 | This default implementation is meant to serve as an example of suggested best practices. Robotlegs does not intend to tie you to this example in any way, but it is provided as a suggestion. You are free to develop your own implementation to suit your favored nomenclature and development needs. If this is something you pursue, please let us know about it, as we are always interested in new approaches and it can potentially be included in the Robotlegs repository as an alternate implementation. 287 | 288 | h3(#context). Context 289 | 290 | Like all Robotlegs implementations the MVCS implementation is centered around one or more Contexts. The context provides a central event bus and takes care of its own startup and shutdown. A context defines scope. Framework actors live within a context and communicate with one another within the scope of that context. It is possible to have several contexts within a single application. This is useful for applications that want to load external modules. While the actors within a context can only communicate within the scope of their context, it is possible for contexts to communicate with one another in a modular application. 291 | 292 | Modular programming will not be covered by this document. All references to the Context within this document will be concerned with an application with a single context. 293 | 294 | h3(#controllersandcommands). Controller & Commands 295 | 296 | The Controller tier is represented by the Command class. Commands are stateless, short-lived objects used to perform a single unit of work within an application. Commands are appropriate for communication between application tiers and are able to send system events that will either launch other Commands or be received by a Mediator to perform work on a View Component in response to the event. Commands are an excellent place to encapsulate the business logic of your application. 297 | 298 | h3(#viewandmediators). View & Mediators 299 | 300 | The View tier is represented by the Mediator class. Classes that extend Mediator are used to handle framework interaction with View Components. A Mediator will listen for framework events, add event listeners to the View Components, and send framework events in response to events received from the View Components they are responsible for. This allows the developer to put application specific logic on the Mediator, and avoid coupling View components to specific applications. 301 | 302 | h3(#modelandservice). Model, Service and the Actor 303 | 304 | Conceptually there are many similarities between the service and model tiers in the MVCS architecture. Because of this similarity, models and services are extended from the same base Actor class. A class that extends the Actor base can serve many functions within your application architecture. Within the context of MVCS, we are going to utilize extensions of Actor for defining both the models and the services an application will need to manage data and communicate with external entities. This document will refer to the model and service classes as Model and Service respectively. 305 | 306 | For clarification, this document refers to "framework actors" and "actors" in reference to all of the classes representing the four tiers of an application. This is not to be confused with the MVCS class named Actor, which is extended only by the Model and Service classes to be used in the examples contained here. 307 | 308 | h4(#modelandmodels). Model 309 | 310 | Model classes for use in the model tier encapsulate and provide an API for data. Models send event notifications when work has been performed on the data model. Models are generally highly portable entities. 311 | 312 | h4(#serviceandservices). Service 313 | 314 | A Service for use in the service tier communicates with "the outside world" from within an application. Web services, file access, or any action that takes place outside of the scope of your application is appropriate for a service class. Service classes dispatch system events in response to external events. A service should be highly portable, encapsulating interaction with an external service. 315 | 316 | h3(#frameworkevents). Framework Events 317 | 318 | Robotlegs uses native flash events for communication between framework actors. Custom events are typically utilized for this purpose, it is however possible to use existing Flash events for this same purpose. Robotlegs does not support Event bubbling, as it does not depend on the Flash display list as an event bus. Utilizing custom events allows developers to add properties to the Event that can be used as strongly typed payloads for system events between framework actors. 319 | 320 | Events are sent from all framework actors: Mediators, Services, Models, and Commands. Mediators are the only actors that receive framework events. Commands are triggered in response to framework events. An event can be both received by a Mediator as well as trigger a command. 321 | 322 | Model and service classes should not listen for or respond to events. Doing so would tightly couple them to application specific logic and reduce the potential for portability and reuse. 323 | 324 | 325 | h2(#commands). Commands 326 | 327 | Commands are short-lived stateless objects. They are instantiated, executed and then immediately disposed of. Commands are only executed in response to framework events and should never be instantiated or executed by other framework actors. 328 | 329 | h3(#commandresponsibilities). Command Responsibilities 330 | 331 | Commands are registered to a Context via that Context's CommandMap. The CommandMap is available by default in Context and Command classes. Commands are registered to the Context with an Event type, the Command class to execute in response to the Event, and optionally the Event class and a one off setting for when a Command should be executed once, and then unregistered for future occurrences of an Event. 332 | 333 | h3(#triggeringcommands). Triggering Commands 334 | 335 | Commands are triggered by framework events dispatched by Mediators, Services, Models, and other Commands. Typically the Event that triggered the Command is injected into the Command giving the Command access to the Event's properties/payload: 336 | 337 |
338 | public class MyCommand extends Command
339 | {
340 | 	[Inject]
341 | 	public var event:MyCustomEvent;
342 | 	
343 | 	[Inject]
344 | 	public var model:MyModel;
345 | 			
346 | 	override public function execute():void
347 | 	{
348 | 		model.updateData( event.myCustomEventPayload )
349 | 	}
350 | }
351 | 
352 | 353 | When the mapped command is instantiated in response to a framework event, all of the dependencies that have been mapped and marked with the [Inject] metadata tag are injected into the Command. In addition, the event instance that triggered the Command is also injected. After these dependencies have been supplied, the executed method is called automatically and the Command's work is performed. It is not necessary, and should never be done, to call the execute() method directly. This is the framework implementation's job. 354 | 355 | h3(#chainingcommands). Chaining Commands 356 | 357 | It is also possible to chain commands: 358 | 359 |
360 | public class MyChainedCommand extends Command
361 | {
362 | 	[Inject]
363 | 	public var event:MyCustomEvent;
364 | 	
365 | 	[Inject]
366 | 	public var model:MyModel;
367 | 			
368 | 	override public function execute():void
369 | 	{
370 | 		model.updateData( event.myCustomEventPayload )
371 | 		
372 | 		//the UPDATED_WITH_NEW_STUFF event triggers a command and is also received by
373 | 		//a mediator to update a View Component, but only if a response is requested
374 | 		if(event.responseNeeded)
375 | 		    dispatch( new MyCustomEvent( MyCustomEvent.UPDATED_WITH_NEW_STUFF, model.getCalculatedResponse() ) )
376 | 	}
377 | }
378 | 
379 | 380 | Using this approach it is possible to chain as many Commands as needed together. In the example above a conditional statement is used. If the condition is not met, the Command is not chained. This provides extreme flexibility within your Commands to perform work on your application. 381 | 382 | h3(#decouplingtiers). Decoupling Application Tiers 383 | 384 | Commands are a very useful mechanism for decoupling the various actors of an application. Because a Command is never instantiated or executed from a Mediator, Model or Service, these classes are never coupled to, or even aware of the existence of Commands. 385 | 386 | To perform their duties, Commands may: 387 | 388 | * Map Mediators, Models, Services, or other Commands within their Context 389 | * Dispatch Events to be received by Mediators or trigger other Commands 390 | * Be injected with Models, Services, and Mediators to perform work on directly 391 | 392 | bq(note). Something to note is that it is not recommended to interact directly with Mediators in a Command. While it is possible, it will couple that Mediator to that Command. Since Mediators, unlike Services and Models, are able to receive system Events, the better practice is to simply dispatch an Event from the Command and listen for it on Mediators that need to respond to the Events. 393 | 394 | 395 | h2(#mediators). Mediators 396 | 397 | The Mediator class is used to mediate a user's interaction with an application's View Components. A Mediator can perform this duty at multiple levels of granularity, mediating an entire application and all of its sub-components, or any and all of an application's sub-components directly. 398 | 399 | h3(#mediatorresponsibilities). Mediator Responsibilities 400 | 401 | Flash, Flex and AIR applications provide virtually limitless possibilities for rich visual user interface components. All of these platforms provide out of the box components such as DataGrids, Buttons, Labels and other common UI components. It is also possible to extend these basic components into custom components, create composite components, or write components from scratch. 402 | 403 | A View Component is any UI component and/or its sub-components. A View Component is encapsulated, handling its own state and operations as much as possible. A View Component provides an API via events, simple methods, and properties upon which Mediators act upon to affect the View Component within a Context. Mediators are responsible for interacting with the framework on behalf of the View Components that they mediate. This includes listening for Events on the components and their sub-components, accessing methods, and reading/setting properties on the components. 404 | 405 | A Mediator listens for Events on its View Component, and accesses data directly on the View Component via its exposed API. A Mediators acts on behalf of other framework actors by responding to their Events and modifying its View Component accordingly. A Mediator notifies other framework actors of Events created by the View Component by relaying those Events, or dispatching appropriate Events to the framework. 406 | 407 | h3(#mappingamediator). Mapping a Mediator 408 | 409 | A Mediator can be mapped in any class that has has the _mediatorMap_ instance available. This includes the Mediator, Context, and Command classes. 410 | 411 | This is the syntax for mapping a mediator: 412 |
mediatorMap.mapView( ViewClass, MediatorClass, autoCreate, autoRemove );
413 | 414 | h3(#automaticmediation). Automatic Mediation of View Components 415 | 416 | When a view component class is mapped for mediation, you can specify if you would like to have the Mediator for the class created automatically. When this option is _true_ the context will listen for the view component instance to dispatch its ADDED_TO_STAGE event. When this event is received, the view component will be automatically mediated and its mediator can begin to send and receive framework events. 417 | 418 | h3(#manualmediation). Manually Mediation of View Components 419 | 420 | There are occasions where the automatic mediation of view components is not desired, or impossible. In these cases, it is possible to manually create the Mediator instance for a class: 421 | 422 |
mediatorMap.createMediator(contextView);
423 | 424 | The above assumes that the view component was previously mapped to a mediator using the _mapView()_ method of the _mediatorMap_. 425 | 426 | h3(#mappingthecontextview). Mapping the Main Application (_contextView_) Mediator 427 | 428 | It is a common pattern to map the contextView to a mediator. This is a special situation, as the automatic mediation cannot be performed on the contextView, as it is already added to the stage and will no longer fire the appropriate events the _mediatorMap_ uses to provide this convenience. Typically, this mapping can be done inside the _startup()_ method of the Context that holds a reference to the _contextView_: 429 | 430 |
override public function startup():void
431 | {
432 | 	mediatorMap.mapView(MediateApplicationExample, AppMediator);
433 | 	mediatorMap.createMediator(contextView);
434 | }
435 | 436 | The _contextView_ is now fully mediated and can send and receive framework events. 437 | 438 | h3(#accessingmediatorviewcomponent). Accessing a Mediator's View Component 439 | 440 | When a View Component is added to the stage within a Context's contextView, it is by default mediated automatically based on configuration supplied to the MediatorMap when the mapping was made. In a basic mediator, the _viewComponent_ property is injected with the view component that is being mediated. A Mediator's _viewComponent_ property is of type Object. In most cases, we want access to a strongly typed object to receive the benefits provided by using strongly typed objects. To achieve this, we inject the typed instance of the view component that is being mediated: 441 | 442 |
public class GalleryLabelMediator extends Mediator implements IMediator
443 | {
444 | 	[Inject]
445 | 	public var myCustomComponent:MyCustomComponent;
446 | 		
447 | 	/**
448 | 	* overriding the onRegister method is a good chance to
449 | 	* add any system or View Component Events the Mediator
450 | 	* is interested in receiving.
451 | 	*/
452 | 	override public function onRegister():void
453 | 	{
454 | 		//adding an event listener to the Context for framework events
455 | 		eventMap.mapListener( eventDispatcher, MyCustomEvent.DO_STUFF, handleDoStuff );
456 | 		//adding an event listener to the view component being mediated
457 | 		eventMap.mapListener( myCustomComponent, MyCustomEvent.DID_SOME_STUFF, handleDidSomeStuff)
458 | 	}
459 | 	
460 | 	protected function handleDoStuff(event:MyCustomEvent):void
461 | 	{
462 | 		//setting a property on the view component from the
463 | 		//strongly typed event payload. The view component
464 | 		//will likely manage its own state based on this
465 | 		//new data.
466 | 		myCustomComponent.aProperty = event.payload
467 | 	}
468 | 	
469 | 	protected function handleDidSomeStuff(event:MyCustomEvent):void
470 | 	{
471 | 		//relaying the event to the framework
472 | 		dispatch(event)
473 | 	}
474 | }
475 | 476 | Following this approach we now have easy direct access to the public properties and methods of the mediated view component. 477 | 478 | h3(#addingeventlistenerstomediators). Adding Event Listeners to a Mediator 479 | 480 | Event listeners are the eyes and ears of concrete Mediators. Since all communication within the framework is handled via native Flash events, event listeners will be placed on Mediators to respond to their interests. In addition to framework events, Mediators listen for events from the view components that they are actively mediating. 481 | 482 | It is common to add event listeners in the onRegister method of the Mediator. At this phase of the Mediator's lifecycle, it has been registered and its view component and other dependencies have been injected. The onRegister method must be overridden in concrete Mediator classes. Event listeners may be added in other methods as well, including event handler methods that are responding to both framework and view component events. 483 | 484 | Mediators are equipped with an EventMap that has a method mapListener(). This method registers each event added to the Mediator, and ensures that the event is removed when the mediator is unregistered from the framework. It is important to remove events in Flash, as events that are added, but not removed from a class eliminate the Player's ability to perform runtime Garbage Collection on that class. It is possible to add your event listeners with the traditional Flash syntax, but be aware that you will also need to remove them manually as well. 485 | 486 | h3(#mediatorslisteningfroframeworkevents). Listening for Framework Events 487 | 488 | All of the actors in the framework carry an _eventDispatcher_ property that is injected into the class when it has been instantiated. The _eventDispatcher_ is a Mediator's mechanism for sending and receiving framework events. 489 | 490 |
eventMap.mapListener(eventDispatcher, SomeEvent.IT_IS_IMPORTANT, handleFrameworkEvent)
491 | 492 | Using this syntax, a Mediator is now listening for _SomeEvent.IT_IS_IMPORTANT_ which will be handled by a method called _handleFrameworkEvent_ 493 | 494 | h3(#mediatorsdispatchingframeworkevents). Dispatching Framework Events 495 | 496 | An equally important duty of a Mediator is sending out events to the framework that other actors might be interested in. These events are generally sent in response to some interaction with the mediated view component by the user of the application. Again, a convenience method is supplied to reduce some of the typing necessary to dispatch an event to the framework 497 | 498 |
dispatch(new SomeEvent(SomeEvent.YOU_WILL_WANT_THIS, myViewComponent.someData))
499 | 500 | This event can now be received by other Mediators or execute a command. The Mediator that dispatched the event is not concerned with how other actors within the application will respond to the event, it is simply broadcasting the message that something has occurred. A mediator may also listen for the events that it dispatches, and respond to them accordingly. 501 | 502 | h3(#mediatorslisteningforcomponentevents). Listening for View Component Events 503 | 504 | A Mediator is responsible for listening to events dispatched by the view component being mediated. This can be a single component, such as a TextField or Button, or a complex hierarchy of nested components. When a view component event has been added to a mediator it will be handled by the method designated to handle the event. As with framework events, the EventMap's mapListener method is the preferred syntax for adding event listeners to a mediator: 505 | 506 |
eventMap.mapListener(myMediatedViewComponent, SomeEvent.USER_DID_SOMETHING, handleUserDidSomethingEvent)
507 | 508 | In response to an event received from a view component, a mediator might: 509 | 510 | * examine the payload of the event (if it exists) 511 | * examine the current state of the view component 512 | * perform work on the view component as required 513 | * send framework events to notify other actors that something has occurred 514 | 515 | h3(#accessingmodelsandservicesfrommediators). Accessing Models and Services via a Mediator 516 | 517 | To promote loose coupling your mediators can listen for system events that will be dispatched by Service and Model classes. By listening for events, your mediators do not need to be interested in where these events originate from, they just make use of the strongly typed payload the event carries with it. For this purpose, multiple mediators can be listening for the same event, adjusting their state according to the data that they have received. 518 | 519 | Directly accessing services through a mediator can provide convenience, without serious risk of coupling. A service is not storing data, simply providing an API for making requests to an external service and receiving the response. Being able to access this API directly can save your application from unnecessary command classes to achieve the same goal. If the service API is repeatedly accessed in the same way from many mediators, it can be beneficial to encapsulate this behavior in a command to keep the behavior consistent and reduce the repetition of injecting the service and accessing it directly in your mediators. 520 | 521 | It is recommended that models and services injected directly into mediators are done so via the interfaces the service and model classes implement. An example of this can be found in the "Example Service":#serviceexample section below. 522 | 523 | h3(#accessingothermediatorsfromamediator). Accessing Other Mediators 524 | 525 | As with Services and Models, it is possible to inject and access other Mediators in a Mediator. This practice is *highly discouraged* as the tight coupling can easily be avoided by communication through framework events. 526 | 527 | 528 | h2(#models). Models 529 | 530 | A model class is used to manage access to an application's data model. A model provides an API that is used by other framework actors to access, manipulate, and update application data. This data includes, but is not limited to, native data types such as String, Array, or ArrayCollection as well as domain specific Objects or collections of these. 531 | 532 | Model are referred to as simply Model, as in UserModel, and at other times they might be referred to as Proxy as in UserProxy. In Robotlegs, both of these naming conventions are used for the same purpose. Providing an API for an applications data. Regardless of the naming convention models will extend the Actor base class which provides core framework dependencies as well as convenience helper methods your models can make use of. This document will refer to these classes as Model. 533 | 534 | h3(#modelresponsibilities). Model Responsibilities 535 | 536 | Model classes encapsulate and provide an API for the application data model. A Model class is the gatekeeper for your application's data. Other actors in the application make requests for data through the API provided by the Model. As data is updated through the Model, the Model is equipped to broadcast events to the framework informing other actors of changes to the data model so they may adjust their state accordingly. 537 | 538 | In addition to controlling access to the data model, the Model is routinely used to perform operations on the data to keep the data in a valid state. This includes performing calculations on the data, or other areas of domain specific logic. This responsibility of the Model is extremely important. The Model is the tier of any given application with the highest potential for portability. By placing domain logic on the Model, future implementations of the model will not have to repeat this same logic as they would if it was placed in the View or Controller tiers. 539 | 540 | As an example, your Model class might perform a sales tax calculation on the shopping cart data that it is string. A Command will access this method, and the final calculation will be dispatched as an event that a Mediator is listening for. The mediator will then update its view component with the updated value. In the first iteration of the application it was a typical Flex application. This calculation could have easily been performed on a Mediator, or even the view itself. The second iteration of the application is a mobile Flash application that requires an entirely new view form factor. Since this logic is contained in the Model, it can be reused for both form factors with entirely different views. 541 | 542 | h3(#mappingamodel). Mapping a Model 543 | 544 | There are several methods available on the injector for mapping your Model classes for injection into your framework actors. In addition, these methods can be used for injecting virtually ANY class into your classes. 545 | 546 | To map an existing instance for injection that will be treated as a singleton, use the following syntax: 547 |
injector.mapValue(MyModelClass, myModelClassInstance)
548 | 549 | To map a new instance of a class for each injection, use the following syntax: 550 |
injector.mapClass(MyModelClass, MyModelClass)
551 | Additionally, this can be used to map interfaces for injection, with a concrete class that implements the interface being injected: 552 |
injector.mapClass(IMyModelClass, MyModelClass)
553 | 554 | To map a singleton instance of an interface or class, use the following syntax: 555 |
injector.mapSingleton(MyModelClass, MyModelClass)
556 | 557 | It is important to note that when referring to a singleton above, it is not a Singleton. It is not enforced outside of the Context as a Singleton. The injector simply insures that one and only one instance of the class will be injected. This is vital for Model classes that are handling your application data model. 558 | 559 | h3(#dispatchingeventsfrommodel). Dispatching Events from a Model 560 | 561 | The Model class provides a convenience method _dispatch_ for sending framework events: 562 | 563 |
dispatch( new ImportantDataEvent(ImportantDataEvent.IMPORTANT_DATA_UPDATED))
564 | 565 | Events can be dispatched for any number of reasons, including but not limited to: 566 | 567 | * Data has been initialized and is ready for other actors to use 568 | * Some piece of data has been added to the Model 569 | * Data has been removed from the Model 570 | * Data has changed or updated 571 | * State has been changed related to the data 572 | 573 | h3(#listeningforeventsinmodel). Listening for Framework Events in a Model 574 | 575 | While this is technically possible it is *highly discouraged*. Don't do it. Just for the sake of clarity: *Don't do it*. If you do, don't say you weren't warned. 576 | 577 | 578 | h2(#services). Services 579 | 580 | Services are utilized to access resources outside of the scope of the application. This is including, but certainly not limited to: 581 | 582 | * web services 583 | * file system 584 | * data bases 585 | * RESTful APIs 586 | * other Flash applications via localConnection 587 | 588 | Services encapsulate this interaction with external entities, and manage the results, faults, and other events that result from this interaction. 589 | 590 | You might notice that the Service and Model base classes are very similar. In fact, you might notice that outside of the class name, they are exactly the same. Why have two classes then? Model and Service classes have entirely different responsibilities within an application. The concrete implementations of these classes will not be similar. Without this separation, you will generally find external service access being performed on Model classes. This creates Models that have the multiple duty of accessing external data, parsing results, handling faults, managing application data state, providing an API for data, providing an API for the service, etc. Separating these tiers helps to alleviate this problem. 591 | 592 | h3(#serviceresponsibilities). Service Responsibilities 593 | 594 | A Service class provides your application with an API for interacting with an external service. A service class will contact the external service ad manage the response that it receives. Services are typically stateless entities. They do not store the data that is returned from an external service, but instead send framework events so that response data and faults can be managed by the appropriate framework actors. 595 | 596 | h3(#mappingservice). Mapping a Service 597 | 598 | There are "several methods available on the injector":#mappingwithinjector for mapping your Service classes for injection into your framework actors. In addition, these methods can be used for injecting virtually ANY class into your classes. 599 | 600 | To map an existing instance for injection that will be treated as a singleton, use the following syntax: 601 |
injector.mapValue(MyServiceClass, myServiceClassInstance)
602 | 603 | To map a new instance of a class for each injection, use the following syntax: 604 |
injector.mapClass(MyServiceClass, MyServiceClass)
605 | Additionally, this can be used to map interfaces for injection, with a concrete class that implements the interface being injected: 606 |
injector.mapClass(IMyServiceClass, MyServiceClass)
607 | 608 | To map a singleton instance of an interface or class, use the following syntax: 609 |
injector.mapSingleton(MyServiceClass, MyServiceClass)
610 | 611 | It is important to note that when referring to a singleton above, it is not a Singleton. It is not enforced outside of the Context as a Singleton. The injector simply insures that one and only one instance of the class will be injected. 612 | 613 | h3(#servicelisteningforframeworkevents). Listening for Framework Events in a Service 614 | 615 | While this is technically possible it is *highly discouraged*. Don't do it. Just for the sake of clarity: *Don't do it*. If you do, don't say you weren't warned. 616 | 617 | h3(#servicedispatchingframeworkevents). Dispatching Framework Events 618 | 619 | The Service class provides a convenience method _dispatch_ for sending framework events: 620 | 621 |
dispatch( new ImportantServiceEvent(ImportantServiceEvent.IMPORTANT_SERVICE_EVENT))
622 | 623 | h3(#serviceexample). Example Service 624 | 625 | The following is the Flickr service class from the "Image Gallery":http://github.com/robotlegs/robotlegs-demos-Bundle/tree/master/FlickrImageGallery/ demo. "The Flickr API AS3 Library":http://code.google.com/p/as3flickrlib/ does a lot of the low level heavy lifting needed to connect to Flickr. This example makes use of that and provides a simple abstraction for use within the scope of the example. 626 | 627 |
package org.robotlegs.demos.imagegallery.remote.services
628 | {
629 | 	import com.adobe.webapis.flickr.FlickrService;
630 | 	import com.adobe.webapis.flickr.Photo;
631 | 	import com.adobe.webapis.flickr.events.FlickrResultEvent;
632 | 	import com.adobe.webapis.flickr.methodgroups.Photos;
633 | 	import com.adobe.webapis.flickr.methodgroups.helpers.PhotoSearchParams;
634 | 	
635 | 	import org.robotlegs.demos.imagegallery.events.GalleryEvent;
636 | 	import org.robotlegs.demos.imagegallery.models.vo.Gallery;
637 | 	import org.robotlegs.demos.imagegallery.models.vo.GalleryImage;
638 | 	import org.robotlegs.mvcs.Actor;
639 | 
640 |     /**
641 |      *  This class utilizes the Flickr API provided by Adobe to connect
642 |      *  to Flickr and retrieve images. It initially loads the current top
643 |      *  "interestingness" photos. It also provides the ability to search
644 |      *  for a specific keyword.    
645 |      */
646 | 	public class FlickrImageService extends Actor implements IGalleryImageService
647 | 	{
648 | 		private var service:FlickrService;
649 | 		private var photos:Photos;
650 | 		
651 | 		protected static const FLICKR_API_KEY:String = "516ab798392cb79523691e6dd79005c2";
652 | 		protected static const FLICKR_SECRET:String = "8f7e19a3ae7a25c9";
653 | 		
654 | 		public function FlickrImageService()
655 | 		{
656 | 			this.service = new FlickrService(FLICKR_API_KEY);
657 | 		}
658 | 		
659 | 		public function get searchAvailable():Boolean
660 | 		{
661 | 			return true;
662 | 		}
663 | 				
664 | 		public function loadGallery():void
665 | 		{
666 | 			service.addEventListener(FlickrResultEvent.INTERESTINGNESS_GET_LIST, handleSearchResult);
667 | 			service.interestingness.getList(null,"",20)
668 | 		}
669 | 		
670 | 		public function search(searchTerm:String):void
671 | 		{
672 | 			if(!this.photos)
673 | 				this.photos = new Photos(this.service);
674 | 			service.addEventListener(FlickrResultEvent.PHOTOS_SEARCH, handleSearchResult);
675 | 			var p:PhotoSearchParams = new PhotoSearchParams()
676 | 			p.text = searchTerm;
677 | 			p.per_page = 20;
678 | 			p.content_type = 1;
679 | 			p.media = "photo"
680 | 			p.sort = "date-posted-desc";
681 | 			this.photos.searchWithParamHelper(p);				
682 | 		}
683 | 		
684 | 		protected function handleSearchResult(event:FlickrResultEvent):void
685 | 		{
686 | 			this.processFlickrPhotoResults(event.data.photos.photos);
687 | 		}
688 | 		
689 | 		protected function processFlickrPhotoResults(results:Array):void
690 | 		{
691 | 			var gallery:Gallery = new Gallery();
692 | 						
693 | 			for each(var flickrPhoto:Photo in results)
694 | 			{
695 | 				var photo:GalleryImage = new GalleryImage()
696 | 				var baseURL:String = 'http://farm' + flickrPhoto.farmId + '.static.flickr.com/' + flickrPhoto.server + '/' + flickrPhoto.id + '_' + flickrPhoto.secret;
697 | 				photo.thumbURL = baseURL + '_s.jpg';
698 | 				photo.URL = baseURL + '.jpg';
699 | 				gallery.photos.addItem( photo );
700 | 			}
701 | 			
702 | 			dispatch(new GalleryEvent(GalleryEvent.GALLERY_LOADED, gallery));
703 | 		}
704 | 		
705 | 	}
706 | }
707 | 708 | The FlickrGalleryService provides a very simple interface for connecting to a gallery service. The application can _loadGallery_, _search_, and find out if _searchAvailable_ is true or false. This interface is defined by the IGalleryService interface: 709 | 710 |
package org.robotlegs.demos.imagegallery.remote.services
711 | {
712 | 	public interface IGalleryImageService
713 | 	{
714 | 		function loadGallery():void;
715 | 		function search(searchTerm:String):void;
716 | 		function get searchAvailable():Boolean;
717 | 	}
718 | }
719 | 720 | h4(#serviceimplementsinterface). Services Should Implement an Interface 721 | 722 | By creating service classes that implement interfaces, it makes it trivial to switch them out at runtime for testing or providing access to additional services to the end users of the application. For example, the FlickrGalleryService can be easily substituted for an XMLGalleryService: 723 | 724 |
package org.robotlegs.demos.imagegallery.remote.services
725 | {
726 | 	import mx.rpc.AsyncToken;
727 | 	import mx.rpc.Responder;
728 | 	import mx.rpc.http.HTTPService;
729 | 	
730 | 	import org.robotlegs.demos.imagegallery.events.GalleryEvent;
731 | 	import org.robotlegs.demos.imagegallery.models.vo.Gallery;
732 | 	import org.robotlegs.demos.imagegallery.models.vo.GalleryImage;
733 | 	import org.robotlegs.mvcs.Actor;
734 | 
735 | 	public class XMLImageService extends Actor implements IGalleryImageService
736 | 	{
737 | 		protected static const BASE_URL:String = "assets/gallery/";
738 | 		
739 | 		public function XMLImageService()
740 | 		{
741 | 			super();
742 | 		}
743 | 
744 | 		public function get searchAvailable():Boolean
745 | 		{
746 | 			return false;
747 | 		}
748 | 				
749 | 		public function loadGallery():void
750 | 		{
751 | 			var service:HTTPService = new HTTPService();
752 | 			var responder:Responder = new Responder(handleServiceResult, handleServiceFault);
753 | 			var token:AsyncToken;
754 | 			service.resultFormat = "e4x";
755 | 			service.url = BASE_URL+"gallery.xml";
756 | 			token = service.send();
757 | 			token.addResponder(responder);
758 | 		}
759 | 		
760 | 		public function search(searchTerm:String):void
761 | 		{
762 | 			trace("search is not available");
763 | 		}
764 | 		
765 | 		protected function handleServiceResult(event:Object):void
766 | 		{
767 | 			var gallery:Gallery = new Gallery();
768 | 						
769 | 			for each(var image:XML in event.result.image)
770 | 			{
771 | 				var photo:GalleryImage = new GalleryImage()
772 | 				photo.thumbURL = BASE_URL + "images/" + image.@name + '_s.jpg';
773 | 				photo.URL = BASE_URL + "images/" + image.@name + '.jpg';
774 | 				gallery.photos.addItem( photo );
775 | 			}
776 | 			
777 | 			dispatchEvent(new GalleryEvent(GalleryEvent.GALLERY_LOADED, gallery));
778 | 		}
779 | 		
780 | 		protected function handleServiceFault(event:Object):void
781 | 		{
782 | 			trace(event);
783 | 		}
784 | 	}
785 | }
786 | 787 | The XML gallery provides access to the same methods as the Flickr and can now be substituted where ever the IGalleryService interface is called for. The services dispatch the same events, and are practically indistinguishable in the final application. In this example, search was not implemented, but the search functionality could be easily implemented in this service as well. 788 | 789 | It is recommended that all services implement an interface that defines their API. In framework actors that receive a service as an injected dependency should ask for the interface, and not the concrete implementation: 790 | 791 |
injector.mapSingletonOf(IGalleryService, FlickrGalleryService);
792 | 793 |
[Inject]
794 | public var galleryService:IGalleryService
795 | 796 | You can easily replace the gallery service utilized by your classes by simply changing the injection: 797 | 798 |
injector.mapSingletonOf(IGalleryService, XMLGalleryService);
799 | 800 | This approach can provide power, flexibility, and enhanced testability to an application. 801 | 802 | h4(#parsingresultsinservice). Parsing Data in a Service 803 | 804 | In the above example service classes or external services provide objects that are not representative of the application domain. The Flickr service provides strongly typed Photo objects and the XML service provides... xml. Both of these data types are perfectly usable, but do not necessarily fit into the context of our application. They are foreigners. The choice is between molding the application around these external data types, or preferably, convert these types into custom data types that are representative of the application. 805 | 806 | There are two places where this manipulation/conversion should take place in the application. The Service or the Model would both be appropriate. The Service is the first point of entry for external data, and this fact makes it a better choice for manipulating the data returned by a service. The foreign data should be converted to the application domain at the first opportunity. 807 | 808 | _provide an example of using a factory class to produce the application domain objects instead of doing it in the service... proper_ 809 | 810 | After the data has been converted to the objects specific to the application domain events are sent with strongly typed payloads to be immediately utilized by interested actors. 811 | 812 | h4(#serviceevents). Service Events 813 | 814 | The final corner of the service triad is the custom event. Without events, services are mute. Any work they might do will go unnoticed by the other application actors. A service will make use of custom events to provide itself with a voice to the broader application. Events to not have to be singular in purpose. If the service is transforming the data it can make use of a common event to dispatch strongly typed data to interested application actors. 815 | 816 | 817 | -------------------------------------------------------------------------------- /best-practices-en/0_toc.textile: -------------------------------------------------------------------------------- 1 |
!=http://joelhooks.com/wp-content/uploads/2009/07/robotlegssketchsmall.gif!

Documentation for Robotlegs v1.0RC1

2 | h2. Table of Contents 3 | 4 | # *"What is Robotlegs":#whatisrobotlegs* 5 | # "Dependency Injection":#dependencyinjection 6 | # "Using Injectors":#usingtheinjectors 7 | ** "Injection Syntax for the SwiftSuspenders Adapter":#injectionsyntax 8 | ** "Injection Mapping with the Injector Class":#mappingwithinjector 9 | ** "Injection Mapping with the MediatorMap Class":#mappingwithmediatormap 10 | ** "Injection Mapping with the CommandMap Class":#mappingwithcommandmap 11 | # "The Context":#thecontext 12 | # *"MVCS Reference Implementation":#mvcs* 13 | ## "Context":#context 14 | ## "Controller & Commands":#controllersandcommands 15 | ## "View & Mediators":#viewandmediators 16 | ## "Model, Service and the Actor":#modelandservice 17 | ## "Model":#modelandmodels 18 | ## "Service":#serviceandservices 19 | ## "Framework Events":#frameworkevents 20 | ## *"Commands":#commands* 21 | ### "Command Responsibilities":#commandresponsibilities 22 | ### "Triggering Commands":#triggeringcommands 23 | ### "Chaining Commands":#chainingcommands 24 | ### "Decoupling Application Tiers":#decouplingtiers 25 | ## *"Mediators":#mediators* 26 | ### "Mediator Responsibilities":#mediatorresponsibilities 27 | ### "Mapping a Mediator":#mappingamediator 28 | ### "Automatic Mediation of View Components":#automaticmediation 29 | ### "Manually Mediation of View Components":#manualmediation 30 | ### "Mapping the Main Application (_contextView_) Mediator":#mappingthecontextview 31 | ### "Accessing a Mediator's View Component":#accessingmediatorviewcomponent 32 | ### "Adding Event Listeners to a Mediator":#addingeventlistenerstomediators 33 | ### "Listening for Framework Events":#mediatorslisteningfroframeworkevents 34 | ### "Dispatching Framework Events":#Mediatorsdispatchingframeworkevents 35 | ### "Listening for View Component Events":#mediatorslisteningforcomponentevents 36 | ### "Accessing Models and Services via a Mediator":#accessingmodelsandservicesfrommediators 37 | ### "Accessing Other Mediators":#accessingothermediatorsfromamediator 38 | ## *"Models":#models* 39 | ### "Model Responsibilities":#modelresponsibilities 40 | ### "Mapping a Model":#mappingamodel 41 | ### "Dispatching Events from a Model":#dispatchingeventsfrommodel 42 | ### "Listening for Framework Events in a Model":#listeningforeventsinmodel 43 | ## *"Services":#services* 44 | ### "Service Responsibilities":#serviceresponsibilities 45 | ### "Mapping a Service":#mappingservice 46 | ### "Listening for Framework Events in a Service":#servicelisteningforframeworkevents 47 | ### "Dispatching Framework Events":#servicedispatchingframeworkevents 48 | ### "Example Service":#serviceexample 49 | **** "Services Should Implement an Interface":#serviceimplementsinterface 50 | **** "Parsing Data in a Service":#parsingresultsinservice 51 | **** "Service Events":#serviceevents 52 | 53 | -------------------------------------------------------------------------------- /best-practices-en/1_what_is_robotlegs.textile: -------------------------------------------------------------------------------- 1 | h2(#whatisrobotlegs). What is Robotlegs 2 | 3 | Robotlegs is a pure AS3 micro-architecture (framework) for developing Flash, Flex, and AIR applications. Robotlegs is narrowly focused on wiring application tiers together and providing a mechanism by which they communicate. Robotlegs seeks to speed up development while providing a time tested architectural solution to common development problems. Robotlegs is not interested in locking you into the framework, your classes are just that, your classes, and should be easily transferable to other frameworks should the need or desire to do so arise in the future. 4 | 5 | The framework supplies a default implementation based on the "Model-View-Controller":http://en.wikipedia.org/wiki/Model–view–controller meta-design pattern. This implementation provides a strong suggestion as to application structure and design. While it does make your application slightly less portable, it still aims to be as minimally invasive as possible in your concrete classes. By extending the "MVCS":#mvcs implementation classes, you are supplied with numerous methods and properties for the sake of convenience. 6 | 7 | You are never obligated to use the standard "MVCS":#mvcs implementation with Robotlegs. You can use any part of it, none of it, or freely roll your own implementation to suit your needs. It is included to provide a proper reference implementation and a jump start to using Robotlegs. 8 | 9 | -------------------------------------------------------------------------------- /best-practices-en/2_dependency_injection.textile: -------------------------------------------------------------------------------- 1 | h2(#dependencyinjection). Dependency Injection 2 | 3 | Robotlegs revolves around the "Dependency Injection":http://www.insideria.com/2009/09/as3-dependency-injection-demys.html design pattern. 4 | 5 | bq. At the simplest, Dependency Injection is that act of supplying objects with their instance variables or properties. When you pass a variable to the constructor of a class, you are using Dependency Injection. When you set a property on a class, you are using Dependency Injection. If you aren't coding your AS3 in a strictly procedural or linear fashion, the odds are that you are making use of Dependency Injection right now. 6 | 7 | Robotlegs uses automated, metadata based Dependency Injection. This is provided as a convenience for the developer and has the advantage of greatly reducing the amount of code needed to wire together an application and provide classes with their necessary dependencies. While it is fully possible to supply these dependencies to your classes manually, allowing the framework to perform these duties reduces the chances for error and generally speeds up the coding process. 8 | 9 | -------------------------------------------------------------------------------- /best-practices-en/3_0_injection.textile: -------------------------------------------------------------------------------- 1 | h2(#usingtheinjectors). Using Injectors 2 | 3 | Robotlegs provides an adapter mechanism for providing a dependency injection mechanism to the framework. By default, the framework is equipped with the "SwiftSuspenders":http://github.com/tschneidereit/SwiftSuspenders injection/reflection library to serve this purpose. Additional adapters are available for SmartyPants-IoC and Spring Actionscript. There can potentially be specific reasons to use another dependency injection adapter, but if you don't have a specific reason for doing so, it is recommended that you use the default "SwiftSuspenders":http://github.com/tschneidereit/SwiftSuspenders as it is performance tuned specifically for Robotlegs. 4 | 5 | h3(#injectionsyntax). Injection Syntax for the SwiftSuspenders Adapter 6 | 7 | SwiftSuspenders supports three types of dependency injection. 8 | 9 | * Property (field) Injection 10 | * Parameter (method/setter) Injection 11 | * Constructor Injection 12 | 13 | For the purposes of this document, we are going to examine Property injection, and how this is used within Robotlegs. There are two options for injecting properties into your class. You can use unnamed, or named injection: 14 | 15 |
[Inject]
16 | public var myDependency:Depedency; //unnamed injection
17 | 18 |
[Inject(name="myNamedDependency")]
19 | public var myNamedDependency:NamedDepedency; //named injection
20 | 21 | Injection mappings are supplied to Robotlegs in three places. The MediatorMap, the CommandMap, and through the Injector directly. Both the MediatorMap and the CommandMap are making use of the Injector as well, but they are doing additional work required by these tiers. As the names imply, MediatorMap is used for mapping Mediators, CommandMap is used for mapping Commands, and anything else that needs to be injected (including but not limited to Models) is mapped directly with the Injector. 22 | 23 | -------------------------------------------------------------------------------- /best-practices-en/3_1_injecting_with_the_injector.textile: -------------------------------------------------------------------------------- 1 | h3(#mappingwithinjector). Injection Mapping with the Injector Class 2 | 3 | The adapters for concrete Injector classes conform to the IInjector interface. This interface provides a consistent API for injection, irrespective of the dependency injection solution provided. This document focuses on SwiftSuspenders, but this syntax is true for any Injector that conforms to the IInjector interface. 4 | 5 | The _injector_ is the workhorse of all of the dependency injection that happens in your application. It is used for injecting framework actors, but can also be used for any other injections that your application might need. This includes, but is not limited to RemoteObjects, HTTPServices, factory classes, or virtually ANY class/instance that you might need as dependencies for your your application objects. 6 | 7 | Below are the four mapping methods that are provided with classes that implement IInjector: 8 | 9 | h4. mapValue 10 | 11 | mapValue is used to map a specific instance of an object to an injector. When asked for a specific class, use this specific instance of the class for injection. 12 | 13 |
//someplace in your application where mapping/configuration occurs
14 | var myClassInstance:MyClass = new MyClass();
15 | injector.mapValue(MyClass, myClassInstance);
16 | 17 | 18 |
//in the class to receive injections
19 | [Inject]
20 | public var myClassInstance:MyClass
21 | 22 |
mapValue(whenAskedFor:Class, instantiateClass:Class, named:String = null)
23 | 24 | The instance of MyClass is created and is held waiting to be injected when requested. When it is requested, that instance is used to fill the injection request. It is important to note that since you have manually created a class instance and mapped it with mapValue, the instance will not have it's dependencies injected automatically. You will need to inject the dependencies manually or via the injector: 25 | 26 |
injector.injectInto(myClassInstance);
27 | 28 | This will provide the instance with its mapped injectable properties immediately. 29 | 30 | h4. mapClass 31 | 32 | mapClass provides a _unique_ instance of the mapped class for each injection request. 33 | 34 |
//someplace in your application where mapping/configuration occurs
35 | injector.mapClass(MyClass);
36 | 37 | 38 |
//in the first class to receive injections
39 | [Inject]
40 | public var myClassInstance:MyClass
41 | 42 | 43 |
//in the second class to receive injections
44 | [Inject]
45 | public var myClassInstance:MyClass
46 | 47 | Each of the injections above will provide a _unique_ instance of MyClass to fulfill the request. 48 | 49 |
mapClass(whenAskedFor:Class, named:String = null)
50 | 51 | The injector provides a method for instantiating mapped objects: 52 | 53 |
injector.mapClass(MyClass);
54 | var myClassInstance:MyClass = injector.instantiate(MyClass);
55 | 56 | This provides an instance of your object and all mapped injection points contained in the object are filled. 57 | 58 | h4. mapSingleton 59 | 60 | mapSingleton provides a _single_ instance of the requested class for every injection. Providing a single instance of a class across all injections ensures that you maintain a consistent state and don't create unnecessary instances of the injected class. This is a managed single instance, enforced by the framework, and not a Singleton enforced within the class itself. 61 | 62 |
//someplace in your application where mapping/configuration occurs
63 | injector.mapSingleton(MyClass);
64 | 65 | 66 |
//in the first class to receive injections
67 | [Inject]
68 | public var myClassInstance:MyClass
69 | 70 | 71 |
//in the second class to receive injections
72 | [Inject]
73 | public var myClassInstance:MyClass
74 | 75 | In the above example, both injections requests will be filled with the same instance of the requested class. _This injection is deferred_, meaning the object is not instantiated until it is first requested. 76 | 77 |
mapSingletonOf(whenAskedFor:Class, useSingletonOf:Class, named:String = null)
78 | 79 | h4. mapSingletonOf 80 | 81 | mapSingletonOf is much like mapSingleton in functionality. It is useful for mapping abstract classes and interfaces, where mapSingleton is for mapping concrete class implementations. 82 | 83 |
//someplace in your application where mapping/configuration occurs
84 | injector.mapSingletonOf(IMyClass, MyClass); //MyClass implements IMyClass
85 | 86 |
//in the first class to receive injections
87 | [Inject]
88 | public var myClassInstance:IMyClass
89 | 90 | 91 |
//in the second class to receive injections
92 | [Inject]
93 | public var myClassInstance:IMyClass
94 | 95 | This injection method is useful for creating classes that are more testable and can take advantage of polymorphism. An example of this can be found below in the "Example Service":#serviceimplementsinterface section. 96 | 97 | -------------------------------------------------------------------------------- /best-practices-en/3_2_injecting_via_MediatorMap.textile: -------------------------------------------------------------------------------- 1 | h3(#mappingwithmediatormap). Injection Mapping with the MediatorMap Class 2 | 3 | The MediatorMap class implements IMediatorMap, which provides two methods for mapping your mediators to views and registering them for injection. 4 | 5 |
mapView(viewClassOrName:*, mediatorClass:Class, injectViewAs:Class = null, autoCreate:Boolean = true, autoRemove:Boolean = true):void
6 | 7 | *mapView* accepts a view class, MyAwesomeWidget, or a fully qualified class name for a view, _com.me.app.view.components::MyAwesomeWidget_ as the first parameter. The second parameter is the Mediator class that will mediate the view component. *[NEED TO PUT IN injectAsView]* The last two parameters autoCreate and autoRemove are boolean switches that provide convenient automatic mediator management. 8 | 9 |
//someplace in your application where mapping/configuration occurs
10 | mediatorMap.mapView(MyAwesomeWidget, MyAwesomeWidgetMediator); 
11 | 12 |
//somewhere inside of the contextView's display list
13 | var myAwesomeWidget:MyAwesomeWidget = new MyAwesomeWidget();
14 | this.addChild(myAwesomeWidget); //the ADDED_TO_STAGE event is dispatched, which triggers the view component to be mediated
15 | 16 | This approach utilizes the automated mediation. Manual mediation, and a more in-depth look at this process will be covered later in the "Mediators":#mediators section. 17 | 18 | 19 | -------------------------------------------------------------------------------- /best-practices-en/3_3_injecting_with_CommandMap.textile: -------------------------------------------------------------------------------- 1 | h3(#mappingwithcommandmap). Injection Mapping with the CommandMap Class 2 | 3 | The CommandMap class implements ICommandMap, which provides one method for mapping commands to framework events that trigger them. 4 | 5 |
mapEvent(eventType:String, commandClass:Class, eventClass:Class = null, oneshot:Boolean = false)
6 | 7 | You will provide the commandMap with a class to execute, the type of event that executes it, and optionally a strong typing for the event and a boolean switch if the command should be executed only a single time and then be unmapped. 8 | 9 | The strongly typed event class optional parameter is used as extra protection against the Flash platforms "magic string" event type system. This will prevent any conflict between events that might have the same String type, but are actually types of different event classes. 10 | 11 |
//someplace in your application where mapping/configuration occurs
12 | commandMap.mapEvent(MyAppDataEvent.DATA_WAS_RECEIVED, MyCoolCommand, MyAppDataEvent); 
13 | 14 | 15 |
//in another framework actor an event is dispatched
16 | //this triggers the mapped command which is subsequently executed
17 | dispatch(new MyAppDataEvent(MyAppDataEvent.DATA_WAS_RECEIVED, someTypedPayload))
18 | 19 | -------------------------------------------------------------------------------- /best-practices-en/4_context.textile: -------------------------------------------------------------------------------- 1 | h2(#thecontext). The Context 2 | 3 | At the heart of any Robotlegs implementation lies the Context. The Context, or Contexts as the case may be, provides the mechanism by which any given implementation's tiers will communicate. An application is by no means limited to a single Context, but for many use cases one Context is sufficient. With the ability to build modular applications on the Flash platform, you will see circumstances where multiple Contexts are necessary. The Context has three functions within an application: provide initialization, provide de-initialization, and provide the central event bus for communication. 4 | 5 |
package org.robotlegs.examples.bootstrap
 6 | {
 7 | 	import flash.display.DisplayObjectContainer;
 8 | 	
 9 | 	import org.robotlegs.base.ContextEvent;
10 | 	import org.robotlegs.core.IContext;
11 | 	import org.robotlegs.mvcs.Context;
12 | 	
13 | 	public class ExampleContext extends Context implements IContext
14 | 	{
15 | 		public function UnionChatContext(contextView:DisplayObjectContainer)
16 | 		{
17 | 			super(contextView);
18 | 		}
19 | 		
20 | 		override public function startup():void
21 | 		{
22 | 			//This Context is mapping a single command to the ContextEvent.STARTUP
23 | 			//The StartupCommand will map additional commands, mediators, services,
24 | 			//and models for use in the application.
25 | 			commandMap.mapEvent( ContextEvent.STARTUP, StartupCommand, ContextEvent, true );
26 | 						
27 | 			//Start the Application (triggers the StartupCommand)
28 | 			dispatch(new ContextEvent(ContextEvent.STARTUP));
29 | 		}
30 | 	}
31 | }
32 | 33 | -------------------------------------------------------------------------------- /best-practices-en/5_mvcs_overview.textile: -------------------------------------------------------------------------------- 1 | h2(#mvcs). MVCS Reference Implementation 2 | 3 | Robotlegs is equipped with a reference implementation. This implementation follows the classic meta-design pattern known as Model-View-Controller (MVC), with the addition of a fourth actor called Service. These tiers, throughout this document, are referred to as the "Core actors," or simply "actors." 4 | 5 | MVCS provides an architectural overview of an application. By combining several time tested design patterns into a concrete implementation, the Robotlegs MVCS implementation can be used as a consistent approach for building your applications. By approaching an application with these architectural concepts you are able to have many common obstacles removed prior to even starting your design: 6 | 7 | * Separation 8 | * Organization 9 | * Decoupling 10 | 11 | h4. Separation 12 | 13 | MVCS provides a natural way for separating your application into discrete layers that provide specific functionality. The view layer handles interaction with the user. The model layer handles the data that is retrieved from external sources or created by the user. The controller tier provides a mechanism for encapsulating complex interaction between the tiers. Finally, the service layer provides an isolated mechanism for communicating with entities outside of the application such as remote service APIs or the file system. 14 | 15 | h4. Organization 16 | 17 | Through this separation we naturally achieve a level of organization. Every project requires some level of organization. Yes, one could toss all of their classes into the root package and call it a day, but this is unrealistic on even the smallest project. When a project is of any non-trivial size it becomes necessary to start organizing the structure of the class files. This need becomes even more acute as a project adds team members contributing to the same application. The Robotlegs MVCS implementation describes an organizational structure for projects neatly divided into the four tiers. 18 | 19 | h4. Decoupling 20 | 21 | The Robotlegs MVCS implementation promotes the decoupling of the four application tiers. Each tier is isolated from the rest, making it much easier to isolate classes and components for testing. In addition to easing the testing process, this also frequently allows for portable classes that can be reused in additional projects. For example, a Service class that connects to a remote API might b useful in several applications. By decoupling this class, it can potentially be moved from project to project with little to no refactoring required. 22 | 23 | This default implementation is meant to serve as an example of suggested best practices. Robotlegs does not intend to tie you to this example in any way, but it is provided as a suggestion. You are free to develop your own implementation to suit your favored nomenclature and development needs. If this is something you pursue, please let us know about it, as we are always interested in new approaches and it can potentially be included in the Robotlegs repository as an alternate implementation. 24 | 25 | h3(#context). Context 26 | 27 | Like all Robotlegs implementations the MVCS implementation is centered around one or more Contexts. The context provides a central event bus and takes care of its own startup and shutdown. A context defines scope. Framework actors live within a context and communicate with one another within the scope of that context. It is possible to have several contexts within a single application. This is useful for applications that want to load external modules. While the actors within a context can only communicate within the scope of their context, it is possible for contexts to communicate with one another in a modular application. 28 | 29 | Modular programming will not be covered by this document. All references to the Context within this document will be concerned with an application with a single context. 30 | 31 | h3(#controllersandcommands). Controller & Commands 32 | 33 | The Controller tier is represented by the Command class. Commands are stateless, short-lived objects used to perform a single unit of work within an application. Commands are appropriate for communication between application tiers and are able to send system events that will either launch other Commands or be received by a Mediator to perform work on a View Component in response to the event. Commands are an excellent place to encapsulate the business logic of your application. 34 | 35 | h3(#viewandmediators). View & Mediators 36 | 37 | The View tier is represented by the Mediator class. Classes that extend Mediator are used to handle framework interaction with View Components. A Mediator will listen for framework events, add event listeners to the View Components, and send framework events in response to events received from the View Components they are responsible for. This allows the developer to put application specific logic on the Mediator, and avoid coupling View components to specific applications. 38 | 39 | h3(#modelandservice). Model, Service and the Actor 40 | 41 | Conceptually there are many similarities between the service and model tiers in the MVCS architecture. Because of this similarity, models and services are extended from the same base Actor class. A class that extends the Actor base can serve many functions within your application architecture. Within the context of MVCS, we are going to utilize extensions of Actor for defining both the models and the services an application will need to manage data and communicate with external entities. This document will refer to the model and service classes as Model and Service respectively. 42 | 43 | For clarification, this document refers to "framework actors" and "actors" in reference to all of the classes representing the four tiers of an application. This is not to be confused with the MVCS class named Actor, which is extended only by the Model and Service classes to be used in the examples contained here. 44 | 45 | h4(#modelandmodels). Model 46 | 47 | Model classes for use in the model tier encapsulate and provide an API for data. Models send event notifications when work has been performed on the data model. Models are generally highly portable entities. 48 | 49 | h4(#serviceandservices). Service 50 | 51 | A Service for use in the service tier communicates with "the outside world" from within an application. Web services, file access, or any action that takes place outside of the scope of your application is appropriate for a service class. Service classes dispatch system events in response to external events. A service should be highly portable, encapsulating interaction with an external service. 52 | 53 | h3(#frameworkevents). Framework Events 54 | 55 | Robotlegs uses native flash events for communication between framework actors. Custom events are typically utilized for this purpose, it is however possible to use existing Flash events for this same purpose. Robotlegs does not support Event bubbling, as it does not depend on the Flash display list as an event bus. Utilizing custom events allows developers to add properties to the Event that can be used as strongly typed payloads for system events between framework actors. 56 | 57 | Events are sent from all framework actors: Mediators, Services, Models, and Commands. Mediators are the only actors that receive framework events. Commands are triggered in response to framework events. An event can be both received by a Mediator as well as trigger a command. 58 | 59 | Model and service classes should not listen for or respond to events. Doing so would tightly couple them to application specific logic and reduce the potential for portability and reuse. 60 | 61 | -------------------------------------------------------------------------------- /best-practices-en/6_commands.textile: -------------------------------------------------------------------------------- 1 | h2(#commands). Commands 2 | 3 | Commands are short-lived stateless objects. They are instantiated, executed and then immediately disposed of. Commands are only executed in response to framework events and should never be instantiated or executed by other framework actors. 4 | 5 | h3(#commandresponsibilities). Command Responsibilities 6 | 7 | Commands are registered to a Context via that Context's CommandMap. The CommandMap is available by default in Context and Command classes. Commands are registered to the Context with an Event type, the Command class to execute in response to the Event, and optionally the Event class and a one off setting for when a Command should be executed once, and then unregistered for future occurrences of an Event. 8 | 9 | h3(#triggeringcommands). Triggering Commands 10 | 11 | Commands are triggered by framework events dispatched by Mediators, Services, Models, and other Commands. Typically the Event that triggered the Command is injected into the Command giving the Command access to the Event's properties/payload: 12 | 13 |
14 | public class MyCommand extends Command
15 | {
16 | 	[Inject]
17 | 	public var event:MyCustomEvent;
18 | 	
19 | 	[Inject]
20 | 	public var model:MyModel;
21 | 			
22 | 	override public function execute():void
23 | 	{
24 | 		model.updateData( event.myCustomEventPayload )
25 | 	}
26 | }
27 | 
28 | 29 | When the mapped command is instantiated in response to a framework event, all of the dependencies that have been mapped and marked with the [Inject] metadata tag are injected into the Command. In addition, the event instance that triggered the Command is also injected. After these dependencies have been supplied, the executed method is called automatically and the Command's work is performed. It is not necessary, and should never be done, to call the execute() method directly. This is the framework implementation's job. 30 | 31 | h3(#chainingcommands). Chaining Commands 32 | 33 | It is also possible to chain commands: 34 | 35 |
36 | public class MyChainedCommand extends Command
37 | {
38 | 	[Inject]
39 | 	public var event:MyCustomEvent;
40 | 	
41 | 	[Inject]
42 | 	public var model:MyModel;
43 | 			
44 | 	override public function execute():void
45 | 	{
46 | 		model.updateData( event.myCustomEventPayload )
47 | 		
48 | 		//the UPDATED_WITH_NEW_STUFF event triggers a command and is also received by
49 | 		//a mediator to update a View Component, but only if a response is requested
50 | 		if(event.responseNeeded)
51 | 		    dispatch( new MyCustomEvent( MyCustomEvent.UPDATED_WITH_NEW_STUFF, model.getCalculatedResponse() ) )
52 | 	}
53 | }
54 | 
55 | 56 | Using this approach it is possible to chain as many Commands as needed together. In the example above a conditional statement is used. If the condition is not met, the Command is not chained. This provides extreme flexibility within your Commands to perform work on your application. 57 | 58 | h3(#decouplingtiers). Decoupling Application Tiers 59 | 60 | Commands are a very useful mechanism for decoupling the various actors of an application. Because a Command is never instantiated or executed from a Mediator, Model or Service, these classes are never coupled to, or even aware of the existence of Commands. 61 | 62 | To perform their duties, Commands may: 63 | 64 | * Map Mediators, Models, Services, or other Commands within their Context 65 | * Dispatch Events to be received by Mediators or trigger other Commands 66 | * Be injected with Models, Services, and Mediators to perform work on directly 67 | 68 | bq(note). Something to note is that it is not recommended to interact directly with Mediators in a Command. While it is possible, it will couple that Mediator to that Command. Since Mediators, unlike Services and Models, are able to receive system Events, the better practice is to simply dispatch an Event from the Command and listen for it on Mediators that need to respond to the Events. 69 | 70 | -------------------------------------------------------------------------------- /best-practices-en/7_mediators.textile: -------------------------------------------------------------------------------- 1 | h2(#mediators). Mediators 2 | 3 | The Mediator class is used to mediate a user's interaction with an application's View Components. A Mediator can perform this duty at multiple levels of granularity, mediating an entire application and all of its sub-components, or any and all of an application's sub-components directly. 4 | 5 | h3(#mediatorresponsibilities). Mediator Responsibilities 6 | 7 | Flash, Flex and AIR applications provide virtually limitless possibilities for rich visual user interface components. All of these platforms provide out of the box components such as DataGrids, Buttons, Labels and other common UI components. It is also possible to extend these basic components into custom components, create composite components, or write components from scratch. 8 | 9 | A View Component is any UI component and/or its sub-components. A View Component is encapsulated, handling its own state and operations as much as possible. A View Component provides an API via events, simple methods, and properties upon which Mediators act upon to affect the View Component within a Context. Mediators are responsible for interacting with the framework on behalf of the View Components that they mediate. This includes listening for Events on the components and their sub-components, accessing methods, and reading/setting properties on the components. 10 | 11 | A Mediator listens for Events on its View Component, and accesses data directly on the View Component via its exposed API. A Mediators acts on behalf of other framework actors by responding to their Events and modifying its View Component accordingly. A Mediator notifies other framework actors of Events created by the View Component by relaying those Events, or dispatching appropriate Events to the framework. 12 | 13 | h3(#mappingamediator). Mapping a Mediator 14 | 15 | A Mediator can be mapped in any class that has has the _mediatorMap_ instance available. This includes the Mediator, Context, and Command classes. 16 | 17 | This is the syntax for mapping a mediator: 18 |
mediatorMap.mapView( ViewClass, MediatorClass, autoCreate, autoRemove );
19 | 20 | h3(#automaticmediation). Automatic Mediation of View Components 21 | 22 | When a view component class is mapped for mediation, you can specify if you would like to have the Mediator for the class created automatically. When this option is _true_ the context will listen for the view component instance to dispatch its ADDED_TO_STAGE event. When this event is received, the view component will be automatically mediated and its mediator can begin to send and receive framework events. 23 | 24 | h3(#manualmediation). Manually Mediation of View Components 25 | 26 | There are occasions where the automatic mediation of view components is not desired, or impossible. In these cases, it is possible to manually create the Mediator instance for a class: 27 | 28 |
mediatorMap.createMediator(contextView);
29 | 30 | The above assumes that the view component was previously mapped to a mediator using the _mapView()_ method of the _mediatorMap_. 31 | 32 | h3(#mappingthecontextview). Mapping the Main Application (_contextView_) Mediator 33 | 34 | It is a common pattern to map the contextView to a mediator. This is a special situation, as the automatic mediation cannot be performed on the contextView, as it is already added to the stage and will no longer fire the appropriate events the _mediatorMap_ uses to provide this convenience. Typically, this mapping can be done inside the _startup()_ method of the Context that holds a reference to the _contextView_: 35 | 36 |
override public function startup():void
 37 | {
 38 | 	mediatorMap.mapView(MediateApplicationExample, AppMediator);
 39 | 	mediatorMap.createMediator(contextView);
 40 | }
41 | 42 | The _contextView_ is now fully mediated and can send and receive framework events. 43 | 44 | h3(#accessingmediatorviewcomponent). Accessing a Mediator's View Component 45 | 46 | When a View Component is added to the stage within a Context's contextView, it is by default mediated automatically based on configuration supplied to the MediatorMap when the mapping was made. In a basic mediator, the _viewComponent_ property is injected with the view component that is being mediated. A Mediator's _viewComponent_ property is of type Object. In most cases, we want access to a strongly typed object to receive the benefits provided by using strongly typed objects. To achieve this, we inject the typed instance of the view component that is being mediated: 47 | 48 |
public class GalleryLabelMediator extends Mediator implements IMediator
 49 | {
 50 | 	[Inject]
 51 | 	public var myCustomComponent:MyCustomComponent;
 52 | 		
 53 | 	/**
 54 | 	* overriding the onRegister method is a good chance to
 55 | 	* add any system or View Component Events the Mediator
 56 | 	* is interested in receiving.
 57 | 	*/
 58 | 	override public function onRegister():void
 59 | 	{
 60 | 		//adding an event listener to the Context for framework events
 61 | 		eventMap.mapListener( eventDispatcher, MyCustomEvent.DO_STUFF, handleDoStuff );
 62 | 		//adding an event listener to the view component being mediated
 63 | 		eventMap.mapListener( myCustomComponent, MyCustomEvent.DID_SOME_STUFF, handleDidSomeStuff)
 64 | 	}
 65 | 	
 66 | 	protected function handleDoStuff(event:MyCustomEvent):void
 67 | 	{
 68 | 		//setting a property on the view component from the
 69 | 		//strongly typed event payload. The view component
 70 | 		//will likely manage its own state based on this
 71 | 		//new data.
 72 | 		myCustomComponent.aProperty = event.payload
 73 | 	}
 74 | 	
 75 | 	protected function handleDidSomeStuff(event:MyCustomEvent):void
 76 | 	{
 77 | 		//relaying the event to the framework
 78 | 		dispatch(event)
 79 | 	}
 80 | }
81 | 82 | Following this approach we now have easy direct access to the public properties and methods of the mediated view component. 83 | 84 | h3(#addingeventlistenerstomediators). Adding Event Listeners to a Mediator 85 | 86 | Event listeners are the eyes and ears of concrete Mediators. Since all communication within the framework is handled via native Flash events, event listeners will be placed on Mediators to respond to their interests. In addition to framework events, Mediators listen for events from the view components that they are actively mediating. 87 | 88 | It is common to add event listeners in the onRegister method of the Mediator. At this phase of the Mediator's lifecycle, it has been registered and its view component and other dependencies have been injected. The onRegister method must be overridden in concrete Mediator classes. Event listeners may be added in other methods as well, including event handler methods that are responding to both framework and view component events. 89 | 90 | Mediators are equipped with an EventMap that has a method mapListener(). This method registers each event added to the Mediator, and ensures that the event is removed when the mediator is unregistered from the framework. It is important to remove events in Flash, as events that are added, but not removed from a class eliminate the Player's ability to perform runtime Garbage Collection on that class. It is possible to add your event listeners with the traditional Flash syntax, but be aware that you will also need to remove them manually as well. 91 | 92 | h3(#mediatorslisteningfroframeworkevents). Listening for Framework Events 93 | 94 | All of the actors in the framework carry an _eventDispatcher_ property that is injected into the class when it has been instantiated. The _eventDispatcher_ is a Mediator's mechanism for sending and receiving framework events. 95 | 96 |
eventMap.mapListener(eventDispatcher, SomeEvent.IT_IS_IMPORTANT, handleFrameworkEvent)
97 | 98 | Using this syntax, a Mediator is now listening for _SomeEvent.IT_IS_IMPORTANT_ which will be handled by a method called _handleFrameworkEvent_ 99 | 100 | h3(#mediatorsdispatchingframeworkevents). Dispatching Framework Events 101 | 102 | An equally important duty of a Mediator is sending out events to the framework that other actors might be interested in. These events are generally sent in response to some interaction with the mediated view component by the user of the application. Again, a convenience method is supplied to reduce some of the typing necessary to dispatch an event to the framework 103 | 104 |
dispatch(new SomeEvent(SomeEvent.YOU_WILL_WANT_THIS, myViewComponent.someData))
105 | 106 | This event can now be received by other Mediators or execute a command. The Mediator that dispatched the event is not concerned with how other actors within the application will respond to the event, it is simply broadcasting the message that something has occurred. A mediator may also listen for the events that it dispatches, and respond to them accordingly. 107 | 108 | h3(#mediatorslisteningforcomponentevents). Listening for View Component Events 109 | 110 | A Mediator is responsible for listening to events dispatched by the view component being mediated. This can be a single component, such as a TextField or Button, or a complex hierarchy of nested components. When a view component event has been added to a mediator it will be handled by the method designated to handle the event. As with framework events, the EventMap's mapListener method is the preferred syntax for adding event listeners to a mediator: 111 | 112 |
eventMap.mapListener(myMediatedViewComponent, SomeEvent.USER_DID_SOMETHING, handleUserDidSomethingEvent)
113 | 114 | In response to an event received from a view component, a mediator might: 115 | 116 | * examine the payload of the event (if it exists) 117 | * examine the current state of the view component 118 | * perform work on the view component as required 119 | * send framework events to notify other actors that something has occurred 120 | 121 | h3(#accessingmodelsandservicesfrommediators). Accessing Models and Services via a Mediator 122 | 123 | To promote loose coupling your mediators can listen for system events that will be dispatched by Service and Model classes. By listening for events, your mediators do not need to be interested in where these events originate from, they just make use of the strongly typed payload the event carries with it. For this purpose, multiple mediators can be listening for the same event, adjusting their state according to the data that they have received. 124 | 125 | Directly accessing services through a mediator can provide convenience, without serious risk of coupling. A service is not storing data, simply providing an API for making requests to an external service and receiving the response. Being able to access this API directly can save your application from unnecessary command classes to achieve the same goal. If the service API is repeatedly accessed in the same way from many mediators, it can be beneficial to encapsulate this behavior in a command to keep the behavior consistent and reduce the repetition of injecting the service and accessing it directly in your mediators. 126 | 127 | It is recommended that models and services injected directly into mediators are done so via the interfaces the service and model classes implement. An example of this can be found in the "Example Service":#serviceexample section below. 128 | 129 | h3(#accessingothermediatorsfromamediator). Accessing Other Mediators 130 | 131 | As with Services and Models, it is possible to inject and access other Mediators in a Mediator. This practice is *highly discouraged* as the tight coupling can easily be avoided by communication through framework events. 132 | 133 | -------------------------------------------------------------------------------- /best-practices-en/8_models.textile: -------------------------------------------------------------------------------- 1 | h2(#models). Models 2 | 3 | A model class is used to manage access to an application's data model. A model provides an API that is used by other framework actors to access, manipulate, and update application data. This data includes, but is not limited to, native data types such as String, Array, or ArrayCollection as well as domain specific Objects or collections of these. 4 | 5 | Model are referred to as simply Model, as in UserModel, and at other times they might be referred to as Proxy as in UserProxy. In Robotlegs, both of these naming conventions are used for the same purpose. Providing an API for an applications data. Regardless of the naming convention models will extend the Actor base class which provides core framework dependencies as well as convenience helper methods your models can make use of. This document will refer to these classes as Model. 6 | 7 | h3(#modelresponsibilities). Model Responsibilities 8 | 9 | Model classes encapsulate and provide an API for the application data model. A Model class is the gatekeeper for your application's data. Other actors in the application make requests for data through the API provided by the Model. As data is updated through the Model, the Model is equipped to broadcast events to the framework informing other actors of changes to the data model so they may adjust their state accordingly. 10 | 11 | In addition to controlling access to the data model, the Model is routinely used to perform operations on the data to keep the data in a valid state. This includes performing calculations on the data, or other areas of domain specific logic. This responsibility of the Model is extremely important. The Model is the tier of any given application with the highest potential for portability. By placing domain logic on the Model, future implementations of the model will not have to repeat this same logic as they would if it was placed in the View or Controller tiers. 12 | 13 | As an example, your Model class might perform a sales tax calculation on the shopping cart data that it is string. A Command will access this method, and the final calculation will be dispatched as an event that a Mediator is listening for. The mediator will then update its view component with the updated value. In the first iteration of the application it was a typical Flex application. This calculation could have easily been performed on a Mediator, or even the view itself. The second iteration of the application is a mobile Flash application that requires an entirely new view form factor. Since this logic is contained in the Model, it can be reused for both form factors with entirely different views. 14 | 15 | h3(#mappingamodel). Mapping a Model 16 | 17 | There are several methods available on the injector for mapping your Model classes for injection into your framework actors. In addition, these methods can be used for injecting virtually ANY class into your classes. 18 | 19 | To map an existing instance for injection that will be treated as a singleton, use the following syntax: 20 |
injector.mapValue(MyModelClass, myModelClassInstance)
21 | 22 | To map a new instance of a class for each injection, use the following syntax: 23 |
injector.mapClass(MyModelClass, MyModelClass)
24 | Additionally, this can be used to map interfaces for injection, with a concrete class that implements the interface being injected: 25 |
injector.mapClass(IMyModelClass, MyModelClass)
26 | 27 | To map a singleton instance of an interface or class, use the following syntax: 28 |
injector.mapSingleton(MyModelClass, MyModelClass)
29 | 30 | It is important to note that when referring to a singleton above, it is not a Singleton. It is not enforced outside of the Context as a Singleton. The injector simply insures that one and only one instance of the class will be injected. This is vital for Model classes that are handling your application data model. 31 | 32 | h3(#dispatchingeventsfrommodel). Dispatching Events from a Model 33 | 34 | The Model class provides a convenience method _dispatch_ for sending framework events: 35 | 36 |
dispatch( new ImportantDataEvent(ImportantDataEvent.IMPORTANT_DATA_UPDATED))
37 | 38 | Events can be dispatched for any number of reasons, including but not limited to: 39 | 40 | * Data has been initialized and is ready for other actors to use 41 | * Some piece of data has been added to the Model 42 | * Data has been removed from the Model 43 | * Data has changed or updated 44 | * State has been changed related to the data 45 | 46 | h3(#listeningforeventsinmodel). Listening for Framework Events in a Model 47 | 48 | While this is technically possible it is *highly discouraged*. Don't do it. Just for the sake of clarity: *Don't do it*. If you do, don't say you weren't warned. 49 | 50 | -------------------------------------------------------------------------------- /best-practices-en/9_services.textile: -------------------------------------------------------------------------------- 1 | h2(#services). Services 2 | 3 | Services are utilized to access resources outside of the scope of the application. This is including, but certainly not limited to: 4 | 5 | * web services 6 | * file system 7 | * data bases 8 | * RESTful APIs 9 | * other Flash applications via localConnection 10 | 11 | Services encapsulate this interaction with external entities, and manage the results, faults, and other events that result from this interaction. 12 | 13 | You might notice that the Service and Model base classes are very similar. In fact, you might notice that outside of the class name, they are exactly the same. Why have two classes then? Model and Service classes have entirely different responsibilities within an application. The concrete implementations of these classes will not be similar. Without this separation, you will generally find external service access being performed on Model classes. This creates Models that have the multiple duty of accessing external data, parsing results, handling faults, managing application data state, providing an API for data, providing an API for the service, etc. Separating these tiers helps to alleviate this problem. 14 | 15 | h3(#serviceresponsibilities). Service Responsibilities 16 | 17 | A Service class provides your application with an API for interacting with an external service. A service class will contact the external service ad manage the response that it receives. Services are typically stateless entities. They do not store the data that is returned from an external service, but instead send framework events so that response data and faults can be managed by the appropriate framework actors. 18 | 19 | h3(#mappingservice). Mapping a Service 20 | 21 | There are "several methods available on the injector":#mappingwithinjector for mapping your Service classes for injection into your framework actors. In addition, these methods can be used for injecting virtually ANY class into your classes. 22 | 23 | To map an existing instance for injection that will be treated as a singleton, use the following syntax: 24 |
injector.mapValue(MyServiceClass, myServiceClassInstance)
25 | 26 | To map a new instance of a class for each injection, use the following syntax: 27 |
injector.mapClass(MyServiceClass, MyServiceClass)
28 | Additionally, this can be used to map interfaces for injection, with a concrete class that implements the interface being injected: 29 |
injector.mapClass(IMyServiceClass, MyServiceClass)
30 | 31 | To map a singleton instance of an interface or class, use the following syntax: 32 |
injector.mapSingleton(MyServiceClass, MyServiceClass)
33 | 34 | It is important to note that when referring to a singleton above, it is not a Singleton. It is not enforced outside of the Context as a Singleton. The injector simply insures that one and only one instance of the class will be injected. 35 | 36 | h3(#servicelisteningforframeworkevents). Listening for Framework Events in a Service 37 | 38 | While this is technically possible it is *highly discouraged*. Don't do it. Just for the sake of clarity: *Don't do it*. If you do, don't say you weren't warned. 39 | 40 | h3(#servicedispatchingframeworkevents). Dispatching Framework Events 41 | 42 | The Service class provides a convenience method _dispatch_ for sending framework events: 43 | 44 |
dispatch( new ImportantServiceEvent(ImportantServiceEvent.IMPORTANT_SERVICE_EVENT))
45 | 46 | h3(#serviceexample). Example Service 47 | 48 | The following is the Flickr service class from the "Image Gallery":http://github.com/robotlegs/robotlegs-demos-Bundle/tree/master/FlickrImageGallery/ demo. "The Flickr API AS3 Library":http://code.google.com/p/as3flickrlib/ does a lot of the low level heavy lifting needed to connect to Flickr. This example makes use of that and provides a simple abstraction for use within the scope of the example. 49 | 50 |
package org.robotlegs.demos.imagegallery.remote.services
 51 | {
 52 | 	import com.adobe.webapis.flickr.FlickrService;
 53 | 	import com.adobe.webapis.flickr.Photo;
 54 | 	import com.adobe.webapis.flickr.events.FlickrResultEvent;
 55 | 	import com.adobe.webapis.flickr.methodgroups.Photos;
 56 | 	import com.adobe.webapis.flickr.methodgroups.helpers.PhotoSearchParams;
 57 | 	
 58 | 	import org.robotlegs.demos.imagegallery.events.GalleryEvent;
 59 | 	import org.robotlegs.demos.imagegallery.models.vo.Gallery;
 60 | 	import org.robotlegs.demos.imagegallery.models.vo.GalleryImage;
 61 | 	import org.robotlegs.mvcs.Actor;
 62 | 
 63 |     /**
 64 |      *  This class utilizes the Flickr API provided by Adobe to connect
 65 |      *  to Flickr and retrieve images. It initially loads the current top
 66 |      *  "interestingness" photos. It also provides the ability to search
 67 |      *  for a specific keyword.    
 68 |      */
 69 | 	public class FlickrImageService extends Actor implements IGalleryImageService
 70 | 	{
 71 | 		private var service:FlickrService;
 72 | 		private var photos:Photos;
 73 | 		
 74 | 		protected static const FLICKR_API_KEY:String = "516ab798392cb79523691e6dd79005c2";
 75 | 		protected static const FLICKR_SECRET:String = "8f7e19a3ae7a25c9";
 76 | 		
 77 | 		public function FlickrImageService()
 78 | 		{
 79 | 			this.service = new FlickrService(FLICKR_API_KEY);
 80 | 		}
 81 | 		
 82 | 		public function get searchAvailable():Boolean
 83 | 		{
 84 | 			return true;
 85 | 		}
 86 | 				
 87 | 		public function loadGallery():void
 88 | 		{
 89 | 			service.addEventListener(FlickrResultEvent.INTERESTINGNESS_GET_LIST, handleSearchResult);
 90 | 			service.interestingness.getList(null,"",20)
 91 | 		}
 92 | 		
 93 | 		public function search(searchTerm:String):void
 94 | 		{
 95 | 			if(!this.photos)
 96 | 				this.photos = new Photos(this.service);
 97 | 			service.addEventListener(FlickrResultEvent.PHOTOS_SEARCH, handleSearchResult);
 98 | 			var p:PhotoSearchParams = new PhotoSearchParams()
 99 | 			p.text = searchTerm;
100 | 			p.per_page = 20;
101 | 			p.content_type = 1;
102 | 			p.media = "photo"
103 | 			p.sort = "date-posted-desc";
104 | 			this.photos.searchWithParamHelper(p);				
105 | 		}
106 | 		
107 | 		protected function handleSearchResult(event:FlickrResultEvent):void
108 | 		{
109 | 			this.processFlickrPhotoResults(event.data.photos.photos);
110 | 		}
111 | 		
112 | 		protected function processFlickrPhotoResults(results:Array):void
113 | 		{
114 | 			var gallery:Gallery = new Gallery();
115 | 						
116 | 			for each(var flickrPhoto:Photo in results)
117 | 			{
118 | 				var photo:GalleryImage = new GalleryImage()
119 | 				var baseURL:String = 'http://farm' + flickrPhoto.farmId + '.static.flickr.com/' + flickrPhoto.server + '/' + flickrPhoto.id + '_' + flickrPhoto.secret;
120 | 				photo.thumbURL = baseURL + '_s.jpg';
121 | 				photo.URL = baseURL + '.jpg';
122 | 				gallery.photos.addItem( photo );
123 | 			}
124 | 			
125 | 			dispatch(new GalleryEvent(GalleryEvent.GALLERY_LOADED, gallery));
126 | 		}
127 | 		
128 | 	}
129 | }
130 | 131 | The FlickrGalleryService provides a very simple interface for connecting to a gallery service. The application can _loadGallery_, _search_, and find out if _searchAvailable_ is true or false. This interface is defined by the IGalleryService interface: 132 | 133 |
package org.robotlegs.demos.imagegallery.remote.services
134 | {
135 | 	public interface IGalleryImageService
136 | 	{
137 | 		function loadGallery():void;
138 | 		function search(searchTerm:String):void;
139 | 		function get searchAvailable():Boolean;
140 | 	}
141 | }
142 | 143 | h4(#serviceimplementsinterface). Services Should Implement an Interface 144 | 145 | By creating service classes that implement interfaces, it makes it trivial to switch them out at runtime for testing or providing access to additional services to the end users of the application. For example, the FlickrGalleryService can be easily substituted for an XMLGalleryService: 146 | 147 |
package org.robotlegs.demos.imagegallery.remote.services
148 | {
149 | 	import mx.rpc.AsyncToken;
150 | 	import mx.rpc.Responder;
151 | 	import mx.rpc.http.HTTPService;
152 | 	
153 | 	import org.robotlegs.demos.imagegallery.events.GalleryEvent;
154 | 	import org.robotlegs.demos.imagegallery.models.vo.Gallery;
155 | 	import org.robotlegs.demos.imagegallery.models.vo.GalleryImage;
156 | 	import org.robotlegs.mvcs.Actor;
157 | 
158 | 	public class XMLImageService extends Actor implements IGalleryImageService
159 | 	{
160 | 		protected static const BASE_URL:String = "assets/gallery/";
161 | 		
162 | 		public function XMLImageService()
163 | 		{
164 | 			super();
165 | 		}
166 | 
167 | 		public function get searchAvailable():Boolean
168 | 		{
169 | 			return false;
170 | 		}
171 | 				
172 | 		public function loadGallery():void
173 | 		{
174 | 			var service:HTTPService = new HTTPService();
175 | 			var responder:Responder = new Responder(handleServiceResult, handleServiceFault);
176 | 			var token:AsyncToken;
177 | 			service.resultFormat = "e4x";
178 | 			service.url = BASE_URL+"gallery.xml";
179 | 			token = service.send();
180 | 			token.addResponder(responder);
181 | 		}
182 | 		
183 | 		public function search(searchTerm:String):void
184 | 		{
185 | 			trace("search is not available");
186 | 		}
187 | 		
188 | 		protected function handleServiceResult(event:Object):void
189 | 		{
190 | 			var gallery:Gallery = new Gallery();
191 | 						
192 | 			for each(var image:XML in event.result.image)
193 | 			{
194 | 				var photo:GalleryImage = new GalleryImage()
195 | 				photo.thumbURL = BASE_URL + "images/" + image.@name + '_s.jpg';
196 | 				photo.URL = BASE_URL + "images/" + image.@name + '.jpg';
197 | 				gallery.photos.addItem( photo );
198 | 			}
199 | 			
200 | 			dispatchEvent(new GalleryEvent(GalleryEvent.GALLERY_LOADED, gallery));
201 | 		}
202 | 		
203 | 		protected function handleServiceFault(event:Object):void
204 | 		{
205 | 			trace(event);
206 | 		}
207 | 	}
208 | }
209 | 210 | The XML gallery provides access to the same methods as the Flickr and can now be substituted where ever the IGalleryService interface is called for. The services dispatch the same events, and are practically indistinguishable in the final application. In this example, search was not implemented, but the search functionality could be easily implemented in this service as well. 211 | 212 | It is recommended that all services implement an interface that defines their API. In framework actors that receive a service as an injected dependency should ask for the interface, and not the concrete implementation: 213 | 214 |
injector.mapSingletonOf(IGalleryService, FlickrGalleryService);
215 | 216 |
[Inject]
217 | public var galleryService:IGalleryService
218 | 219 | You can easily replace the gallery service utilized by your classes by simply changing the injection: 220 | 221 |
injector.mapSingletonOf(IGalleryService, XMLGalleryService);
222 | 223 | This approach can provide power, flexibility, and enhanced testability to an application. 224 | 225 | h4(#parsingresultsinservice). Parsing Data in a Service 226 | 227 | In the above example service classes or external services provide objects that are not representative of the application domain. The Flickr service provides strongly typed Photo objects and the XML service provides... xml. Both of these data types are perfectly usable, but do not necessarily fit into the context of our application. They are foreigners. The choice is between molding the application around these external data types, or preferably, convert these types into custom data types that are representative of the application. 228 | 229 | There are two places where this manipulation/conversion should take place in the application. The Service or the Model would both be appropriate. The Service is the first point of entry for external data, and this fact makes it a better choice for manipulating the data returned by a service. The foreign data should be converted to the application domain at the first opportunity. 230 | 231 | _provide an example of using a factory class to produce the application domain objects instead of doing it in the service... proper_ 232 | 233 | After the data has been converted to the objects specific to the application domain events are sent with strongly typed payloads to be immediately utilized by interested actors. 234 | 235 | h4(#serviceevents). Service Events 236 | 237 | The final corner of the service triad is the custom event. Without events, services are mute. Any work they might do will go unnoticed by the other application actors. A service will make use of custom events to provide itself with a voice to the broader application. Events to not have to be singular in purpose. If the service is transforming the data it can make use of a common event to dispatch strongly typed data to interested application actors. 238 | 239 | 240 | -------------------------------------------------------------------------------- /best-practices-zh-cn.textile: -------------------------------------------------------------------------------- 1 | 2 |
!=http://joelhooks.com/wp-content/uploads/2009/07/robotlegssketchsmall.gif!

Documentation for Robotlegs v1.0RC1

3 | h2. 目录 4 | 5 | # *"Robotlegs 是什么":#whatisrobotlegs* 6 | # "依赖注入":#dependencyinjection 7 | # "使用 Injectors":#usingtheinjectors 8 | ** "SwiftSuspenders 适配器注入语法":#injectionsyntax 9 | ** "Injector 类的映射注入":#mappingwithinjector 10 | ** "MediatorMap 类的依赖注入":#mappingwithmediatormap 11 | ** "CommandMap 类的依赖注入":#mappingwithcommandmap 12 | # "The Context":#thecontext 13 | # *"MVCS 参考实现":#mvcs* 14 | ## "Context":#context 15 | ## "Controller & Commands":#controllersandcommands 16 | ## "View & Mediators":#viewandmediators 17 | ## "Model, Service and the Actor":#modelandservice 18 | ## "Model":#modelandmodels 19 | ## "Service":#serviceandservices 20 | ## "框架事件":#frameworkevents 21 | ## *"Commands":#commands* 22 | ### "Command 职责":#commandresponsibilities 23 | ### "触发 Command":#triggeringcommands 24 | ### "链接 Command":#chainingcommands 25 | ### "应用程序层的解耦":#decouplingtiers 26 | ## *"Mediators":#mediators* 27 | ### "Mediator 职责":#mediatorresponsibilities 28 | ### "映射一个 Mediator":#mappingamediator 29 | ### "View Component 的自动中介":#automaticmediation 30 | ### "View Component 的手动中介":#manualmediation 31 | ### "映射主程序 (_contextView_) Mediator":#mappingthecontextview 32 | ### "访问一个 Mediator 的 View Component":#accessingmediatorviewcomponent 33 | ### "给一个 Mediator 添加事件监听":#addingeventlistenerstomediators 34 | ### "监听框架事件":#mediatorslisteningfroframeworkevents 35 | ### "广播框架事件":#Mediatorsdispatchingframeworkevents 36 | ### "监听 View Component 事件":#mediatorslisteningforcomponentevents 37 | ### "通过 Mediator 访问 Model 和 Service":#accessingmodelsandservicesfrommediators 38 | ### "访问其它 Mediator":#accessingothermediatorsfromamediator 39 | ## *"Models":#models* 40 | ### "Model 职责":#modelresponsibilities 41 | ### "映射一个 Model":#mappingamodel 42 | ### "从一个Model里广播事件":#dispatchingeventsfrommodel 43 | ### "在一个 Model 里监听框架事件":#listeningforeventsinmodel 44 | ## *"Services":#services* 45 | ### "Service 职责":#serviceresponsibilities 46 | ### "映射一个 Service":#mappingservice 47 | ### "在一个 Service 里监听框架事件":#servicelisteningforframeworkevents 48 | ### "广播框架事件":#servicedispatchingframeworkevents 49 | ### "Service 示例":#serviceexample 50 | **** "Services 应该实现一个接口":#serviceimplementsinterface 51 | **** "在一个 Service 里解析数据":#parsingresultsinservice 52 | **** "Service 事件":#serviceevents 53 | 54 | 55 | h2(#whatisrobotlegs). Robotlegs 是什么 56 | 57 | Robotlegs 是一个用来开发Flash, Flex, 和 AIR 应用的纯 AS3 微架构(框架). Robotlegs 专注于将应用程序各层排布在一起并提供它们相互通讯的机制. Robotlegs 试图通过提供一种解决常见开发问题的经过时间检验的架构解决方案来加速开发. Robotlegs 无意锁定你到框架, 你的类就是你的类的样子, 而且应该很容易地切换到其他框架. 58 | 59 | 框架提供一个基于 "Model-View-Controller":http://en.wikipedia.org/wiki/Model–view–controller 元设计模式的默认实现. 这个实现提供一个针对应用程序结构和设计的强烈建议. 虽然它确实轻微减低了你的应用程序的便携性, 不过它依然以最低限度影响你的具体类为目标. 通过扩展 "MVCS":#mvcs 实现类, 你可以获得很多有用的方法和属性. 60 | 61 | 你不必使用Robotlegs的标准 "MVCS":#mvcs 实现.你可以使用它的任意部分, 或者完全不使用它, 或者使用自己的实现来适应你的需求. 它是为了提供合适的参考实现和快速开始使用 Robotlegs 而被包含进来。 62 | 63 | 64 | h2(#dependencyinjection). 依赖注入 65 | 66 | Robotlegs 围绕 "依赖注入":http://www.insideria.com/2009/09/as3-dependency-injection-demys.html 设计模式展开. 67 | 68 | bq. 最简单地, 依赖注入是为对象提供实例变量或属性的行为. 当你传递一个变量到一个类的构造函数, 你在使用依赖注入. 当你设置一个类的属性, 你在使用依赖注入. 如果你不是使用严格的过程或线性方式编写AS3, 很可能你现在就在使用依赖注入。 69 | 70 | Robotlegs 使用基于元数据的自动依赖注入. 这是为了方便开发而提供, 而且在排布应用程序并提供类和它所需要的依赖时,可以减少很多代码量. 虽然完全可以手动提供这些依赖, 但是允许框架来履行这些职责可以减少出错的机会,并且通常可以加快编码进程。. 71 | 72 | 73 | h2(#usingtheinjectors). 使用 Injectors 74 | 75 | Robotlegs 采用一种适配器(adapter)机制来为框架提供依赖注入机制. 默认地, 框架配备了 "SwiftSuspenders":http://github.com/tschneidereit/SwiftSuspenders 注入/反射库来适合这个要求. 另有 SmartyPants-IoC 和 Spring Actionscript 的适配器可以使用. 可能有潜在的特定需求来使用其它的依赖注入适配器, 但是如果没有特别的理由, 建议使用默认的 "SwiftSuspenders":http://github.com/tschneidereit/SwiftSuspenders, 因为它为 Robotlegs 做了一些特别调整. 76 | 77 | h3(#injectionsyntax). SwiftSuspenders 适配器注入语法 78 | 79 | SwiftSuspenders 支持三种类型的依赖注入 80 | 81 | * 属性(域)注入 82 | * 参数(方法/设值) 注入 83 | * 构造注入 84 | 85 | 鉴于此文档的目的, 我们将特别介绍属性注入, 以及在 Robotlegs 里如何使用. 将属性注入类有两种选择. 你可以使用未命名,或命名的注入: 86 | 87 |
[Inject]
 88 | public var myDependency:Depedency; //未命名注入
89 | 90 |
[Inject(name="myNamedDependency")]
 91 | public var myNamedDependency:NamedDepedency; //命名注入
92 | 93 | Robotlegs 里三处提供了注入映射. MediatorMap, CommandMap, 和直接通过 Injector . MediatorMap 和 CommandMap 也都是使用 Injector, 但它们同时做了一些各自层(tier)所需要的额外工作. 顾名思义, MediatorMap 用来映射 Mediator, CommandMap 用来映射 Command, 其它所有需要被注入的内容 (包括但不限于 Model) 都要直接使用 Injector 映射. 94 | 95 | 96 | h3(#mappingwithinjector). Injector 类的映射注入 97 | 98 | 具体 Injector 类的适配器都遵照 IInjector 接口. 这个接口为不同的依赖注入解决方案提供了统一的API. 本文档专注于 SwiftSuspenders, 但这些语法同样适应于其它任何遵照 Iinjector 接口的 Injector. 99 | 100 | _injector_ 是你应用程序里所发生的所有依赖注入的生产车间. 它用来注入框架 actor, 同时也可以用来执行你的应用程序所需要的任何其它注入. 这包括但不限于 RemoteObjects, HTTPServices, 工厂类, 或者事实上任何有可能成为你的对象所需要的依赖的类/接口. 101 | 102 | 下面是实现 IInjector 接口的类所提供的四个映射方法: 103 | 104 | h4. mapValue 105 | 106 | mapValue 用来映射一个对象的特定实例到一个 injector. 当请求一个特定的类,使用类的这个特定实例来注入. 107 | 108 |
//你的应用程序中某个映射/配置发生的地方
109 | var myClassInstance:MyClass = new MyClass();
110 | injector.mapValue(MyClass, myClassInstance);
111 | 112 |
//在接收注入的类中
113 | [Inject]
114 | public var myClassInstance:MyClass
115 | 116 |
mapValue(whenAskedFor:Class, instantiateClass:Class, named:String = null)
117 | 118 | MyClass 的实例被创建和保留,并在被请求的时候被注入. 当此类被请求的时候,这个实例被用来满足这个注入请求. 请注意很重要的一点,因为你已经手动创建并通过 mapValue 映射了一个类实例, 这个实例所需要的依赖将不会被自动注入. 你需要手动或通过 injector 注入这些依赖: 119 | 120 |
injector.injectInto(myClassInstance);
121 | 122 | 这将立即为此实例提供已映射的可以注入的属性。. 123 | 124 | h4. mapClass 125 | 126 | mapClass 为每一个注入请求提供这个被映射的类的一个 _特有_( _unique_) 实例. 127 | 128 |
//你的应用程序中某个映射/配置发生的地方
129 | injector.mapClass(MyClass);
130 | 131 | 132 |
//在第一个接收注入的类里
133 | [Inject]
134 | public var myClassInstance:MyClass
135 | 136 | 137 |
//在第二个接收注入的类里
138 | [Inject]
139 | public var myClassInstance:MyClass
140 | 141 | 为上面的每一个注入提供MyClass的 _特有_( _unique_) 实例来完成请求. 142 | 143 |
mapClass(whenAskedFor:Class, named:String = null)
144 | 145 | injector 提供了一个方法来实例化被映射的对象: 146 | 147 |
injector.mapClass(MyClass);
148 | var myClassInstance:MyClass = injector.instantiate(MyClass);
149 | 150 | 这提供你的对象的一个实例,并填充了此对象包含的所有被映射的注入点(injection points). 151 | 152 | h4. mapSingleton 153 | 154 | mapSingleton 为每一个注入请求提供类的一个 _单一_(_single_) 实例. 为所有映射提供类的单一实例确保你维护一个一致的状态并且不用担心创建被此类的多余实例. 这是一个被框架强制和管理的单一实例, 而不是一个在类内部强制的单例(Singleton). 155 | 156 |
//你的应用程序中某个映射/配置发生的地方
157 | injector.mapSingleton(MyClass);
158 | 159 | 160 |
//在第一个接收注入的类里
161 | [Inject]
162 | public var myClassInstance:MyClass
163 | 164 | 165 |
//在第二个接收注入的类里
166 | [Inject]
167 | public var myClassInstance:MyClass
168 | 169 | 在上面的例子里, 两个注入请求都将由被请求类的相同实例填充. _这个注入是被延迟的_, 即对象直到第一次被请求才被实例化. 170 | 171 |
mapSingletonOf(whenAskedFor:Class, useSingletonOf:Class, named:String = null)
172 | 173 | h4. mapSingletonOf 174 | 175 | mapSingletonOf在功能上非常像 mapSingleton. 它对映射抽象类和接口很有用, 而 mapSingleton 用来映射具体类实现. 176 | 177 |
//你的应用程序中某个映射/配置发生的地方
178 | injector.mapSingletonOf(IMyClass, MyClass); //MyClass implements IMyClass
179 | 180 |
//在第一个接收注入的类里
181 | [Inject]
182 | public var myClassInstance:IMyClass
183 | 184 | 185 |
//在第二个接收注入的类里
186 | [Inject]
187 | public var myClassInstance:IMyClass
188 | 189 | 这个注入方法对创建使用多态的更具可测性的类非常有用. 在下面的 "Service 实例":#serviceimplementsinterface 章节可以找到一个例子. 190 | 191 | 192 | h3(#mappingwithmediatormap). MediatorMap 类的依赖注入 193 | 194 | MediatorMap 实现 IMediatorMap 接口. IMediatorMap 提供两个方法来将你的 mediators 映射到 view 并注册它们以便用来注入. 195 | 196 |
mapView(viewClassOrName:*, mediatorClass:Class, injectViewAs:Class = null, autoCreate:Boolean = true, autoRemove:Boolean = true):void
197 | 198 | *mapView* 接受一个视图类, MyAwesomeWidget, 或者一个视图的类全名, _com.me.app.view.components::MyAwesomeWidget_ 作为第一个参数. 第二个参数是将要作为视图组件中介的 Mediator 类. *[injectViewAs 内容未完成]*, 最后的两个参数 autoCreate 和 autoRemove 提供方便的自动管理 mediator 的布尔值开关. 199 | 200 |
//在你的程序里某个映射/配置发生的地方
201 | mediatorMap.mapView(MyAwesomeWidget, MyAwesomeWidgetMediator); 
202 | 203 |
//在conntextView 的显示列表里的某个地方
204 | var myAwesomeWidget:MyAwesomeWidget = new MyAwesomeWidget();
205 | this.addChild(myAwesomeWidget); // ADDED_TO_STAGE 事件被抛出, 触发这个视图组件的中介机制
206 | 207 | 这个方法使用了自动中介机制. 手动中介,以及对此方面更深入的内容将稍后在 "Mediators":#mediators 章节中介绍. 208 | 209 | 210 | 211 | h3(#mappingwithcommandmap). CommandMap 类的依赖注入 212 | 213 | CommandMap 类实现 ICommandMap 接口, 提供一个用来将 command 映射到到触发它们的框架事件的方法. 214 | 215 |
mapEvent(eventType:String, commandClass:Class, eventClass:Class = null, oneshot:Boolean = false)
216 | 217 | 你要提供给 commandMap 一个可以执行的类, 执行它的事件类型, 可选的这个事件的强类型, 以及这个 command 是否只被执行一次并且随即取消映射的布尔值开关. 218 | 219 | 这个可选的强类型事件类用来对Flash平台的"magic string"事件类型系统做额外的保护.以避免采用相同事件类型字符串的不同事件类之间的冲突. 220 | 221 |
//在你的程序里某个映射/配置发生的地方
222 | commandMap.mapEvent(MyAppDataEvent.DATA_WAS_RECEIVED, MyCoolCommand, MyAppDataEvent); 
223 | 224 | 225 |
//在事件被广播的另外一个框架actor里
226 | //这触发了随后被执行的被映射的 command
227 | dispatch(new MyAppDataEvent(MyAppDataEvent.DATA_WAS_RECEIVED, someTypedPayload))
228 | 229 | 230 | h2(#thecontext). The Context 231 | 232 | Context 是所有 Robotlegs 具体实现的中心. 一个 Context, 或者也许多个 Context, 提供其它层进行通讯的机制. 一个应用程序并非只能使用一个 Context, 但大多情况下一个 Context 就足够了. 如果在 Flash 平台上创建模块化应用程序, 多个 Context 就是必须的了. Context 在一个应用程序里有三个功能: 提供初始化, *[de-initialization - 非初始化?]*, 和用来通讯的事件中心bus. 233 | 234 |
package org.robotlegs.examples.bootstrap
235 | {
236 | 	import flash.display.DisplayObjectContainer;
237 | 	
238 | 	import org.robotlegs.base.ContextEvent;
239 | 	import org.robotlegs.core.IContext;
240 | 	import org.robotlegs.mvcs.Context;
241 | 	
242 | 	public class ExampleContext extends Context implements IContext
243 | 	{
244 | 		public function UnionChatContext(contextView:DisplayObjectContainer)
245 | 		{
246 | 			super(contextView);
247 | 		}
248 | 		
249 | 		override public function startup():void
250 | 		{
251 | 			//这个 Context 只映射一个 command 到 ContextEvent.STARTUP 事件. 
252 | 			//这个 StartupCommand 将映射其它将在应用程序里使用的的 command, 
253 | 			//mediator, service, 和 model.
254 | 			commandMap.mapEvent( ContextEvent.STARTUP, StartupCommand, ContextEvent, true );
255 | 						
256 | 			//启动应用程序 (触发 StartupCommand)
257 | 			dispatch(new ContextEvent(ContextEvent.STARTUP));
258 | 		}
259 | 	}
260 | }
261 | 262 | 263 | h2(#mvcs). MVCS 参考实现 264 | 265 | Robotlegs 装备了一个参考实现. 这个实现遵照经典的 Model-View-Controller (MVC) 元设计模式, 另外增加了第四个叫做 Service 的 actor. 这些层在本文档中通称为"核心 actor", 或者简称为"actor". 266 | 267 | MVCS 提供一个应用程序的框架概况. 通过将几个经过时间检验的设计模式整合到一个具体实现, Robotlegs 的 MVCS 实现可以用做创建你的应用程序的一致方案. 通过这些架构概念着手一个应用程序, 你甚至可以在开始你的设计之前避免很多常见的障碍: 268 | 269 | * 分离 270 | * 组织 271 | * 解耦 272 | 273 | h4. 分离 274 | 275 | MVCS 提供一种将你的应用程序分离到提供特定功能的无关联的层的很自然的方法. view 层处理用户交互. model 层处理用户创建的或从外部获取的数据. controller 提供一种封装各层之间复杂交互的机制. 最后, service 层提供一种和外界(比如远程服务 API 或文件系统)交互的独立机制. 276 | 277 | h4. 组织 278 | 279 | 通过这种分离我们自然获得一个组织水平. 每个项目都需要某个组织水平. 是的, 有人可以把他们所有的类都扔到顶级包下完事, 但即使是最小的项目这也是不可接受的. 当一个项目有了一定的规模就需要开始组织类文件的结构了. 当向同一个应用程序开发中增加团队成员的时候问题就更加严重了. RobotLegs 的 MVCS 实现为项目描绘出一个分为四层的优雅的组织结构. 280 | 281 | h4. 解耦 282 | 283 | Robotlegs 的MVCS实现将应用程序解耦为4层. 每层都与其它层隔离, 使分离类和组件分别测试变得非常容易. 除了简化测试进程, 通常也使类更具便携性以在其它项目中使用. 比如, 一个连接到远程 API 的 Service 类可能在多个项目中都很有用. 通过解耦这个类, 它可以不需重构便从一个项目转移到另一个中使用. 284 | 285 | 这个默认实现只是充作最佳实践建议的一个例子. Robotlegs 并不打算以任何方式束缚你到这个例子, 它只是一个建议. 你可以随意开发自己的实现来适应你喜欢的命名规范和开发需求. 如果这正是你所追求的, 请一定告诉我们, 因为我们一直对新方案很感兴趣, 而且它有可能作为一种替代实现包含到 RobotLegs 的代码仓库中. 286 | 287 | h3(#context). Context 288 | 289 | 像 RobotLegs 中的其它实现一样, MVCS 实现也是围绕一个或多个 Context. 这个 context 提供一个中心的事件 bus 并且处理自己的启动和关闭. 一个 context 定义了一个范围. 框架 actor 们处在 context 之内,并且在 context 定义的范围之内进行相互间的通讯. 一个应用程序是可以有多个 context 的. 这对想要加载外部模块的应用程序很有用. 因为在一个 context 里的 actor 只能在他们的 context 定义的范围之内相互通讯, 所以在一个模块化的应用程序里, 不同 context 之间的通讯是完全可能的. 290 | 291 | 本文档不讨论模块化编程的内容. 之后本文档内所有提到的 Context 都指在一个应用程序里的单一的 context. 292 | 293 | h3(#controllersandcommands). Controller & Commands 294 | 295 | Controller 层由 Command 类体现. Command 是用来执行应用程序单一单位工作的, 无状态的, 短生命周期的对象. Command 用于应用程序各层之间相互通讯, 也可能用来发送系统事件. 这些系统事件既可能发动其它的 Command, 也可能被一个 Mediator 接收,然后对一个 View Component 进行对应这个事件的工作. Command 是封装你的应用程序业务逻辑的绝佳场所. 296 | 297 | h3(#viewandmediators). View & Mediators 298 | 299 | View 由 Mediator 类体现. 继承 Mediator 的类用来处理框架和 View Component 之间的交互. 一个 Mediator 将会监听框架事件和 View Component 事件, 并在处理所负责的 View Component 发出的事件时发送框架事件. 这样开发者可以将应用程序特有的逻辑放到 Mediator, 而避免把 View Component 耦合到特定的应用程序. 300 | 301 | h3(#modelandservice). Model, Service and the Actor 302 | 303 | MVCS 架构里的 service 和 model 在概念上有着非常多的相似之处. 因为这种相似性, model 和 service 继承了同样的 Actor 基类. 继承 Actor 基类可以获得很多应用程序架构内的功能. 在 MVCS 的 context 里, 我们通过利用继承 Actor 基类来定义应用程序所需要用来管理数据以及和外界通讯的 model 和 service 类. 本文档将把 model 和 service 类分别叫做 Model 和 Service. 304 | 305 | 澄清一点, 本文档把体现应用程序四个层的所有类都称为"framework actor"或"actor". 请不要和本例中包含的只被 Model 和 Service 类继承的 MVCS 类 Actor 混淆. 306 | 307 | h4(#modelandmodels). Model 308 | 309 | Model 类用来在 model 层对数据进行封装并为其提供 API. Model 会在对数据模型进行某些工作之后发出事件通知. Model 通常具有极高的便携性. 310 | 311 | h4(#serviceandservices). Service 312 | 313 | 一个 service 层的 Service 用来和"外面的世界"进行通讯. Web service, 文件存取, 或者其它任何应用程序范围之外的行为对 service 类都很适合. Service 类在处理外部事件时会广播系统事件. 一个 service 应该封装和外部服务的交互且具有非常高的便携性. 314 | 315 | h3(#frameworkevents). 框架事件 316 | 317 | Robotlegs 使用Flash的原生事件用于框架 actor 之间的通讯. 自定义事件类通常用于此用途, 虽然使用现有的 Flash 事件同样可行. Robotlegs 不支持事件冒泡, 因为它并不依赖 Flash 显示列表作为 event bus. 使用自定义类允许开发者通过给事件添加属性来为框架 actor 之间通讯所用的系统事件提供强类型的负载. 318 | 319 | 所有的框架 actor 都可以发送事件: Mediator, Service, Model, 和 Command. Mediator 是唯一接收框架事件的actor. Command 是在对框架事件的处理中被触发. 一个事件既可以被一个 Mediator 接收, 也可以触发一个 command. 320 | 321 | model 和 service 不应该监听和处理事件. 这样做会把它们紧耦合到应用程序特有逻辑而降低潜在的便携性和复用性. 322 | 323 | 324 | h2(#commands). Command 325 | 326 | Command 是短生命周期的无状态对象. 它们在被实例化和执行之后立即释放. Command 应该只在处理框架事件时被执行, 而不应该被任何其他框架 actor 实例化或执行. 327 | 328 | h3(#commandresponsibilities). Command 职责 329 | 330 | Command 被 Context 的 CommandMap 注册到 Context. CommandMap 在 Context 和 Command 类里默认可用. Command 类被注册到 Context 时接收4个参数: 一个事件类型; 响应这个事件时执行的 Command 类; 可选的事件类; 一个是否该 Command 只被执行一次随即被取消注册而不响应后续事件触发的一次性设置. 331 | 332 | h3(#triggeringcommands). 触发 Command 333 | 334 | Command 被 Mediators, Services, Models, 和其它 Command 广播的框架事件触发. 典型的, 触发这个 Command 的事件会被注入到这个 Command, 以提供对其属性/负载的访问: 335 | 336 |
337 | public class MyCommand extends Command
338 | {
339 | 	[Inject]
340 | 	public var event:MyCustomEvent;
341 | 	
342 | 	[Inject]
343 | 	public var model:MyModel;
344 | 			
345 | 	override public function execute():void
346 | 	{
347 | 		model.updateData( event.myCustomEventPayload )
348 | 	}
349 | }
350 | 
351 | 352 | 一个被映射的 command 在响应一个框架事件时被实例化, 所有已被映射, 并被 [Inject] 元数据标签标记过的依赖都会被注入到这个 Command. 另外, 触发这个 Command 的事件实例也会被注入. 当这些依赖被注入完毕, Command 的执行方法会被自动调用, Command 便会进行它的工作. 你不需要, 而且不应该直接调用 execute() 方法. 这是框架的工作. 353 | 354 | h3(#chainingcommands). 链接 Command 355 | 356 | 链接 command 也是可行的: 357 | 358 |
359 | public class MyChainedCommand extends Command
360 | {
361 | 	[Inject]
362 | 	public var event:MyCustomEvent;
363 | 	
364 | 	[Inject]
365 | 	public var model:MyModel;
366 | 			
367 | 	override public function execute():void
368 | 	{
369 | 		model.updateData( event.myCustomEventPayload )
370 | 		
371 | 		//UPDATED_WITH_NEW_STUFF 触发一个 command 的同时被
372 | 		//一个 mediator 接收然后更新一个View Component, 但是只在需要这个响应的时候
373 | 		if(event.responseNeeded)
374 | 		    dispatch( new MyCustomEvent( MyCustomEvent.UPDATED_WITH_NEW_STUFF, model.getCalculatedResponse() ) )
375 | 	}
376 | }
377 | 
378 | 379 | 使用这种方法可以把需要的任意多的 Command 链接在一起. 上面的例子使用了一个条件语句. 如果条件不满足 Command 就不会被链接. 这为你的 Command 执行应用程序工作提供了极大的灵活性. 380 | 381 | h3(#decouplingtiers). 应用程序层的解耦 382 | 383 | Command 是解耦一个应用程序里各个 actor 的非常有用的机制. 因为一个 Command 永远不会被 Mediator, Model 或者 Service 实例化或执行, 这些类也就不会被耦合到 command, 甚至都不知道 command 的存在. 384 | 385 | 为了履行它们的职责, Command 可能: 386 | 387 | * 映射 Mediator, Model, Service, 或者 Context 里的其它 Command 388 | * 广播可能被 Mediator 接收或者触发其它 Command 的事件. 389 | * 被注入Model, Service, 和Mediator 以直接进行工作. 390 | 391 | bq(note). 需要注意的是, 不建议在一个 Command 里直接和 Mediator 交互. 虽然这是可行的, 但会将这个 Mediator 耦合到这个 Command. 因为 Mediator 不像 Service 和 Model, 它可以接受系统事件, 更好的做法是让 Command 广播事件, 然后让需要响应这些事件的 Mediator 监听它们. 392 | 393 | 394 | h2(#mediators). Mediator 395 | 396 | Mediator 类用来作为用户交互和系统的 View Component 之间的中介. 一个 Mediator 可以在多个级别的粒度上履行它的职责, 中介一个应用程序整体和它的子组件, 或者一个应用程序的任何和所有子组件. 397 | 398 | h3(#mediatorresponsibilities). Mediator 职责 399 | 400 | Flash, Flex 和 AIR 应用程序为富视觉用户界面组件提供了无限的可能. 所有这些平台都提供了一套组件, 像 DataGrid, Button, Label 和其它常用的UI组件. 也可以继承这些基本的组件来创建自定义组件, 创建复合组件, 或者完全重写新的组件. 401 | 402 | 一个 View Component 是任何的UI组件和/或它的子组件. 一个 View Component 是已被封装的, 尽可能多地处理自己的状态和操作. 一个 View Component 提供一个包含了事件, 简单方法和属性的API, . Mediators负责代表它所中介的View Component和框架交互. 这包括监听组件及其子组件的事件, 调用其方法, 和读取/设置组件的属性. 403 | 404 | 一个 Mediator 监听它的 View Component 的事件, 通过 View Component 暴露的 API 访问其数据. 一个 Mediators 通过响应其它框架 actor 的事件并对自己的 View Component 进行相应修改来代表它们. 一个 Mediator 通过转发 View Component 的事件或自己向框架广播合适的事件来通知其它的框架 actor. 405 | 406 | h3(#mappingamediator). 映射一个 Mediator 407 | 408 | 任何可以访问到 _mediatorMap_ 实例的类都可以映射 Mediator. 这包括 Mediator, Context, 和 Command 类. 409 | 410 | 这是映射一个 mediator 的语法: 411 |
mediatorMap.mapView( ViewClass, MediatorClass, autoCreate, autoRemove );
412 | 413 | h3(#automaticmediation). View Component 的自动中介 414 | 415 | 当映射一个 view component 类以获得中介时, 你可以指定是否自动为它创建 Mediator. 当此项为 _true_ 时 context 将监听这个 view component 的 ADDED_TO_STAGE 事件. 当收到这个事件这个 view component 会被自动中介, 它的 mediator 就可以开始发送和接收框架事件了. 416 | 417 | h3(#manualmediation). View Component 的手动中介 418 | 419 | 有时候可能不希望或者不可能使用 view component 的自动中介. 在这种情况下可以手动创建一个 Mediator 类的实例: 420 | 421 |
mediatorMap.createMediator(contextView);
422 | 423 | 这里假设这个 view component 之前已经被 _mediatorMap_ 的 _mapView()_ 方法映射过了. 424 | 425 | h3(#mappingthecontextview). 映射主程序 (_contextView_) Mediator 426 | 427 | 映射 contextView 到一个 mediator 是一个常见的模式. 这是个特殊情况, 因为自动中介对 contextView 不起作用, 因为它已经被添加到舞台上, 而不会再发出 _mediatorMap_ 自动中介所需要的事件了. 这个映射通常在持有 _contextView_ 引用的 Context 的 _setup()_ 方法里完成: 428 | 429 |
override public function startup():void
430 | {
431 | 	mediatorMap.mapView(MediateApplicationExample, AppMediator);
432 | 	mediatorMap.createMediator(contextView);
433 | }
434 | 435 | _contextView_ 并没有被完全中介, 还可以发送和接受框架事件. 436 | 437 | h3(#accessingmediatorviewcomponent). 访问一个 Mediator 的 View Component 438 | 439 | 当一个 View Component 在一个 Context 的 contextView 里被添加到舞台上的时候, 它默认地会被根据 MediatorMap 做映射时的配置被自动中介. 在一个基本的 mediator 里, _viewComponent_ 会被注入为被中介的 view component. 一个 Mediator 的 _viewComponent_ 属性是 Object 类型的. 在大多数情况下, 我们希望访问一个强类型的对象以从中获益. 为此目的, 我们注入被中介的 view component 的强类型实例: 440 | 441 |
public class GalleryLabelMediator extends Mediator implements IMediator
442 | {
443 | 	[Inject]
444 | 	public var myCustomComponent:MyCustomComponent;
445 | 		
446 | 	/**
447 | 	* 覆写 onRegister 是添加此 Mediator 关心的任何系统或 View Component 事件的好机会.
448 | 	*/
449 | 	override public function onRegister():void
450 | 	{
451 | 		//添加一个事件监听器到 Context 来监听框架事件
452 | 		eventMap.mapListener( eventDispatcher, MyCustomEvent.DO_STUFF, handleDoStuff );
453 | 		//添加一个事件监听器到被中介的 view component
454 | 		eventMap.mapListener( myCustomComponent, MyCustomEvent.DID_SOME_STUFF, handleDidSomeStuff)
455 | 	}
456 | 	
457 | 	protected function handleDoStuff(event:MyCustomEvent):void
458 | 	{
459 | 		//把事件的强类型负载设置到 view component 的属性. 
460 | 		//View component 很可能基于这个新数据管理自己的状态.
461 | 		myCustomComponent.aProperty = event.payload
462 | 	}
463 | 	
464 | 	protected function handleDidSomeStuff(event:MyCustomEvent):void
465 | 	{
466 | 		//把这个事件转发到框架
467 | 		dispatch(event)
468 | 	}
469 | }
470 | 471 | 通过这种方法我们现在可以很方便地访问被中介的 view component 的公开属性和方法. 472 | 473 | h3(#addingeventlistenerstomediators). 给一个 Mediator 添加事件监听 474 | 475 | 事件监听器是 Mediator 的眼睛和鼻子. 因为框架内的所有通讯都通过原生的Flash事件, Mediator 可以通过添加事件监听器来响应感兴趣的事件. 除了框架事件, Mediator同时监听所中介的 view component 的事件. 476 | 477 | 通常在 Mediator 的 onRegister 方法里添加事件监听. 在 Mediator 生命周期中的这个阶段, 它已经被注册并且它的 view component 和其它依赖也都已被注入. 具体的 Mediator 类必须覆写 onRegister 方法. 也可以在其它方法里添加事件监听, 比如响应框架事件和 view component 事件的事件处理方法里. 478 | 479 | Mediators 装备了一个有 mapListener() 方法的 EventMap. 这个方法注册每个被添加到 Mediator 的事件监听, 并且确保 mediator 被框架取消注册时删除这些事件监听. Flash 里删除事件监听是很重要的, 因为如果一个类里添加了事件监听而没有删除, Player将无法对此类进行运行时垃圾回收(GC, Garbage Collection). 也可以使用传统的 Flash 语法添加事件监听器, 但要注意也要手动把它们删除. 480 | 481 | h3(#mediatorslisteningfroframeworkevents). 监听框架事件 482 | 483 | 所有框架里的actor在实例化时都会被注入一个 _eventDispatcher_ 属性. 这个 _eventDispatcher_ 就是 Mediator 发送和接受框架事件的机制. 484 | 485 |
eventMap.mapListener(eventDispatcher, SomeEvent.IT_IS_IMPORTANT, handleFrameworkEvent)
486 | 487 | 通过此语法, 一个 Mediator 现在监听了 _SomeEvent.IT_IS_IMPORTANT_ 事件并在 _handleFrameworkEvent_ 方法里处理它. 488 | 489 | h3(#mediatorsdispatchingframeworkevents). 广播框架事件 490 | 491 | Mediator的一个很重要的职责就是向框架发送其它 actor 可能感兴趣的事件. 这些事件通常是在响应应用程序用户和被中介的 view component 之间的一些交互时发出的. 这里同样有一个可以减少发送事件到框架的代码输入的很有用的方法: 492 | 493 |
dispatch(new SomeEvent(SomeEvent.YOU_WILL_WANT_THIS, myViewComponent.someData))
494 | 495 | 这个事件现在可以被其它 Mediator 接收或者执行一个 command 了. 发出事件的 Mediator 并不关心其它的 actor 如何回应这个事件, 它只是简单地广播一条有事发生的消息. 一个 mediator 也可以监听自己发出的事件, 然后据此作出回应. 496 | 497 | h3(#mediatorslisteningforcomponentevents). 监听 View Component 事件 498 | 499 | Mediator 负责所中介的 view component 发出的事件. 这可以是个独立组件, 比如 TextField 或者 Button, 也可以是有嵌套层级的复杂组件. 当 mediator 收到 view component 发出的事件会使用指定的方法处理它. 和框架事件一样, EventMap 的 mapListener 方法是给一个 mediator 添加事件监听的首选. 500 | 501 |
eventMap.mapListener(myMediatedViewComponent, SomeEvent.USER_DID_SOMETHING, handleUserDidSomethingEvent)
502 | 503 | 响应一个 view component 的事件时, 一个 mediator 可能: 504 | 505 | * 考察事件的负载 (如果有) 506 | * 考察 view component 的当前状态 507 | * 对 view component 进行需要的工作 508 | * 发送系统事件以通知其它actor有事发生 509 | 510 | h3(#accessingmodelsandservicesfrommediators). 通过 Mediator 访问 Model 和 Service 511 | 512 | 你的 mediator 可以监听 Service 和 Model 类派出的系统事件来提高松耦合性. 通过监听事件, 你的 mediator 不需要关心事件来源, 而只需直接使用事件携带的强类型的负载. 因此, 多个 mediator 可以监听相同的事件然后根据所收到的数据调整自己的状态. 513 | 514 | 在一个 mediator 里直接访问 service 可以提供很大便利而不会带来严重的耦合性问题. 一个 service 并不存储数据, 只是简单地提供一个向外部service发送请求并接受响应的API. 能够直接访问这个API可以避免在你的应用程序中增加不需要的 command 类来达到同样目的. 如果这个 service API 在很多 mediator 中通过相同的方式访问, 将此行为封装到一个 command 里有益于保持此行为的一致性并减少对此 service 的反复调用以及在你的 mediator 里的直接访问. 515 | 516 | 建议通过 model 和 service 实现的接口将 model 和 service 注入 mediator. 下面的 "Service 实例":#serviceexample 章节可以找到一个这样的例子. 517 | 518 | h3(#accessingothermediatorsfromamediator). 访问其它 Mediator 519 | 520 | 如同 Service 和 Model,在一个 Mediator 里也可以注入和访问其它的 Mediator. 这种做法是 *强烈不建议的* 因为这种紧耦合可以简单地通过使用框架事件进行通讯而避免. 521 | 522 | 523 | h2(#models). Model 524 | 525 | Model 类用来管理对应用程序的数据模型的访问. Model 为其它框架actor提供一个 API 来访问, 操作和更新应用程序数据. 这个数据包括但不限于原生数据类型比如 String, Array, 或者像 ArrayCollection 一样的域特有对象或集合. 526 | 527 | Model 有时被当做简单的 Model 比如 UserModel, 有时也被当做 Proxy 比如 UserProxy. 在 Robotlegs 里, 这两种命名都是用作相同的目的, 为应用程序数据提供一个 API. 不管采用哪种命名 model 都继承提供了核心框架依赖和一些有用方法的 Actor 基类. 本文档将这些类当做 Model. 528 | 529 | h3(#modelresponsibilities). Model 职责 530 | 531 | Model类封装了应用程序数据模型并为其提供一个 API. 一个 Model 类是你的应用程序数据的看门人. 应用程序里的其它 actor 通过 Model 提供的 API 请求数据. 因为数据是通过 Model 更新, Model 装备了向框架广播事件的机制以向其它 actor 通知数据模型的变化使它们得以据此调整自己的状态. 532 | 533 | 除了控制对数据模型的访问, Model 通常也被用来保证数据状态的有效性. 这包括对数据进行计算, 或域特有逻辑的其它领域. Model 的这个职责非常重要. Model 是应用程序中最有潜力具有便携性的层. 通过把域逻辑放入 Model, 以后的 model 实现就不再需要像把域逻辑放入 View 或 Controller 层那样重复这些相同的逻辑, 534 | 535 | 作为一个例子, 你的 Model 里可能执行购物车数据的计算. 一个 Command 将会访问这个方法, 最终的计算结果将会被作为被某个 Mediator 监听的事件派发出去. 这个 mediator 将会根据这个被更新的数据更新自己的 view component, 应用程序的第一个迭代是个典型的 Flex 程序. 这个计算也很容易在一个 Mediator 甚至视图里进行. 应用程序的第二个迭代是一个需要全新视图元素的移动设备 Flash 应用程序. 因为这个逻辑在 Model 里, 所以可以很容易被两个完全不同元素的视图复用. 536 | 537 | h3(#mappingamodel). 映射一个 Model 538 | 539 | Injector 有几个方法可以用来将你的 Model 类映射到你的框架actor. 另外, 这些方法事实上可以用来注入任何类到你的类里. 540 | 541 | 将一个已存在的实例当做一个单例注入映射, 使用下面的语法: 542 |
injector.mapValue(MyModelClass, myModelClassInstance)
543 | 544 | 为每个注入映射一个类的新实例, 使用下面的语法: 545 |
injector.mapClass(MyModelClass, MyModelClass)
546 | 另外, 这也可以用来使用被注入的实现某接口的合适的类来映射这个用来注入的接口. 547 |
injector.mapClass(IMyModelClass, MyModelClass)
548 | 549 | 为某个接口或类映射一个单例实例, 使用下面的语法: 550 |
injector.mapSingleton(MyModelClass, MyModelClass)
551 | 552 | 需要注意重要的一点, 当提及上面的一个单例时, 它并不是一个单例模式的单例. 在这个 Context 之外并不强制它作为一个单例. Injector 简单地确保这个类的唯一一个实例被注入. 这对处理你的应用程序数据模型的 Model 非常重要. 553 | 554 | h3(#dispatchingeventsfrommodel). 从一个Model里广播事件 555 | 556 | Model 类提供一个方便的 _dispatch_ 方法用来发送框架事件: 557 | 558 |
dispatch( new ImportantDataEvent(ImportantDataEvent.IMPORTANT_DATA_UPDATED))
559 | 560 | 有很多理由派发一个事件, 包括但不限于: 561 | 562 | * 数据已被初始化并准备好被其它 actor 使用 563 | * 一些数据片被添加到 Model 564 | * 数据被从 Model 中删除 565 | * 数据已改变或者更新 566 | * 数据相关的状态已改变 567 | 568 | h3(#listeningforeventsinmodel). 在一个 Model 里监听框架事件 569 | 570 | 虽然技术上可能, 但 *强烈不建议* 这样做. 不要这样做. 只是为了说清楚: *不要这样做*. 如果你这样做了, 不要说你没被警告过. 571 | 572 | 573 | h2(#services). Service 574 | 575 | Service 用来访问应用程序范围之外的资源. 这包括但当然不限于: 576 | 577 | * web services 578 | * 文件系统 579 | * 数据库 580 | * RESTful APIs 581 | * 通过 localConnection 的其它 Flash 应用程序 582 | 583 | Service 封装了这些和外部实体的交互, 并管理这个交互产生的 result , fault 或其它事件. 584 | 585 | 你可能注意到 Service 和 Model 的基类非常相像. 事实上, 你可能注意到除了类名, 它们其实是一样的. 那么为什么用两个类呢? Model 和 Service 类在一个应用程序里有完全不同的职责. 这些类的具体实现将不再相像. 如果没有这个分离, 你将经常发现 Model 类在访问外部服务. 这让 Model 有很多职责, 访问外部数据, 解析结果, 处理失败, 管理应用程序数据状态, 为数据提供一个 API, 为外部服务提供一个 API, 等等. 通过分离这些层有助于缓解这个问题. 586 | 587 | h3(#serviceresponsibilities). Service 职责 588 | 589 | 一个 Service 类为你的应用程序提供一个和外部服务交互的 API. 一个 service 类将连接外部服务并管理它收到的响应. Service 类通常是无状态的实体. 他们并不存储从外部服务收到的数据, 而是发送框架事件来让合适的框架 actor 管理响应数据和失败. 590 | 591 | h3(#mappingservice). 映射一个 Service 592 | 593 | 有 "injector 的多个可用的方法":#mappingwithinjector 可以用来映射你的 Service 类以注入你的其它框架 actor. 另外, 这些方法也可以用来注入事实上任何类到你的类里. 594 | 595 | 将一个已存在的实例当做一个单例注入映射, 使用下面的语法: 596 |
injector.mapValue(MyServiceClass, myServiceClassInstance)
597 | 598 | 为每个注入映射一个类的新实例, 使用下面的语法: 599 |
injector.mapClass(MyServiceClass, MyServiceClass)
600 | 另外, 这也可以用来使用被注入的实现某接口的合适的类来映射这个用来注入的接口. 601 |
injector.mapClass(IMyServiceClass, MyServiceClass)
602 | 603 | 为某个接口或类映射一个单例实例, 使用下面的语法: 604 |
injector.mapSingleton(MyServiceClass, MyServiceClass)
605 | 606 | 需要注意重要的一点, 当提及上面的一个单例时, 它并不是一个单例模式的单例. 在这个 Context 之外并不强制它作为一个单例. Injector 简单地确保这个类的唯一一个实例被注入. 607 | 608 | h3(#servicelisteningforframeworkevents). 在一个 Service 里监听框架事件 609 | 610 | 虽然技术上可能, 但 *强烈不建议* 这样做. 不要这样做. 只是为了说清楚: *不要这样做*. 如果你这样做了, 不要说你没被警告过. 611 | 612 | h3(#servicedispatchingframeworkevents). 广播框架事件 613 | 614 | Service 类提供一个方便的 _dispatch_ 方法用来发送框架事件: 615 | 616 |
dispatch( new ImportantServiceEvent(ImportantServiceEvent.IMPORTANT_SERVICE_EVENT))
617 | 618 | h3(#serviceexample). Service 示例 619 | 620 | 下面是来自 "Image Gallery":http://github.com/robotlegs/robotlegs-demos-Bundle/tree/master/FlickrImageGallery/ demo 的 Flickr service 类 . "The Flickr API AS3 Library":http://code.google.com/p/as3flickrlib/ 做了很多连接到 Flickr 的底层处理. 这个例子使用了它并为在这个例子范围内使用提供了一个简单的抽象. 621 | 622 |
package org.robotlegs.demos.imagegallery.remote.services
623 | {
624 | 	import com.adobe.webapis.flickr.FlickrService;
625 | 	import com.adobe.webapis.flickr.Photo;
626 | 	import com.adobe.webapis.flickr.events.FlickrResultEvent;
627 | 	import com.adobe.webapis.flickr.methodgroups.Photos;
628 | 	import com.adobe.webapis.flickr.methodgroups.helpers.PhotoSearchParams;
629 | 	
630 | 	import org.robotlegs.demos.imagegallery.events.GalleryEvent;
631 | 	import org.robotlegs.demos.imagegallery.models.vo.Gallery;
632 | 	import org.robotlegs.demos.imagegallery.models.vo.GalleryImage;
633 | 	import org.robotlegs.mvcs.Actor;
634 | 
635 |     /**
636 |      *  这个类使用了 Adobe 提供的 Flickr API 来连接到 
637 |      *  Flickr 并获取图片. 它最开始加载当前最"有趣"的
638 |      *  的照片, 同时也提供了搜索其它关键词的能力. 
639 |      */
640 | 	public class FlickrImageService extends Actor implements IGalleryImageService
641 | 	{
642 | 		private var service:FlickrService;
643 | 		private var photos:Photos;
644 | 		
645 | 		protected static const FLICKR_API_KEY:String = "516ab798392cb79523691e6dd79005c2";
646 | 		protected static const FLICKR_SECRET:String = "8f7e19a3ae7a25c9";
647 | 		
648 | 		public function FlickrImageService()
649 | 		{
650 | 			this.service = new FlickrService(FLICKR_API_KEY);
651 | 		}
652 | 		
653 | 		public function get searchAvailable():Boolean
654 | 		{
655 | 			return true;
656 | 		}
657 | 				
658 | 		public function loadGallery():void
659 | 		{
660 | 			service.addEventListener(FlickrResultEvent.INTERESTINGNESS_GET_LIST, handleSearchResult);
661 | 			service.interestingness.getList(null,"",20)
662 | 		}
663 | 		
664 | 		public function search(searchTerm:String):void
665 | 		{
666 | 			if(!this.photos)
667 | 				this.photos = new Photos(this.service);
668 | 			service.addEventListener(FlickrResultEvent.PHOTOS_SEARCH, handleSearchResult);
669 | 			var p:PhotoSearchParams = new PhotoSearchParams()
670 | 			p.text = searchTerm;
671 | 			p.per_page = 20;
672 | 			p.content_type = 1;
673 | 			p.media = "photo"
674 | 			p.sort = "date-posted-desc";
675 | 			this.photos.searchWithParamHelper(p);				
676 | 		}
677 | 		
678 | 		protected function handleSearchResult(event:FlickrResultEvent):void
679 | 		{
680 | 			this.processFlickrPhotoResults(event.data.photos.photos);
681 | 		}
682 | 		
683 | 		protected function processFlickrPhotoResults(results:Array):void
684 | 		{
685 | 			var gallery:Gallery = new Gallery();
686 | 						
687 | 			for each(var flickrPhoto:Photo in results)
688 | 			{
689 | 				var photo:GalleryImage = new GalleryImage()
690 | 				var baseURL:String = 'http://farm' + flickrPhoto.farmId + '.static.flickr.com/' + flickrPhoto.server + '/' + flickrPhoto.id + '_' + flickrPhoto.secret;
691 | 				photo.thumbURL = baseURL + '_s.jpg';
692 | 				photo.URL = baseURL + '.jpg';
693 | 				gallery.photos.addItem( photo );
694 | 			}
695 | 			
696 | 			dispatch(new GalleryEvent(GalleryEvent.GALLERY_LOADED, gallery));
697 | 		}
698 | 		
699 | 	}
700 | }
701 | 702 | FlickrGalleryService 提供了一个连接到一个 gallery 服务的非常简单的接口. 应用程序可以 _loadGallery_, _search_, 并查询 _searchAvailable_ 是 true 还是 false. IGalleryService 接口定义的接口: 703 | 704 |
package org.robotlegs.demos.imagegallery.remote.services
705 | {
706 | 	public interface IGalleryImageService
707 | 	{
708 | 		function loadGallery():void;
709 | 		function search(searchTerm:String):void;
710 | 		function get searchAvailable():Boolean;
711 | 	}
712 | }
713 | 714 | h4(#serviceimplementsinterface). Services 应该实现一个接口 715 | 716 | 通过创建实现了接口的 service, 为了测试在运行时切换它们, 或者对应用程序的最终用户提供对其它 service 的访问将会很简单. 比如, FlickrGalleryService 可以很容易替换为 XMLGalleryService: 717 | 718 |
package org.robotlegs.demos.imagegallery.remote.services
719 | {
720 | 	import mx.rpc.AsyncToken;
721 | 	import mx.rpc.Responder;
722 | 	import mx.rpc.http.HTTPService;
723 | 	
724 | 	import org.robotlegs.demos.imagegallery.events.GalleryEvent;
725 | 	import org.robotlegs.demos.imagegallery.models.vo.Gallery;
726 | 	import org.robotlegs.demos.imagegallery.models.vo.GalleryImage;
727 | 	import org.robotlegs.mvcs.Actor;
728 | 
729 | 	public class XMLImageService extends Actor implements IGalleryImageService
730 | 	{
731 | 		protected static const BASE_URL:String = "assets/gallery/";
732 | 		
733 | 		public function XMLImageService()
734 | 		{
735 | 			super();
736 | 		}
737 | 
738 | 		public function get searchAvailable():Boolean
739 | 		{
740 | 			return false;
741 | 		}
742 | 				
743 | 		public function loadGallery():void
744 | 		{
745 | 			var service:HTTPService = new HTTPService();
746 | 			var responder:Responder = new Responder(handleServiceResult, handleServiceFault);
747 | 			var token:AsyncToken;
748 | 			service.resultFormat = "e4x";
749 | 			service.url = BASE_URL+"gallery.xml";
750 | 			token = service.send();
751 | 			token.addResponder(responder);
752 | 		}
753 | 		
754 | 		public function search(searchTerm:String):void
755 | 		{
756 | 			trace("search is not available");
757 | 		}
758 | 		
759 | 		protected function handleServiceResult(event:Object):void
760 | 		{
761 | 			var gallery:Gallery = new Gallery();
762 | 						
763 | 			for each(var image:XML in event.result.image)
764 | 			{
765 | 				var photo:GalleryImage = new GalleryImage()
766 | 				photo.thumbURL = BASE_URL + "images/" + image.@name + '_s.jpg';
767 | 				photo.URL = BASE_URL + "images/" + image.@name + '.jpg';
768 | 				gallery.photos.addItem( photo );
769 | 			}
770 | 			
771 | 			dispatchEvent(new GalleryEvent(GalleryEvent.GALLERY_LOADED, gallery));
772 | 		}
773 | 		
774 | 		protected function handleServiceFault(event:Object):void
775 | 		{
776 | 			trace(event);
777 | 		}
778 | 	}
779 | }
780 | 781 | XML gallery 提供了和 Flickr 一样的方法可以访问并可在任何 IGalleryService 接口被调用的地方进行替换. 这些 service 派发相同的事件并且在最终的应用程序里很难区分. 在这个例子里, 搜索并没有被实现, 但搜索功能在这个 service 里也同样可以很容易实现, 782 | 783 | 建议所有的 service 都实现一个定义了它们 API 的接口. 在框架 actor 里接收一个 service 作为依赖注入时可以请求这个接口, 而不是具体的实现. 784 | 785 |
injector.mapSingletonOf(IGalleryService, FlickrGalleryService);
786 | 787 |
[Inject]
788 | public var galleryService:IGalleryService
789 | 790 | 你可以通过简单地改变注入来使用你的类代替这个 gallery service: 791 | 792 |
injector.mapSingletonOf(IGalleryService, XMLGalleryService);
793 | 794 | 这种方式可以为一个应用程序提供健壮性, 灵活性, 和增强的可测试性. 795 | 796 | h4(#parsingresultsinservice). 在一个 Service 里解析数据 797 | 798 | 在上面的例子里 service 类或者外部服务提供了不符合应用程序域的对象. Flickr service 提供强类型的 Photo 对象而 XML service 提供 xml. 这些数据类型都很好用, 但是并不符合我们应用程序的 context. 它们是外来者. 可以围绕外部数据类型对应用程序进行建模, 或者更可取地, 转换这些数据以符合应用程序. 799 | 800 | 应用程序里有两处可以进行这项操作/转换. Service 和 Model 都很适合. Service 是进入外部数据的第一个点, 所以它是操作一个外部服务返回的数据的更好的选择. 外来数据应该在第一个机会转换到应用程序域. 801 | 802 | _提供一个使用工厂类而不是在 service 里生成应用程序域对象的例子... 适当的_ 803 | 804 | 当数据被转换为应用程序域特有的对象之后发出带有强类型负载的事件以被对此关心的 actor 立即使用. 805 | 806 | h4(#serviceevents). Service 事件 807 | 808 | service 组合的最后一个部分是自定义事件. 没有事件的 service 只是哑巴. 他们可能做的任何工作都不会被其它框架成员注意到. 一个 service 将会使用自定义事件来向应用程序发出声音. 事件并不一定是唯一的意图. 如果这个 service 正在转换数据它可以使用一个普通的事件来派发强类型的数据给感兴趣的应用程序 actor. 809 | 810 | 811 | -------------------------------------------------------------------------------- /best-practices-zh-cn/0_toc.textile: -------------------------------------------------------------------------------- 1 |
!=http://joelhooks.com/wp-content/uploads/2009/07/robotlegssketchsmall.gif!

Documentation for Robotlegs v1.0RC1

2 | h2. 目录 3 | 4 | # *"Robotlegs 是什么":#whatisrobotlegs* 5 | # "依赖注入":#dependencyinjection 6 | # "使用 Injectors":#usingtheinjectors 7 | ** "SwiftSuspenders 适配器注入语法":#injectionsyntax 8 | ** "Injector 类的映射注入":#mappingwithinjector 9 | ** "MediatorMap 类的依赖注入":#mappingwithmediatormap 10 | ** "CommandMap 类的依赖注入":#mappingwithcommandmap 11 | # "The Context":#thecontext 12 | # *"MVCS 参考实现":#mvcs* 13 | ## "Context":#context 14 | ## "Controller & Commands":#controllersandcommands 15 | ## "View & Mediators":#viewandmediators 16 | ## "Model, Service and the Actor":#modelandservice 17 | ## "Model":#modelandmodels 18 | ## "Service":#serviceandservices 19 | ## "框架事件":#frameworkevents 20 | ## *"Commands":#commands* 21 | ### "Command 职责":#commandresponsibilities 22 | ### "触发 Command":#triggeringcommands 23 | ### "链接 Command":#chainingcommands 24 | ### "应用程序层的解耦":#decouplingtiers 25 | ## *"Mediators":#mediators* 26 | ### "Mediator 职责":#mediatorresponsibilities 27 | ### "映射一个 Mediator":#mappingamediator 28 | ### "View Component 的自动中介":#automaticmediation 29 | ### "View Component 的手动中介":#manualmediation 30 | ### "映射主程序 (_contextView_) Mediator":#mappingthecontextview 31 | ### "访问一个 Mediator 的 View Component":#accessingmediatorviewcomponent 32 | ### "给一个 Mediator 添加事件监听":#addingeventlistenerstomediators 33 | ### "监听框架事件":#mediatorslisteningfroframeworkevents 34 | ### "广播框架事件":#Mediatorsdispatchingframeworkevents 35 | ### "监听 View Component 事件":#mediatorslisteningforcomponentevents 36 | ### "通过 Mediator 访问 Model 和 Service":#accessingmodelsandservicesfrommediators 37 | ### "访问其它 Mediator":#accessingothermediatorsfromamediator 38 | ## *"Models":#models* 39 | ### "Model 职责":#modelresponsibilities 40 | ### "映射一个 Model":#mappingamodel 41 | ### "从一个Model里广播事件":#dispatchingeventsfrommodel 42 | ### "在一个 Model 里监听框架事件":#listeningforeventsinmodel 43 | ## *"Services":#services* 44 | ### "Service 职责":#serviceresponsibilities 45 | ### "映射一个 Service":#mappingservice 46 | ### "在一个 Service 里监听框架事件":#servicelisteningforframeworkevents 47 | ### "广播框架事件":#servicedispatchingframeworkevents 48 | ### "Service 示例":#serviceexample 49 | **** "Services 应该实现一个接口":#serviceimplementsinterface 50 | **** "在一个 Service 里解析数据":#parsingresultsinservice 51 | **** "Service 事件":#serviceevents 52 | 53 | -------------------------------------------------------------------------------- /best-practices-zh-cn/1_what_is_robotlegs.textile: -------------------------------------------------------------------------------- 1 | h2(#whatisrobotlegs). Robotlegs 是什么 2 | 3 | Robotlegs 是一个用来开发Flash, Flex, 和 AIR 应用的纯 AS3 微架构(框架). Robotlegs 专注于将应用程序各层排布在一起并提供它们相互通讯的机制. Robotlegs 试图通过提供一种解决常见开发问题的经过时间检验的架构解决方案来加速开发. Robotlegs 无意锁定你到框架, 你的类就是你的类的样子, 而且应该很容易地切换到其他框架. 4 | 5 | 框架提供一个基于 "Model-View-Controller":http://en.wikipedia.org/wiki/Model–view–controller 元设计模式的默认实现. 这个实现提供一个针对应用程序结构和设计的强烈建议. 虽然它确实轻微减低了你的应用程序的便携性, 不过它依然以最低限度影响你的具体类为目标. 通过扩展 "MVCS":#mvcs 实现类, 你可以获得很多有用的方法和属性. 6 | 7 | 你不必使用Robotlegs的标准 "MVCS":#mvcs 实现.你可以使用它的任意部分, 或者完全不使用它, 或者使用自己的实现来适应你的需求. 它是为了提供合适的参考实现和快速开始使用 Robotlegs 而被包含进来。 8 | 9 | -------------------------------------------------------------------------------- /best-practices-zh-cn/2_dependency_injection.textile: -------------------------------------------------------------------------------- 1 | h2(#dependencyinjection). 依赖注入 2 | 3 | Robotlegs 围绕 "依赖注入":http://www.insideria.com/2009/09/as3-dependency-injection-demys.html 设计模式展开. 4 | 5 | bq. 最简单地, 依赖注入是为对象提供实例变量或属性的行为. 当你传递一个变量到一个类的构造函数, 你在使用依赖注入. 当你设置一个类的属性, 你在使用依赖注入. 如果你不是使用严格的过程或线性方式编写AS3, 很可能你现在就在使用依赖注入。 6 | 7 | Robotlegs 使用基于元数据的自动依赖注入. 这是为了方便开发而提供, 而且在排布应用程序并提供类和它所需要的依赖时,可以减少很多代码量. 虽然完全可以手动提供这些依赖, 但是允许框架来履行这些职责可以减少出错的机会,并且通常可以加快编码进程。. 8 | 9 | -------------------------------------------------------------------------------- /best-practices-zh-cn/3_0_injection.textile: -------------------------------------------------------------------------------- 1 | h2(#usingtheinjectors). 使用 Injectors 2 | 3 | Robotlegs 采用一种适配器(adapter)机制来为框架提供依赖注入机制. 默认地, 框架配备了 "SwiftSuspenders":http://github.com/tschneidereit/SwiftSuspenders 注入/反射库来适合这个要求. 另有 SmartyPants-IoC 和 Spring Actionscript 的适配器可以使用. 可能有潜在的特定需求来使用其它的依赖注入适配器, 但是如果没有特别的理由, 建议使用默认的 "SwiftSuspenders":http://github.com/tschneidereit/SwiftSuspenders, 因为它为 Robotlegs 做了一些特别调整. 4 | 5 | h3(#injectionsyntax). SwiftSuspenders 适配器注入语法 6 | 7 | SwiftSuspenders 支持三种类型的依赖注入 8 | 9 | * 属性(域)注入 10 | * 参数(方法/设值) 注入 11 | * 构造注入 12 | 13 | 鉴于此文档的目的, 我们将特别介绍属性注入, 以及在 Robotlegs 里如何使用. 将属性注入类有两种选择. 你可以使用未命名,或命名的注入: 14 | 15 |
[Inject]
16 | public var myDependency:Depedency; //未命名注入
17 | 18 |
[Inject(name="myNamedDependency")]
19 | public var myNamedDependency:NamedDepedency; //命名注入
20 | 21 | Robotlegs 里三处提供了注入映射. MediatorMap, CommandMap, 和直接通过 Injector . MediatorMap 和 CommandMap 也都是使用 Injector, 但它们同时做了一些各自层(tier)所需要的额外工作. 顾名思义, MediatorMap 用来映射 Mediator, CommandMap 用来映射 Command, 其它所有需要被注入的内容 (包括但不限于 Model) 都要直接使用 Injector 映射. 22 | 23 | -------------------------------------------------------------------------------- /best-practices-zh-cn/3_1_injecting_with_the_injector.textile: -------------------------------------------------------------------------------- 1 | h3(#mappingwithinjector). Injector 类的映射注入 2 | 3 | 具体 Injector 类的适配器都遵照 IInjector 接口. 这个接口为不同的依赖注入解决方案提供了统一的API. 本文档专注于 SwiftSuspenders, 但这些语法同样适应于其它任何遵照 Iinjector 接口的 Injector. 4 | 5 | _injector_ 是你应用程序里所发生的所有依赖注入的生产车间. 它用来注入框架 actor, 同时也可以用来执行你的应用程序所需要的任何其它注入. 这包括但不限于 RemoteObjects, HTTPServices, 工厂类, 或者事实上任何有可能成为你的对象所需要的依赖的类/接口. 6 | 7 | 下面是实现 IInjector 接口的类所提供的四个映射方法: 8 | 9 | h4. mapValue 10 | 11 | mapValue 用来映射一个对象的特定实例到一个 injector. 当请求一个特定的类,使用类的这个特定实例来注入. 12 | 13 |
//你的应用程序中某个映射/配置发生的地方
14 | var myClassInstance:MyClass = new MyClass();
15 | injector.mapValue(MyClass, myClassInstance);
16 | 17 |
//在接收注入的类中
18 | [Inject]
19 | public var myClassInstance:MyClass
20 | 21 |
mapValue(whenAskedFor:Class, instantiateClass:Class, named:String = null)
22 | 23 | MyClass 的实例被创建和保留,并在被请求的时候被注入. 当此类被请求的时候,这个实例被用来满足这个注入请求. 请注意很重要的一点,因为你已经手动创建并通过 mapValue 映射了一个类实例, 这个实例所需要的依赖将不会被自动注入. 你需要手动或通过 injector 注入这些依赖: 24 | 25 |
injector.injectInto(myClassInstance);
26 | 27 | 这将立即为此实例提供已映射的可以注入的属性。. 28 | 29 | h4. mapClass 30 | 31 | mapClass 为每一个注入请求提供这个被映射的类的一个 _特有_( _unique_) 实例. 32 | 33 |
//你的应用程序中某个映射/配置发生的地方
34 | injector.mapClass(MyClass);
35 | 36 | 37 |
//在第一个接收注入的类里
38 | [Inject]
39 | public var myClassInstance:MyClass
40 | 41 | 42 |
//在第二个接收注入的类里
43 | [Inject]
44 | public var myClassInstance:MyClass
45 | 46 | 为上面的每一个注入提供MyClass的 _特有_( _unique_) 实例来完成请求. 47 | 48 |
mapClass(whenAskedFor:Class, named:String = null)
49 | 50 | injector 提供了一个方法来实例化被映射的对象: 51 | 52 |
injector.mapClass(MyClass);
53 | var myClassInstance:MyClass = injector.instantiate(MyClass);
54 | 55 | 这提供你的对象的一个实例,并填充了此对象包含的所有被映射的注入点(injection points). 56 | 57 | h4. mapSingleton 58 | 59 | mapSingleton 为每一个注入请求提供类的一个 _单一_(_single_) 实例. 为所有映射提供类的单一实例确保你维护一个一致的状态并且不用担心创建被此类的多余实例. 这是一个被框架强制和管理的单一实例, 而不是一个在类内部强制的单例(Singleton). 60 | 61 |
//你的应用程序中某个映射/配置发生的地方
62 | injector.mapSingleton(MyClass);
63 | 64 | 65 |
//在第一个接收注入的类里
66 | [Inject]
67 | public var myClassInstance:MyClass
68 | 69 | 70 |
//在第二个接收注入的类里
71 | [Inject]
72 | public var myClassInstance:MyClass
73 | 74 | 在上面的例子里, 两个注入请求都将由被请求类的相同实例填充. _这个注入是被延迟的_, 即对象直到第一次被请求才被实例化. 75 | 76 |
mapSingletonOf(whenAskedFor:Class, useSingletonOf:Class, named:String = null)
77 | 78 | h4. mapSingletonOf 79 | 80 | mapSingletonOf在功能上非常像 mapSingleton. 它对映射抽象类和接口很有用, 而 mapSingleton 用来映射具体类实现. 81 | 82 |
//你的应用程序中某个映射/配置发生的地方
83 | injector.mapSingletonOf(IMyClass, MyClass); //MyClass implements IMyClass
84 | 85 |
//在第一个接收注入的类里
86 | [Inject]
87 | public var myClassInstance:IMyClass
88 | 89 | 90 |
//在第二个接收注入的类里
91 | [Inject]
92 | public var myClassInstance:IMyClass
93 | 94 | 这个注入方法对创建使用多态的更具可测性的类非常有用. 在下面的 "Service 实例":#serviceimplementsinterface 章节可以找到一个例子. 95 | 96 | -------------------------------------------------------------------------------- /best-practices-zh-cn/3_2_injecting_via_MediatorMap.textile: -------------------------------------------------------------------------------- 1 | h3(#mappingwithmediatormap). MediatorMap 类的依赖注入 2 | 3 | MediatorMap 实现 IMediatorMap 接口. IMediatorMap 提供两个方法来将你的 mediators 映射到 view 并注册它们以便用来注入. 4 | 5 |
mapView(viewClassOrName:*, mediatorClass:Class, injectViewAs:Class = null, autoCreate:Boolean = true, autoRemove:Boolean = true):void
6 | 7 | *mapView* 接受一个视图类, MyAwesomeWidget, 或者一个视图的类全名, _com.me.app.view.components::MyAwesomeWidget_ 作为第一个参数. 第二个参数是将要作为视图组件中介的 Mediator 类. *[injectViewAs 内容未完成]*, 最后的两个参数 autoCreate 和 autoRemove 提供方便的自动管理 mediator 的布尔值开关. 8 | 9 |
//在你的程序里某个映射/配置发生的地方
10 | mediatorMap.mapView(MyAwesomeWidget, MyAwesomeWidgetMediator); 
11 | 12 |
//在conntextView 的显示列表里的某个地方
13 | var myAwesomeWidget:MyAwesomeWidget = new MyAwesomeWidget();
14 | this.addChild(myAwesomeWidget); // ADDED_TO_STAGE 事件被抛出, 触发这个视图组件的中介机制
15 | 16 | 这个方法使用了自动中介机制. 手动中介,以及对此方面更深入的内容将稍后在 "Mediators":#mediators 章节中介绍. 17 | 18 | 19 | -------------------------------------------------------------------------------- /best-practices-zh-cn/3_3_injecting_with_CommandMap.textile: -------------------------------------------------------------------------------- 1 | h3(#mappingwithcommandmap). CommandMap 类的依赖注入 2 | 3 | CommandMap 类实现 ICommandMap 接口, 提供一个用来将 command 映射到到触发它们的框架事件的方法. 4 | 5 |
mapEvent(eventType:String, commandClass:Class, eventClass:Class = null, oneshot:Boolean = false)
6 | 7 | 你要提供给 commandMap 一个可以执行的类, 执行它的事件类型, 可选的这个事件的强类型, 以及这个 command 是否只被执行一次并且随即取消映射的布尔值开关. 8 | 9 | 这个可选的强类型事件类用来对Flash平台的"magic string"事件类型系统做额外的保护.以避免采用相同事件类型字符串的不同事件类之间的冲突. 10 | 11 |
//在你的程序里某个映射/配置发生的地方
12 | commandMap.mapEvent(MyAppDataEvent.DATA_WAS_RECEIVED, MyCoolCommand, MyAppDataEvent); 
13 | 14 | 15 |
//在事件被广播的另外一个框架actor里
16 | //这触发了随后被执行的被映射的 command
17 | dispatch(new MyAppDataEvent(MyAppDataEvent.DATA_WAS_RECEIVED, someTypedPayload))
18 | 19 | -------------------------------------------------------------------------------- /best-practices-zh-cn/4_context.textile: -------------------------------------------------------------------------------- 1 | h2(#thecontext). The Context 2 | 3 | Context 是所有 Robotlegs 具体实现的中心. 一个 Context, 或者也许多个 Context, 提供其它层进行通讯的机制. 一个应用程序并非只能使用一个 Context, 但大多情况下一个 Context 就足够了. 如果在 Flash 平台上创建模块化应用程序, 多个 Context 就是必须的了. Context 在一个应用程序里有三个功能: 提供初始化, *[de-initialization - 非初始化?]*, 和用来通讯的事件中心bus. 4 | 5 |
package org.robotlegs.examples.bootstrap
 6 | {
 7 | 	import flash.display.DisplayObjectContainer;
 8 | 	
 9 | 	import org.robotlegs.base.ContextEvent;
10 | 	import org.robotlegs.core.IContext;
11 | 	import org.robotlegs.mvcs.Context;
12 | 	
13 | 	public class ExampleContext extends Context implements IContext
14 | 	{
15 | 		public function UnionChatContext(contextView:DisplayObjectContainer)
16 | 		{
17 | 			super(contextView);
18 | 		}
19 | 		
20 | 		override public function startup():void
21 | 		{
22 | 			//这个 Context 只映射一个 command 到 ContextEvent.STARTUP 事件. 
23 | 			//这个 StartupCommand 将映射其它将在应用程序里使用的的 command, 
24 | 			//mediator, service, 和 model.
25 | 			commandMap.mapEvent( ContextEvent.STARTUP, StartupCommand, ContextEvent, true );
26 | 						
27 | 			//启动应用程序 (触发 StartupCommand)
28 | 			dispatch(new ContextEvent(ContextEvent.STARTUP));
29 | 		}
30 | 	}
31 | }
32 | 33 | -------------------------------------------------------------------------------- /best-practices-zh-cn/5_mvcs_overview.textile: -------------------------------------------------------------------------------- 1 | h2(#mvcs). MVCS 参考实现 2 | 3 | Robotlegs 装备了一个参考实现. 这个实现遵照经典的 Model-View-Controller (MVC) 元设计模式, 另外增加了第四个叫做 Service 的 actor. 这些层在本文档中通称为"核心 actor", 或者简称为"actor". 4 | 5 | MVCS 提供一个应用程序的框架概况. 通过将几个经过时间检验的设计模式整合到一个具体实现, Robotlegs 的 MVCS 实现可以用做创建你的应用程序的一致方案. 通过这些架构概念着手一个应用程序, 你甚至可以在开始你的设计之前避免很多常见的障碍: 6 | 7 | * 分离 8 | * 组织 9 | * 解耦 10 | 11 | h4. 分离 12 | 13 | MVCS 提供一种将你的应用程序分离到提供特定功能的无关联的层的很自然的方法. view 层处理用户交互. model 层处理用户创建的或从外部获取的数据. controller 提供一种封装各层之间复杂交互的机制. 最后, service 层提供一种和外界(比如远程服务 API 或文件系统)交互的独立机制. 14 | 15 | h4. 组织 16 | 17 | 通过这种分离我们自然获得一个组织水平. 每个项目都需要某个组织水平. 是的, 有人可以把他们所有的类都扔到顶级包下完事, 但即使是最小的项目这也是不可接受的. 当一个项目有了一定的规模就需要开始组织类文件的结构了. 当向同一个应用程序开发中增加团队成员的时候问题就更加严重了. RobotLegs 的 MVCS 实现为项目描绘出一个分为四层的优雅的组织结构. 18 | 19 | h4. 解耦 20 | 21 | Robotlegs 的MVCS实现将应用程序解耦为4层. 每层都与其它层隔离, 使分离类和组件分别测试变得非常容易. 除了简化测试进程, 通常也使类更具便携性以在其它项目中使用. 比如, 一个连接到远程 API 的 Service 类可能在多个项目中都很有用. 通过解耦这个类, 它可以不需重构便从一个项目转移到另一个中使用. 22 | 23 | 这个默认实现只是充作最佳实践建议的一个例子. Robotlegs 并不打算以任何方式束缚你到这个例子, 它只是一个建议. 你可以随意开发自己的实现来适应你喜欢的命名规范和开发需求. 如果这正是你所追求的, 请一定告诉我们, 因为我们一直对新方案很感兴趣, 而且它有可能作为一种替代实现包含到 RobotLegs 的代码仓库中. 24 | 25 | h3(#context). Context 26 | 27 | 像 RobotLegs 中的其它实现一样, MVCS 实现也是围绕一个或多个 Context. 这个 context 提供一个中心的事件 bus 并且处理自己的启动和关闭. 一个 context 定义了一个范围. 框架 actor 们处在 context 之内,并且在 context 定义的范围之内进行相互间的通讯. 一个应用程序是可以有多个 context 的. 这对想要加载外部模块的应用程序很有用. 因为在一个 context 里的 actor 只能在他们的 context 定义的范围之内相互通讯, 所以在一个模块化的应用程序里, 不同 context 之间的通讯是完全可能的. 28 | 29 | 本文档不讨论模块化编程的内容. 之后本文档内所有提到的 Context 都指在一个应用程序里的单一的 context. 30 | 31 | h3(#controllersandcommands). Controller & Commands 32 | 33 | Controller 层由 Command 类体现. Command 是用来执行应用程序单一单位工作的, 无状态的, 短生命周期的对象. Command 用于应用程序各层之间相互通讯, 也可能用来发送系统事件. 这些系统事件既可能发动其它的 Command, 也可能被一个 Mediator 接收,然后对一个 View Component 进行对应这个事件的工作. Command 是封装你的应用程序业务逻辑的绝佳场所. 34 | 35 | h3(#viewandmediators). View & Mediators 36 | 37 | View 由 Mediator 类体现. 继承 Mediator 的类用来处理框架和 View Component 之间的交互. 一个 Mediator 将会监听框架事件和 View Component 事件, 并在处理所负责的 View Component 发出的事件时发送框架事件. 这样开发者可以将应用程序特有的逻辑放到 Mediator, 而避免把 View Component 耦合到特定的应用程序. 38 | 39 | h3(#modelandservice). Model, Service and the Actor 40 | 41 | MVCS 架构里的 service 和 model 在概念上有着非常多的相似之处. 因为这种相似性, model 和 service 继承了同样的 Actor 基类. 继承 Actor 基类可以获得很多应用程序架构内的功能. 在 MVCS 的 context 里, 我们通过利用继承 Actor 基类来定义应用程序所需要用来管理数据以及和外界通讯的 model 和 service 类. 本文档将把 model 和 service 类分别叫做 Model 和 Service. 42 | 43 | 澄清一点, 本文档把体现应用程序四个层的所有类都称为"framework actor"或"actor". 请不要和本例中包含的只被 Model 和 Service 类继承的 MVCS 类 Actor 混淆. 44 | 45 | h4(#modelandmodels). Model 46 | 47 | Model 类用来在 model 层对数据进行封装并为其提供 API. Model 会在对数据模型进行某些工作之后发出事件通知. Model 通常具有极高的便携性. 48 | 49 | h4(#serviceandservices). Service 50 | 51 | 一个 service 层的 Service 用来和"外面的世界"进行通讯. Web service, 文件存取, 或者其它任何应用程序范围之外的行为对 service 类都很适合. Service 类在处理外部事件时会广播系统事件. 一个 service 应该封装和外部服务的交互且具有非常高的便携性. 52 | 53 | h3(#frameworkevents). 框架事件 54 | 55 | Robotlegs 使用Flash的原生事件用于框架 actor 之间的通讯. 自定义事件类通常用于此用途, 虽然使用现有的 Flash 事件同样可行. Robotlegs 不支持事件冒泡, 因为它并不依赖 Flash 显示列表作为 event bus. 使用自定义类允许开发者通过给事件添加属性来为框架 actor 之间通讯所用的系统事件提供强类型的负载. 56 | 57 | 所有的框架 actor 都可以发送事件: Mediator, Service, Model, 和 Command. Mediator 是唯一接收框架事件的actor. Command 是在对框架事件的处理中被触发. 一个事件既可以被一个 Mediator 接收, 也可以触发一个 command. 58 | 59 | model 和 service 不应该监听和处理事件. 这样做会把它们紧耦合到应用程序特有逻辑而降低潜在的便携性和复用性. 60 | 61 | -------------------------------------------------------------------------------- /best-practices-zh-cn/6_commands.textile: -------------------------------------------------------------------------------- 1 | h2(#commands). Command 2 | 3 | Command 是短生命周期的无状态对象. 它们在被实例化和执行之后立即释放. Command 应该只在处理框架事件时被执行, 而不应该被任何其他框架 actor 实例化或执行. 4 | 5 | h3(#commandresponsibilities). Command 职责 6 | 7 | Command 被 Context 的 CommandMap 注册到 Context. CommandMap 在 Context 和 Command 类里默认可用. Command 类被注册到 Context 时接收4个参数: 一个事件类型; 响应这个事件时执行的 Command 类; 可选的事件类; 一个是否该 Command 只被执行一次随即被取消注册而不响应后续事件触发的一次性设置. 8 | 9 | h3(#triggeringcommands). 触发 Command 10 | 11 | Command 被 Mediators, Services, Models, 和其它 Command 广播的框架事件触发. 典型的, 触发这个 Command 的事件会被注入到这个 Command, 以提供对其属性/负载的访问: 12 | 13 |
14 | public class MyCommand extends Command
15 | {
16 | 	[Inject]
17 | 	public var event:MyCustomEvent;
18 | 	
19 | 	[Inject]
20 | 	public var model:MyModel;
21 | 			
22 | 	override public function execute():void
23 | 	{
24 | 		model.updateData( event.myCustomEventPayload )
25 | 	}
26 | }
27 | 
28 | 29 | 一个被映射的 command 在响应一个框架事件时被实例化, 所有已被映射, 并被 [Inject] 元数据标签标记过的依赖都会被注入到这个 Command. 另外, 触发这个 Command 的事件实例也会被注入. 当这些依赖被注入完毕, Command 的执行方法会被自动调用, Command 便会进行它的工作. 你不需要, 而且不应该直接调用 execute() 方法. 这是框架的工作. 30 | 31 | h3(#chainingcommands). 链接 Command 32 | 33 | 链接 command 也是可行的: 34 | 35 |
36 | public class MyChainedCommand extends Command
37 | {
38 | 	[Inject]
39 | 	public var event:MyCustomEvent;
40 | 	
41 | 	[Inject]
42 | 	public var model:MyModel;
43 | 			
44 | 	override public function execute():void
45 | 	{
46 | 		model.updateData( event.myCustomEventPayload )
47 | 		
48 | 		//UPDATED_WITH_NEW_STUFF 触发一个 command 的同时被
49 | 		//一个 mediator 接收然后更新一个View Component, 但是只在需要这个响应的时候
50 | 		if(event.responseNeeded)
51 | 		    dispatch( new MyCustomEvent( MyCustomEvent.UPDATED_WITH_NEW_STUFF, model.getCalculatedResponse() ) )
52 | 	}
53 | }
54 | 
55 | 56 | 使用这种方法可以把需要的任意多的 Command 链接在一起. 上面的例子使用了一个条件语句. 如果条件不满足 Command 就不会被链接. 这为你的 Command 执行应用程序工作提供了极大的灵活性. 57 | 58 | h3(#decouplingtiers). 应用程序层的解耦 59 | 60 | Command 是解耦一个应用程序里各个 actor 的非常有用的机制. 因为一个 Command 永远不会被 Mediator, Model 或者 Service 实例化或执行, 这些类也就不会被耦合到 command, 甚至都不知道 command 的存在. 61 | 62 | 为了履行它们的职责, Command 可能: 63 | 64 | * 映射 Mediator, Model, Service, 或者 Context 里的其它 Command 65 | * 广播可能被 Mediator 接收或者触发其它 Command 的事件. 66 | * 被注入Model, Service, 和Mediator 以直接进行工作. 67 | 68 | bq(note). 需要注意的是, 不建议在一个 Command 里直接和 Mediator 交互. 虽然这是可行的, 但会将这个 Mediator 耦合到这个 Command. 因为 Mediator 不像 Service 和 Model, 它可以接受系统事件, 更好的做法是让 Command 广播事件, 然后让需要响应这些事件的 Mediator 监听它们. 69 | 70 | -------------------------------------------------------------------------------- /best-practices-zh-cn/7_mediators.textile: -------------------------------------------------------------------------------- 1 | h2(#mediators). Mediator 2 | 3 | Mediator 类用来作为用户交互和系统的 View Component 之间的中介. 一个 Mediator 可以在多个级别的粒度上履行它的职责, 中介一个应用程序整体和它的子组件, 或者一个应用程序的任何和所有子组件. 4 | 5 | h3(#mediatorresponsibilities). Mediator 职责 6 | 7 | Flash, Flex 和 AIR 应用程序为富视觉用户界面组件提供了无限的可能. 所有这些平台都提供了一套组件, 像 DataGrid, Button, Label 和其它常用的UI组件. 也可以继承这些基本的组件来创建自定义组件, 创建复合组件, 或者完全重写新的组件. 8 | 9 | 一个 View Component 是任何的UI组件和/或它的子组件. 一个 View Component 是已被封装的, 尽可能多地处理自己的状态和操作. 一个 View Component 提供一个包含了事件, 简单方法和属性的API, . Mediators负责代表它所中介的View Component和框架交互. 这包括监听组件及其子组件的事件, 调用其方法, 和读取/设置组件的属性. 10 | 11 | 一个 Mediator 监听它的 View Component 的事件, 通过 View Component 暴露的 API 访问其数据. 一个 Mediators 通过响应其它框架 actor 的事件并对自己的 View Component 进行相应修改来代表它们. 一个 Mediator 通过转发 View Component 的事件或自己向框架广播合适的事件来通知其它的框架 actor. 12 | 13 | h3(#mappingamediator). 映射一个 Mediator 14 | 15 | 任何可以访问到 _mediatorMap_ 实例的类都可以映射 Mediator. 这包括 Mediator, Context, 和 Command 类. 16 | 17 | 这是映射一个 mediator 的语法: 18 |
mediatorMap.mapView( ViewClass, MediatorClass, autoCreate, autoRemove );
19 | 20 | h3(#automaticmediation). View Component 的自动中介 21 | 22 | 当映射一个 view component 类以获得中介时, 你可以指定是否自动为它创建 Mediator. 当此项为 _true_ 时 context 将监听这个 view component 的 ADDED_TO_STAGE 事件. 当收到这个事件这个 view component 会被自动中介, 它的 mediator 就可以开始发送和接收框架事件了. 23 | 24 | h3(#manualmediation). View Component 的手动中介 25 | 26 | 有时候可能不希望或者不可能使用 view component 的自动中介. 在这种情况下可以手动创建一个 Mediator 类的实例: 27 | 28 |
mediatorMap.createMediator(contextView);
29 | 30 | 这里假设这个 view component 之前已经被 _mediatorMap_ 的 _mapView()_ 方法映射过了. 31 | 32 | h3(#mappingthecontextview). 映射主程序 (_contextView_) Mediator 33 | 34 | 映射 contextView 到一个 mediator 是一个常见的模式. 这是个特殊情况, 因为自动中介对 contextView 不起作用, 因为它已经被添加到舞台上, 而不会再发出 _mediatorMap_ 自动中介所需要的事件了. 这个映射通常在持有 _contextView_ 引用的 Context 的 _setup()_ 方法里完成: 35 | 36 |
override public function startup():void
 37 | {
 38 | 	mediatorMap.mapView(MediateApplicationExample, AppMediator);
 39 | 	mediatorMap.createMediator(contextView);
 40 | }
41 | 42 | _contextView_ 并没有被完全中介, 还可以发送和接受框架事件. 43 | 44 | h3(#accessingmediatorviewcomponent). 访问一个 Mediator 的 View Component 45 | 46 | 当一个 View Component 在一个 Context 的 contextView 里被添加到舞台上的时候, 它默认地会被根据 MediatorMap 做映射时的配置被自动中介. 在一个基本的 mediator 里, _viewComponent_ 会被注入为被中介的 view component. 一个 Mediator 的 _viewComponent_ 属性是 Object 类型的. 在大多数情况下, 我们希望访问一个强类型的对象以从中获益. 为此目的, 我们注入被中介的 view component 的强类型实例: 47 | 48 |
public class GalleryLabelMediator extends Mediator implements IMediator
 49 | {
 50 | 	[Inject]
 51 | 	public var myCustomComponent:MyCustomComponent;
 52 | 		
 53 | 	/**
 54 | 	* 覆写 onRegister 是添加此 Mediator 关心的任何系统或 View Component 事件的好机会.
 55 | 	*/
 56 | 	override public function onRegister():void
 57 | 	{
 58 | 		//添加一个事件监听器到 Context 来监听框架事件
 59 | 		eventMap.mapListener( eventDispatcher, MyCustomEvent.DO_STUFF, handleDoStuff );
 60 | 		//添加一个事件监听器到被中介的 view component
 61 | 		eventMap.mapListener( myCustomComponent, MyCustomEvent.DID_SOME_STUFF, handleDidSomeStuff)
 62 | 	}
 63 | 	
 64 | 	protected function handleDoStuff(event:MyCustomEvent):void
 65 | 	{
 66 | 		//把事件的强类型负载设置到 view component 的属性. 
 67 | 		//View component 很可能基于这个新数据管理自己的状态.
 68 | 		myCustomComponent.aProperty = event.payload
 69 | 	}
 70 | 	
 71 | 	protected function handleDidSomeStuff(event:MyCustomEvent):void
 72 | 	{
 73 | 		//把这个事件转发到框架
 74 | 		dispatch(event)
 75 | 	}
 76 | }
77 | 78 | 通过这种方法我们现在可以很方便地访问被中介的 view component 的公开属性和方法. 79 | 80 | h3(#addingeventlistenerstomediators). 给一个 Mediator 添加事件监听 81 | 82 | 事件监听器是 Mediator 的眼睛和鼻子. 因为框架内的所有通讯都通过原生的Flash事件, Mediator 可以通过添加事件监听器来响应感兴趣的事件. 除了框架事件, Mediator同时监听所中介的 view component 的事件. 83 | 84 | 通常在 Mediator 的 onRegister 方法里添加事件监听. 在 Mediator 生命周期中的这个阶段, 它已经被注册并且它的 view component 和其它依赖也都已被注入. 具体的 Mediator 类必须覆写 onRegister 方法. 也可以在其它方法里添加事件监听, 比如响应框架事件和 view component 事件的事件处理方法里. 85 | 86 | Mediators 装备了一个有 mapListener() 方法的 EventMap. 这个方法注册每个被添加到 Mediator 的事件监听, 并且确保 mediator 被框架取消注册时删除这些事件监听. Flash 里删除事件监听是很重要的, 因为如果一个类里添加了事件监听而没有删除, Player将无法对此类进行运行时垃圾回收(GC, Garbage Collection). 也可以使用传统的 Flash 语法添加事件监听器, 但要注意也要手动把它们删除. 87 | 88 | h3(#mediatorslisteningfroframeworkevents). 监听框架事件 89 | 90 | 所有框架里的actor在实例化时都会被注入一个 _eventDispatcher_ 属性. 这个 _eventDispatcher_ 就是 Mediator 发送和接受框架事件的机制. 91 | 92 |
eventMap.mapListener(eventDispatcher, SomeEvent.IT_IS_IMPORTANT, handleFrameworkEvent)
93 | 94 | 通过此语法, 一个 Mediator 现在监听了 _SomeEvent.IT_IS_IMPORTANT_ 事件并在 _handleFrameworkEvent_ 方法里处理它. 95 | 96 | h3(#mediatorsdispatchingframeworkevents). 广播框架事件 97 | 98 | Mediator的一个很重要的职责就是向框架发送其它 actor 可能感兴趣的事件. 这些事件通常是在响应应用程序用户和被中介的 view component 之间的一些交互时发出的. 这里同样有一个可以减少发送事件到框架的代码输入的很有用的方法: 99 | 100 |
dispatch(new SomeEvent(SomeEvent.YOU_WILL_WANT_THIS, myViewComponent.someData))
101 | 102 | 这个事件现在可以被其它 Mediator 接收或者执行一个 command 了. 发出事件的 Mediator 并不关心其它的 actor 如何回应这个事件, 它只是简单地广播一条有事发生的消息. 一个 mediator 也可以监听自己发出的事件, 然后据此作出回应. 103 | 104 | h3(#mediatorslisteningforcomponentevents). 监听 View Component 事件 105 | 106 | Mediator 负责所中介的 view component 发出的事件. 这可以是个独立组件, 比如 TextField 或者 Button, 也可以是有嵌套层级的复杂组件. 当 mediator 收到 view component 发出的事件会使用指定的方法处理它. 和框架事件一样, EventMap 的 mapListener 方法是给一个 mediator 添加事件监听的首选. 107 | 108 |
eventMap.mapListener(myMediatedViewComponent, SomeEvent.USER_DID_SOMETHING, handleUserDidSomethingEvent)
109 | 110 | 响应一个 view component 的事件时, 一个 mediator 可能: 111 | 112 | * 考察事件的负载 (如果有) 113 | * 考察 view component 的当前状态 114 | * 对 view component 进行需要的工作 115 | * 发送系统事件以通知其它actor有事发生 116 | 117 | h3(#accessingmodelsandservicesfrommediators). 通过 Mediator 访问 Model 和 Service 118 | 119 | 你的 mediator 可以监听 Service 和 Model 类派出的系统事件来提高松耦合性. 通过监听事件, 你的 mediator 不需要关心事件来源, 而只需直接使用事件携带的强类型的负载. 因此, 多个 mediator 可以监听相同的事件然后根据所收到的数据调整自己的状态. 120 | 121 | 在一个 mediator 里直接访问 service 可以提供很大便利而不会带来严重的耦合性问题. 一个 service 并不存储数据, 只是简单地提供一个向外部service发送请求并接受响应的API. 能够直接访问这个API可以避免在你的应用程序中增加不需要的 command 类来达到同样目的. 如果这个 service API 在很多 mediator 中通过相同的方式访问, 将此行为封装到一个 command 里有益于保持此行为的一致性并减少对此 service 的反复调用以及在你的 mediator 里的直接访问. 122 | 123 | 建议通过 model 和 service 实现的接口将 model 和 service 注入 mediator. 下面的 "Service 实例":#serviceexample 章节可以找到一个这样的例子. 124 | 125 | h3(#accessingothermediatorsfromamediator). 访问其它 Mediator 126 | 127 | 如同 Service 和 Model,在一个 Mediator 里也可以注入和访问其它的 Mediator. 这种做法是 *强烈不建议的* 因为这种紧耦合可以简单地通过使用框架事件进行通讯而避免. 128 | 129 | -------------------------------------------------------------------------------- /best-practices-zh-cn/8_models.textile: -------------------------------------------------------------------------------- 1 | h2(#models). Model 2 | 3 | Model 类用来管理对应用程序的数据模型的访问. Model 为其它框架actor提供一个 API 来访问, 操作和更新应用程序数据. 这个数据包括但不限于原生数据类型比如 String, Array, 或者像 ArrayCollection 一样的域特有对象或集合. 4 | 5 | Model 有时被当做简单的 Model 比如 UserModel, 有时也被当做 Proxy 比如 UserProxy. 在 Robotlegs 里, 这两种命名都是用作相同的目的, 为应用程序数据提供一个 API. 不管采用哪种命名 model 都继承提供了核心框架依赖和一些有用方法的 Actor 基类. 本文档将这些类当做 Model. 6 | 7 | h3(#modelresponsibilities). Model 职责 8 | 9 | Model类封装了应用程序数据模型并为其提供一个 API. 一个 Model 类是你的应用程序数据的看门人. 应用程序里的其它 actor 通过 Model 提供的 API 请求数据. 因为数据是通过 Model 更新, Model 装备了向框架广播事件的机制以向其它 actor 通知数据模型的变化使它们得以据此调整自己的状态. 10 | 11 | 除了控制对数据模型的访问, Model 通常也被用来保证数据状态的有效性. 这包括对数据进行计算, 或域特有逻辑的其它领域. Model 的这个职责非常重要. Model 是应用程序中最有潜力具有便携性的层. 通过把域逻辑放入 Model, 以后的 model 实现就不再需要像把域逻辑放入 View 或 Controller 层那样重复这些相同的逻辑, 12 | 13 | 作为一个例子, 你的 Model 里可能执行购物车数据的计算. 一个 Command 将会访问这个方法, 最终的计算结果将会被作为被某个 Mediator 监听的事件派发出去. 这个 mediator 将会根据这个被更新的数据更新自己的 view component, 应用程序的第一个迭代是个典型的 Flex 程序. 这个计算也很容易在一个 Mediator 甚至视图里进行. 应用程序的第二个迭代是一个需要全新视图元素的移动设备 Flash 应用程序. 因为这个逻辑在 Model 里, 所以可以很容易被两个完全不同元素的视图复用. 14 | 15 | h3(#mappingamodel). 映射一个 Model 16 | 17 | Injector 有几个方法可以用来将你的 Model 类映射到你的框架actor. 另外, 这些方法事实上可以用来注入任何类到你的类里. 18 | 19 | 将一个已存在的实例当做一个单例注入映射, 使用下面的语法: 20 |
injector.mapValue(MyModelClass, myModelClassInstance)
21 | 22 | 为每个注入映射一个类的新实例, 使用下面的语法: 23 |
injector.mapClass(MyModelClass, MyModelClass)
24 | 另外, 这也可以用来使用被注入的实现某接口的合适的类来映射这个用来注入的接口. 25 |
injector.mapClass(IMyModelClass, MyModelClass)
26 | 27 | 为某个接口或类映射一个单例实例, 使用下面的语法: 28 |
injector.mapSingleton(MyModelClass, MyModelClass)
29 | 30 | 需要注意重要的一点, 当提及上面的一个单例时, 它并不是一个单例模式的单例. 在这个 Context 之外并不强制它作为一个单例. Injector 简单地确保这个类的唯一一个实例被注入. 这对处理你的应用程序数据模型的 Model 非常重要. 31 | 32 | h3(#dispatchingeventsfrommodel). 从一个Model里广播事件 33 | 34 | Model 类提供一个方便的 _dispatch_ 方法用来发送框架事件: 35 | 36 |
dispatch( new ImportantDataEvent(ImportantDataEvent.IMPORTANT_DATA_UPDATED))
37 | 38 | 有很多理由派发一个事件, 包括但不限于: 39 | 40 | * 数据已被初始化并准备好被其它 actor 使用 41 | * 一些数据片被添加到 Model 42 | * 数据被从 Model 中删除 43 | * 数据已改变或者更新 44 | * 数据相关的状态已改变 45 | 46 | h3(#listeningforeventsinmodel). 在一个 Model 里监听框架事件 47 | 48 | 虽然技术上可能, 但 *强烈不建议* 这样做. 不要这样做. 只是为了说清楚: *不要这样做*. 如果你这样做了, 不要说你没被警告过. 49 | 50 | -------------------------------------------------------------------------------- /best-practices-zh-cn/9_services.textile: -------------------------------------------------------------------------------- 1 | h2(#services). Service 2 | 3 | Service 用来访问应用程序范围之外的资源. 这包括但当然不限于: 4 | 5 | * web services 6 | * 文件系统 7 | * 数据库 8 | * RESTful APIs 9 | * 通过 localConnection 的其它 Flash 应用程序 10 | 11 | Service 封装了这些和外部实体的交互, 并管理这个交互产生的 result , fault 或其它事件. 12 | 13 | 你可能注意到 Service 和 Model 的基类非常相像. 事实上, 你可能注意到除了类名, 它们其实是一样的. 那么为什么用两个类呢? Model 和 Service 类在一个应用程序里有完全不同的职责. 这些类的具体实现将不再相像. 如果没有这个分离, 你将经常发现 Model 类在访问外部服务. 这让 Model 有很多职责, 访问外部数据, 解析结果, 处理失败, 管理应用程序数据状态, 为数据提供一个 API, 为外部服务提供一个 API, 等等. 通过分离这些层有助于缓解这个问题. 14 | 15 | h3(#serviceresponsibilities). Service 职责 16 | 17 | 一个 Service 类为你的应用程序提供一个和外部服务交互的 API. 一个 service 类将连接外部服务并管理它收到的响应. Service 类通常是无状态的实体. 他们并不存储从外部服务收到的数据, 而是发送框架事件来让合适的框架 actor 管理响应数据和失败. 18 | 19 | h3(#mappingservice). 映射一个 Service 20 | 21 | 有 "injector 的多个可用的方法":#mappingwithinjector 可以用来映射你的 Service 类以注入你的其它框架 actor. 另外, 这些方法也可以用来注入事实上任何类到你的类里. 22 | 23 | 将一个已存在的实例当做一个单例注入映射, 使用下面的语法: 24 |
injector.mapValue(MyServiceClass, myServiceClassInstance)
25 | 26 | 为每个注入映射一个类的新实例, 使用下面的语法: 27 |
injector.mapClass(MyServiceClass, MyServiceClass)
28 | 另外, 这也可以用来使用被注入的实现某接口的合适的类来映射这个用来注入的接口. 29 |
injector.mapClass(IMyServiceClass, MyServiceClass)
30 | 31 | 为某个接口或类映射一个单例实例, 使用下面的语法: 32 |
injector.mapSingleton(MyServiceClass, MyServiceClass)
33 | 34 | 需要注意重要的一点, 当提及上面的一个单例时, 它并不是一个单例模式的单例. 在这个 Context 之外并不强制它作为一个单例. Injector 简单地确保这个类的唯一一个实例被注入. 35 | 36 | h3(#servicelisteningforframeworkevents). 在一个 Service 里监听框架事件 37 | 38 | 虽然技术上可能, 但 *强烈不建议* 这样做. 不要这样做. 只是为了说清楚: *不要这样做*. 如果你这样做了, 不要说你没被警告过. 39 | 40 | h3(#servicedispatchingframeworkevents). 广播框架事件 41 | 42 | Service 类提供一个方便的 _dispatch_ 方法用来发送框架事件: 43 | 44 |
dispatch( new ImportantServiceEvent(ImportantServiceEvent.IMPORTANT_SERVICE_EVENT))
45 | 46 | h3(#serviceexample). Service 示例 47 | 48 | 下面是来自 "Image Gallery":http://github.com/robotlegs/robotlegs-demos-Bundle/tree/master/FlickrImageGallery/ demo 的 Flickr service 类 . "The Flickr API AS3 Library":http://code.google.com/p/as3flickrlib/ 做了很多连接到 Flickr 的底层处理. 这个例子使用了它并为在这个例子范围内使用提供了一个简单的抽象. 49 | 50 |
package org.robotlegs.demos.imagegallery.remote.services
 51 | {
 52 | 	import com.adobe.webapis.flickr.FlickrService;
 53 | 	import com.adobe.webapis.flickr.Photo;
 54 | 	import com.adobe.webapis.flickr.events.FlickrResultEvent;
 55 | 	import com.adobe.webapis.flickr.methodgroups.Photos;
 56 | 	import com.adobe.webapis.flickr.methodgroups.helpers.PhotoSearchParams;
 57 | 	
 58 | 	import org.robotlegs.demos.imagegallery.events.GalleryEvent;
 59 | 	import org.robotlegs.demos.imagegallery.models.vo.Gallery;
 60 | 	import org.robotlegs.demos.imagegallery.models.vo.GalleryImage;
 61 | 	import org.robotlegs.mvcs.Actor;
 62 | 
 63 |     /**
 64 |      *  这个类使用了 Adobe 提供的 Flickr API 来连接到 
 65 |      *  Flickr 并获取图片. 它最开始加载当前最"有趣"的
 66 |      *  的照片, 同时也提供了搜索其它关键词的能力. 
 67 |      */
 68 | 	public class FlickrImageService extends Actor implements IGalleryImageService
 69 | 	{
 70 | 		private var service:FlickrService;
 71 | 		private var photos:Photos;
 72 | 		
 73 | 		protected static const FLICKR_API_KEY:String = "516ab798392cb79523691e6dd79005c2";
 74 | 		protected static const FLICKR_SECRET:String = "8f7e19a3ae7a25c9";
 75 | 		
 76 | 		public function FlickrImageService()
 77 | 		{
 78 | 			this.service = new FlickrService(FLICKR_API_KEY);
 79 | 		}
 80 | 		
 81 | 		public function get searchAvailable():Boolean
 82 | 		{
 83 | 			return true;
 84 | 		}
 85 | 				
 86 | 		public function loadGallery():void
 87 | 		{
 88 | 			service.addEventListener(FlickrResultEvent.INTERESTINGNESS_GET_LIST, handleSearchResult);
 89 | 			service.interestingness.getList(null,"",20)
 90 | 		}
 91 | 		
 92 | 		public function search(searchTerm:String):void
 93 | 		{
 94 | 			if(!this.photos)
 95 | 				this.photos = new Photos(this.service);
 96 | 			service.addEventListener(FlickrResultEvent.PHOTOS_SEARCH, handleSearchResult);
 97 | 			var p:PhotoSearchParams = new PhotoSearchParams()
 98 | 			p.text = searchTerm;
 99 | 			p.per_page = 20;
100 | 			p.content_type = 1;
101 | 			p.media = "photo"
102 | 			p.sort = "date-posted-desc";
103 | 			this.photos.searchWithParamHelper(p);				
104 | 		}
105 | 		
106 | 		protected function handleSearchResult(event:FlickrResultEvent):void
107 | 		{
108 | 			this.processFlickrPhotoResults(event.data.photos.photos);
109 | 		}
110 | 		
111 | 		protected function processFlickrPhotoResults(results:Array):void
112 | 		{
113 | 			var gallery:Gallery = new Gallery();
114 | 						
115 | 			for each(var flickrPhoto:Photo in results)
116 | 			{
117 | 				var photo:GalleryImage = new GalleryImage()
118 | 				var baseURL:String = 'http://farm' + flickrPhoto.farmId + '.static.flickr.com/' + flickrPhoto.server + '/' + flickrPhoto.id + '_' + flickrPhoto.secret;
119 | 				photo.thumbURL = baseURL + '_s.jpg';
120 | 				photo.URL = baseURL + '.jpg';
121 | 				gallery.photos.addItem( photo );
122 | 			}
123 | 			
124 | 			dispatch(new GalleryEvent(GalleryEvent.GALLERY_LOADED, gallery));
125 | 		}
126 | 		
127 | 	}
128 | }
129 | 130 | FlickrGalleryService 提供了一个连接到一个 gallery 服务的非常简单的接口. 应用程序可以 _loadGallery_, _search_, 并查询 _searchAvailable_ 是 true 还是 false. IGalleryService 接口定义的接口: 131 | 132 |
package org.robotlegs.demos.imagegallery.remote.services
133 | {
134 | 	public interface IGalleryImageService
135 | 	{
136 | 		function loadGallery():void;
137 | 		function search(searchTerm:String):void;
138 | 		function get searchAvailable():Boolean;
139 | 	}
140 | }
141 | 142 | h4(#serviceimplementsinterface). Services 应该实现一个接口 143 | 144 | 通过创建实现了接口的 service, 为了测试在运行时切换它们, 或者对应用程序的最终用户提供对其它 service 的访问将会很简单. 比如, FlickrGalleryService 可以很容易替换为 XMLGalleryService: 145 | 146 |
package org.robotlegs.demos.imagegallery.remote.services
147 | {
148 | 	import mx.rpc.AsyncToken;
149 | 	import mx.rpc.Responder;
150 | 	import mx.rpc.http.HTTPService;
151 | 	
152 | 	import org.robotlegs.demos.imagegallery.events.GalleryEvent;
153 | 	import org.robotlegs.demos.imagegallery.models.vo.Gallery;
154 | 	import org.robotlegs.demos.imagegallery.models.vo.GalleryImage;
155 | 	import org.robotlegs.mvcs.Actor;
156 | 
157 | 	public class XMLImageService extends Actor implements IGalleryImageService
158 | 	{
159 | 		protected static const BASE_URL:String = "assets/gallery/";
160 | 		
161 | 		public function XMLImageService()
162 | 		{
163 | 			super();
164 | 		}
165 | 
166 | 		public function get searchAvailable():Boolean
167 | 		{
168 | 			return false;
169 | 		}
170 | 				
171 | 		public function loadGallery():void
172 | 		{
173 | 			var service:HTTPService = new HTTPService();
174 | 			var responder:Responder = new Responder(handleServiceResult, handleServiceFault);
175 | 			var token:AsyncToken;
176 | 			service.resultFormat = "e4x";
177 | 			service.url = BASE_URL+"gallery.xml";
178 | 			token = service.send();
179 | 			token.addResponder(responder);
180 | 		}
181 | 		
182 | 		public function search(searchTerm:String):void
183 | 		{
184 | 			trace("search is not available");
185 | 		}
186 | 		
187 | 		protected function handleServiceResult(event:Object):void
188 | 		{
189 | 			var gallery:Gallery = new Gallery();
190 | 						
191 | 			for each(var image:XML in event.result.image)
192 | 			{
193 | 				var photo:GalleryImage = new GalleryImage()
194 | 				photo.thumbURL = BASE_URL + "images/" + image.@name + '_s.jpg';
195 | 				photo.URL = BASE_URL + "images/" + image.@name + '.jpg';
196 | 				gallery.photos.addItem( photo );
197 | 			}
198 | 			
199 | 			dispatchEvent(new GalleryEvent(GalleryEvent.GALLERY_LOADED, gallery));
200 | 		}
201 | 		
202 | 		protected function handleServiceFault(event:Object):void
203 | 		{
204 | 			trace(event);
205 | 		}
206 | 	}
207 | }
208 | 209 | XML gallery 提供了和 Flickr 一样的方法可以访问并可在任何 IGalleryService 接口被调用的地方进行替换. 这些 service 派发相同的事件并且在最终的应用程序里很难区分. 在这个例子里, 搜索并没有被实现, 但搜索功能在这个 service 里也同样可以很容易实现, 210 | 211 | 建议所有的 service 都实现一个定义了它们 API 的接口. 在框架 actor 里接收一个 service 作为依赖注入时可以请求这个接口, 而不是具体的实现. 212 | 213 |
injector.mapSingletonOf(IGalleryService, FlickrGalleryService);
214 | 215 |
[Inject]
216 | public var galleryService:IGalleryService
217 | 218 | 你可以通过简单地改变注入来使用你的类代替这个 gallery service: 219 | 220 |
injector.mapSingletonOf(IGalleryService, XMLGalleryService);
221 | 222 | 这种方式可以为一个应用程序提供健壮性, 灵活性, 和增强的可测试性. 223 | 224 | h4(#parsingresultsinservice). 在一个 Service 里解析数据 225 | 226 | 在上面的例子里 service 类或者外部服务提供了不符合应用程序域的对象. Flickr service 提供强类型的 Photo 对象而 XML service 提供 xml. 这些数据类型都很好用, 但是并不符合我们应用程序的 context. 它们是外来者. 可以围绕外部数据类型对应用程序进行建模, 或者更可取地, 转换这些数据以符合应用程序. 227 | 228 | 应用程序里有两处可以进行这项操作/转换. Service 和 Model 都很适合. Service 是进入外部数据的第一个点, 所以它是操作一个外部服务返回的数据的更好的选择. 外来数据应该在第一个机会转换到应用程序域. 229 | 230 | _提供一个使用工厂类而不是在 service 里生成应用程序域对象的例子... 适当的_ 231 | 232 | 当数据被转换为应用程序域特有的对象之后发出带有强类型负载的事件以被对此关心的 actor 立即使用. 233 | 234 | h4(#serviceevents). Service 事件 235 | 236 | service 组合的最后一个部分是自定义事件. 没有事件的 service 只是哑巴. 他们可能做的任何工作都不会被其它框架成员注意到. 一个 service 将会使用自定义事件来向应用程序发出声音. 事件并不一定是唯一的意图. 如果这个 service 正在转换数据它可以使用一个普通的事件来派发强类型的数据给感兴趣的应用程序 actor. 237 | 238 | 239 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | if [ -z "$1" ] 3 | then 4 | echo "Input document name followed by [ENTER]" 5 | read doc 6 | else 7 | doc="$1" 8 | fi 9 | awk -v doc="../$doc.textile" 'FNR==1{print "">doc}{print > doc }' ../$doc/*.textile --------------------------------------------------------------------------------