Reflex #2 : vous reprendrez bien un peu de IOC ?



Reflex intègre un système d’Injection de contenu, qui est l’une des représentations de l’IOC.
Nous allons voir ici à quoi cela sert, et comment le mettre en œuvre dans Reflex.

Si ce n’est déjà fait, je vous laisse jeter un œil sur la première partie de cette architecture, et de récupérer le swc avant de lire la suite.

A quoi ça sert l’IOC ?

L’IOC ( Inversion Of Control ) est un patron d’architecture, possédant plusieurs représentations ( inversion de contrôle, injection de dépendance, …). Je ne vais pas faire un cours sur l’Injection de contenu : je vous laisse voir ici pour plus d’informations.

Dans Reflex, le but du package IOC est de décentraliser les objets de configurations de l’application. En gros, mettre les services d’accès aux données dans un fichier de configuration XML au lieu de les coder en dur dans l’application Flex.

Certains spécialistes me diront certainement que l’Injection de contenu n’est pas forcement une représentation de l’IOC, et donc un abus de langage pour Reflex : je répondrais par “traduisez ici IOC par Injection Of Content, et laissons tomber les patrons d’architecture !”.

Au commencement, le ServicesLocator

Nous avons vu dans la première partie comment réaliser simplement un appel à une méthode distante. Avoir un point d’accès global aux différents services est pratique lorsque l’on commence à avoir plusieurs services, ou si des vues différentes utilisent un service identique. Voyons comment procéder avec le premier exemple.

Reprenons l’exemple du tuto #1 : il faut charger les services dans le ServicesLocator. Nous allons donc modifier notre vue :

<?xml version="1.0" encoding="utf-8"?>
<mx:Application
	xmlns:mx="http://www.adobe.com/2006/mxml"
	layout="absolute"

	creationComplete="init()"

	xmlns:controller="controller.*"
	xmlns:model="model.*">

	<mx:Script>
		<![CDATA[
			import com.lafabrick.reflex.service.ServicesLocator;

			public static const SERVICE_USER:String = "userService";
			public static const SERVICE_OTHER:String = "otherService";

			private function init() : void
			{
				ServicesLocator.addService( SERVICE_USER, remoteUser );
				ServicesLocator.addService( SERVICE_OTHER, remoteOther );

				userControl.service = ServicesLocator.getService( SERVICE_USER );
			}

		]]>
	</mx:Script>

	<mx:RemoteObject id="remoteUser"
		destination="javaFacadeUser" showBusyCursor="true" />

	<mx:RemoteObject id="remoteOther"
		destination="javaFacadeOther" showBusyCursor="true" />

	<controller:UserController id="userControl" />

	<model:UserModel id="userModel" />

	<mx:VBox>
		<mx:TextInput id="login" />
		<mx:TextInput id="password" displayAsPassword="true" />

		<mx:Button label="connect"
			click="{userControl.login(login.text, password.text)}" />

		<mx:Label x="208" y="44"
			text="User Name : {userModel.userLogged.name}"/>
	</mx:VBox>

</mx:Application>
  • À la création de l’application ( creationComplete ), nous déclenchons la fonction init();.
  • Nous ajoutons les services RemoteObject dans le ServicesLocator. La signature de cette méthode est la suivante :
    ServicesLocator.addService( serviceName:String, serviceObject:AbstractService )
    

     

    • 



serviceName:String est le nom de référence utilisé pour indexer, et plus tard retrouver le bon service.
    • serviceObject:AbstractService est l’identifiant (id) de l’object service (le RemoteObject).

  • Nous supprimons la propriété service dans la déclaration mxml de UserController, pour la déclarer en ActionScript dans la fonction init(). 

ServicesLocator.getService( serviceName:String ) renvoie l’instance du service défini par serviceName, référencée dans le locator.

Note : Au niveau de l’appel du service, nous pourrions appeler la méthode getController de ControllerLocator.

userControl.login(login.text, password.text);


devient :

( ControllerLocator.getController( MonApplication.SERVICE_USER )
	as UserController ).login(login.text, password.text);

Dans ce cas, nous devons « caster » le retour de getController(…) en UserController ( sinon erreur ! Flex ne connaîtrait pas la méthode login )

Un composant pour la vue

Histoire de mettre en pratique un peu plus l’utilisation des “locators” de Reflex, nous allons créer un composant MXML étendant Canvas, qui va contenir la représentation visuelle de notre application. Nous allons utiliser ControllerLocator dans ce fichier pour récupérer le UserController.

<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas
	xmlns:mx="http://www.adobe.com/2006/mxml"
	width="100%"
	height="100%">

	<mx:Script>
		<![CDATA[
			import model.UserModel;
			import controller.UserController;
			import com.lafabrick.reflex.controller.ControllerLocator;
			import com.lafabrick.reflex.model.ModelLocator;

			[Bindable]
			private var userDataModel:UserModel =
				( ModelLocator.getModel( UserModel ) as UserModel);

		]]>
	</mx:Script>

	<mx:VBox horizontalCenter="0" verticalCenter="0">
		<mx:TextInput id="login" />
		<mx:TextInput id="password" displayAsPassword="true" />

		<mx:Button
			label="connect"
			click="{ ( ControllerLocator.getController( UserController )
				as UserController ).login( login.text, password.text )}" />

		<mx:Label x="208" y="44"
			text="User Name : {userDataModel.userLogged.name}"/>
	</mx:VBox>

</mx:Canvas>

Pour que le “Binding” fonctionne correctement entre la vue et le modèle, nous devons utiliser des pointeurs vers les modèles à utiliser.

Le fichier de configuration XML (IOC)

Nous venons de voir comment utiliser le ServiceLocator. Intégrons maintenant le système IOC pour ne plus avoir à le gérer !

Ce fichier vous permet de décrire des objets, pour ensuite les récupérer et les instancier dans votre application ( via ApplicationContext ). Ce fichier s’inspire fortement des fichiers de configuration de Spring, un framework puissant pour Java.

Ci-dessous le fichier représentant 2 services RemoteObject :

<?xml version="1.0" encoding="UTF-8"?>
<objects>

	<object id="remotingUser" className="mx.rpc.remoting.mxml.RemoteObject">
		<property name="destination" className="String">javaFacadeUser</property>
		<property name="showBusyCursor" className="Boolean">true</property>
	</object>

	<object id="remotingOther" className="mx.rpc.remoting.mxml.RemoteObject">
		<property name="destination" className="String">javaFacadeOther</property>
		<property name="showBusyCursor" className="Boolean">true</property>
	</object>

</objects>
  • Le tag <objects> englobe les différents objets définis dans le fichier. Il est défini par :
  • Un objet <object> est défini par :
    • un identifiant id. Cet id est celui que nous utiliserons plus tard dans l’application Flex pour retrouver et instancier l’objet.
    • un nom de classe className : la classe de référence.
  • Les propriétés des objets sont définies par :
    • leur nom name
    • leur classe className de référence.

Ici, un RemoteObject a besoin de 2 propriétés : la destination du type String, et éventuellement showBusyCursor du type Boolean.

Régler le problème d’inclusion de classe

Le “petit” problème de ce système est que la déclaration externe des objets fait que les classes ne sont pas intégrées dans le projet Flex. Le compilateur Flex ne trouve pas de référence aux classes, et ne les intègre donc pas dans la compilation. Un façon très simple de régler ce problème est de créer un petit fichier actionScript, et d’ajouter des objets bidons du même type que ceux définis dans votre fichier de config.

Exemple avec les services : (fichier includeService.as)

import mx.rpc.http.mxml.HTTPService;
import mx.rpc.remoting.mxml.RemoteObject;
import mx.rpc.soap.mxml.WebService;

private var ser1:RemoteObject;
private var ser2:HTTPService;
private var ser3:WebService;

Ce fichier sera intégré dans votre application.

Chargement et gestion du contexte XML

Vous allez voir : simple comme bonjour !
Nous allons ajouter le chargement du fichier via ApplicationContext. ApplicationContext permet de charger automatiquement le contexte cible, et de retrouver, instancier, et enregistrer automatiquement tous les services qui se trouvent dans votre fichier de configuration XML dans le ServicesLocator.

Votre application finale :

<?xml version="1.0" encoding="utf-8"?>
<mx:Application
	xmlns:mx="http://www.adobe.com/2006/mxml"
	layout="absolute"

	creationComplete="init()"

	xmlns:model="model.*"
	xmlns:view="view.*"
	xmlns:controller="controller.*"
	xmlns:service="service.*">

	<mx:Script>
		<![CDATA[
			import com.lafabrick.reflex.ioc.event.ContextEvent;
			import com.lafabrick.reflex.ioc.ApplicationContext;
			import com.lafabrick.reflex.controller.ControllerLocator;
			import com.lafabrick.reflex.service.ServicesLocator;

			include "includeService.as"

			private function init() : void
			{
				ApplicationContext.addEventListener(
					ContextEvent.CONTEXT_LOADED, onLoaded );

				ApplicationContext.load( "ioc/application-config.xml" );
			}

			private function onLoaded( event:ContextEvent ) :void
			{
				// Contexte chargé
				// ...
			}

		]]>
	</mx:Script>

	<controller:UserController id="userControl" />

	<model:UserModel id="userModel" />

	<view:LoginView id="view" />

</mx:Application>
  • Nous continuons à utiliser le “creationComplete” pour déclencher la fonction init() qui chargera le fichier de configuration.
  • Il ne faut pas oublier d’inclure au besoin les classes contenues dans notre fichier de config !
    include "includeService.as"
    
  • Dans la fonction init(), nous ajoutons un écouteur sur ApplicationContext (ContextEvent.CONTEXT_LOADED), qui déclenchera la fonction onLoaded quand le fichier de configuration sera chargé. Puis nous lançons le chargement du fichier de config ( ioc/application-config.xml ).
  • … Et nous incluons la vue LoginView.

Modification de notre UserController

Dans un soucis de clarté, nous allons créer 2 classes contenants les constantes référençants les noms des services (contenus dans le fichier de configuration), et les noms des méthodes de ces services.

MethodConstant (dans notre exemple uniquement la méthode login)

package service
{
	public class MethodConstant
	{
		public static const LOGIN:String = "login";
	}
}

ServiceConstant (les identifiants des objets contenus dans le fichier de configuration)

package service
{
	public class ServiceConstant
	{
		public static const SERVICE_USER:String = "remotingUser";
		public static const SERVICE_OTHER:String = "remotingOther";
	}
}

Nous venons de charger notre fichier de configuration au niveau de notre Application. Nous devons maintenant modifier notre contrôleur pour qu’il écoute le chargement du fichier (événement ContextEvent.CONTEXT_LOADED), et mette à jour sa propriété service.

package controller
{
	import com.lafabrick.reflex.controller.BaseController;
	import com.lafabrick.reflex.ioc.ApplicationContext;
	import com.lafabrick.reflex.ioc.event.ContextEvent;
	import com.lafabrick.reflex.model.ModelLocator;
	import com.lafabrick.reflex.service.ServicesLocator;

	import dataObject.UserDo;

	import model.UserModel;

	import mx.rpc.events.FaultEvent;
	import mx.rpc.events.ResultEvent;

	import service.MethodConstant;
	import service.ServiceConstant;

	public class UserController extends BaseController
	{

		public function UserController() : void
		{
			ApplicationContext.addEventListener(ContextEvent.CONTEXT_LOADED, onLoaded);
		}

		private function onLoaded( event:ContextEvent ) :void
		{
			// Contexte chargé
			this.service = ServicesLocator.getService( ServiceConstant.SERVICE_USER );
		}

		public function login( log:String, pass:String ) : void
		{
			executeMethod(MethodConstant.LOGIN, onLoginResult, onLoginFault, log, pass);
		}

		public function onLoginResult( event:ResultEvent ) : void
		{
			(ModelLocator.getModel( UserModel ) as UserModel).userLogged =
				event.result as UserDo;

			trace((ModelLocator.getModel( UserModel ) as UserModel).userLogged.name);
		}
		public function onLoginFault( event:FaultEvent ) : void
		{
			trace("resultat pourri ..."+event.fault);
		}

	}
}

Les sources de cette exemple

Et voilà ! Je vais préparer d’autres articles, notemment l’intégration de Reflex dans un projet Gumbo/Flex4. N’hésitez pas à poster vos retours, c’est en flexant qu’on devient flexeron.


Tags: , , , ,


3 commentaires ...

» RSS des commentaires
  1. Armetiz /
    -->

    Chapeau bas l’artiste !
    C’est en effet en flexant que l’on devient flexeron :p

  2. Alex /
    -->

    Interessant.
    D’autres frameworks sur le même sujet en AS: ASGARD, INDIGO, pixIoC.

    As-tu pensé, pour la partie IoC, au chargement dynamique de classes via des SWF ? Leur adresse pourrait être définie dans le descripteur xml des objets à instancier. On aurait un système de plugins et plusieurs ApplicationContext.

  3. fab /
    -->

    J’ai regardé un peu les autres frameworks, notamment Indigo qui me plais bien ! Reflex répond avant tout a un besoin de simplicité, et cette V1 est là pour proposer l’essentiel.
    L’idée du chargement dynamique de classe est très intéressante, et fera certainement partie intégrante de l’évolution de ce petit framework.


Soit pas timide...

Powered by WP Hashcash


Bad Behavior has blocked 333 access attempts in the last 7 days.