Apache Adobe Flex TutorialTutoriaux Adobe Flex & AIR en Français

29mai/100

Flex 4 – (2) Création de composants Flex ré-utilisables, les meilleures techniques

La création de composants est un must quand on travaille avec Flex. Mais autant faut-il savoir les créer de la meilleures manière afin qu'ils puissent être ré-utilisés et faciliter la maintenance de votre code.

A ce propos, Tom Sugden a écrit un très bon article qui explique bien les meilleures manières pour faire un bon composant Flex:

Writing Genuinely Reusable Flex Components

Pour faire simple, je vais traduire cet article, et rajouter quelques commentaires et suggestions…

Pour de gros projets, en entreprise, on se retrouve souvent à extraire un ensemble de composants réutilisables dans un projet Flex Library. En théorie, les mêmes composants peuvent être utilisés dans plusieurs modules et/ou dans des applications Flex / Air. Cela permet d'avoir une base de code solide et facile à maintenir pour un développement plus rapide.

Cependant, dans la pratique, certaines erreurs de conception sont faites, qui limitent la ré-utilisabilité des composants. Cet article explique comment créer un composant totalement ré-utilisable et met en avant certaines techniques du SDK Flex qui peuvent être appliquées à vos propres composants.

Comment définir un composant "vraiment" réutilisable?

Il y a différents niveaux de ré-utilisabilité, mais un composant ré-utilisable devrait toujours pouvoir faire le rendu de n'importe quel type de donnée. Il devrait pouvoir fonctionner avec un tableau d'objets basiques (type Object) ou une collection d'objet de type Kangaroo par exemple.

Le composant Flex DataGrid permet de faire cela:

<mx:DataGrid dataProvider="{ kangaroos }">
 <mx:columns>
 <mx:DataGridColumn headerText="Name" dataField="name"/>
 <mx:DataGridColumn headerText="Weight" labelFunction="calculateWeight"/>
 </mx:columns>
</mx:DataGrid>

Notez que les propriétés "dataField" et "labelFunction" indiquent au composant comment récupérer sa donnée depuis les objets de type Kangaroo, sans imposer de dépendance. On a ici deux mécanismes possibles pour rendre un composant vraiment ré-utilisable. Même si le développeur n'as pas le contrôle sur la classe Kangaroo (si elle fait partie d'une librairie externe par exemple), il peuvent tout de même être rendu dans une DataGrid.

L'Anti-Pattern Data Interface

Une des erreurs courantes est d'obliger la donnée qui va être rendue par un composant à implémenter une interface spécifique. Par exemple, on va prendre le composant DistributionBar qui fait le rendu d'un graphique comme celui-ci:

DistributionBar

On affiche un certain nombre de régions de tailles différentes, chacune contenant un label. Il est tentant de configurer ce composant pour qu'il utilise un tableau d'objet IRegion:

public interface IRegion
{
 function get label() : String;
 function get size() : int;
}

Le composant pourra ensuite extraire les informations de taille et de label pour chaque région par cette interface. L'argument en faveur de cette démarche est que l'interface découple le composant de l'objet concret qui est rendu. N'importe quel objet peut être rendu tant qu'il implémente IRegion, mais c'est dans le "tant que" que réside la faille de conception.

En imposant l'utilisation de l'interface IRegion, la ré-utilisabilité du composant est limitée. L'interface doit être implémentée par les classes du Model avant de pouvoir être rendue par le composant. Encore pire, si vos models sont produits par une autre librairie (dont vous pouvez ne pas avoir les sources) ou par une autre équipe, il se peut que vous ne puissiez pas changer les classes, elles devront donc être "wrappées" (encapsulées dans d'autres objets qui implémentent IRegion).

Pour ces raisons, le composant n'est pas vraiment ré-utilisable.

Notez que cette considération est importante pour les composants qui doivent être complètement ré-utilisables comme les DataGrid ou les List Flex. Pour des composants plus "métiers" ou le fait de pouvoir instancier son composant sans configurer trop de propriétés, on pourra utiliser le "pattern Data Interface". Parfois, il vaut mieux passer du temps à créer un composant qui soit ré-utilisable mais parfois il est plus aisé/rapide de créer plusieurs composants (par héritage) pour des besoins métiers spécifiques.

Les composants ré-utilisables du SDK Flex

Le SDK Flex contient de nombreux composants ré-utilisables, utilisant plusieurs approches:

  1. Data Fields
  2. Data Functions
  3. Data Descriptor
  4. Factory Objects

Ces approches et ces techniques peuvent être appliquées à vos propres composants pour les rendre ré-utilisables.

Data Fields

Un "data field" est une propriété de type String qui va spécifier le nom d'une autre propriété. Par exemple, la propriété "labelField" du composant ComboBox ou la propriété "dataField" et "dataTipField" de DataGridColumn:

<mx:ComboBox dataProvider="{ items }" labelField="name"/>

Le composant va ensuite utiliser le "data field" pour lire une valeur depuis les item dont il doit faire le rendu. Par exemple:

for each (var item:Object in dataProvider)
{
 var value:Object = item[dataField];
 // do something with the value
}

Voici une approche simple qui offre une grande flexibilité. Le composant peut ainsi faire le rendu de n'importe quelle propriété passée comme "dataField".

L'inconvénient de cette approche est le fait que l'on essaie d'accéder à une propriété de manière dynamique. Cette technique est plus lente qu'un accès à une propriété d'un objet fortement typé, et ne permet pas d'avoir une vérification à la compilation. Cependant, la flexibilité et le découplage apporté peut occulter ces inconvénients.

Pour ceux qui ne sont pas à l'aise avec la notation item[dataField], c'est simplement l'accès d'une propriété d'un Object comme un tableau. On utilise ici la propriété dataField, qui est une String, pour accéder à une propriété. En effet, dans ce cas, on a pas un objet typé (simplement un Object), on ne peut donc pas faire item.dataField car dataField est simplement une représentation String du nom de la propriété. La notation tableau permet d'accéder à n'importe quelle propriété d'un objet par son nom.

Data Functions

Une "data Function" est une propriété de type Function qui est utilisée pour spécifier une référence vers une autre fonction. Par exemple, la propriété labelFunction de ComboBox ou la propriété "dataFunction" de DataGridColumn:

<mx:DataGridColumn headerText="weight" dataFunction="calculateWeight"/>

Le composant invoque ensuite cette data function, en lui passant comme paramètre, l'item de donnée. Par exemple:

for each (var item:Object in dataProvider)
{
 var value:Object = dataFunction(item);
 // do something with the value
}

Cette approche est similaire à celle de l'utilisation d'un dataField, mais offre plus de flexibilité, car la fonction peut effectuer des calculs ou un formatage avant de renvoyer la valeur du composant à rendre.

Cette approche est sûrement celle qui peut vous permettre de réaliser des composants très complexes et performants. facilement La propriété filterFunction des ArrayCollection par exemple vous permet de réaliser un filtrage depuis l'extérieur sur vos objets. Vous pouvez ainsi, dans cette filterFunction, faire une conversion de type si vous le souhaitez car vous être dans votre contexte applicatif, en dehors du composant. Le découplage est ainsi maximal.

Data Descriptor

Un Data Descriptor est une interface par laquelle un composant peut analyser les éléments dont il doit faire le rendu. Un développeur peut ainsi passer sa propre implémentation de l'interface au composant pour le configurer. Un exemple de ce comportement peut être trouvé dans le composant Tree du SDK Flex:

<mx:Tree dataProvider="{ items }">
 <mx:dataDescriptor><my:MyDataDescriptor/></mx:dataDescriptor>
</mx:Tree>

Le Tree peut ensuite faire la découverte des caractéristiques de la donnée dont il a besoin, à travers le data Descriptor. Par exemple:

for each (var item:Object in dataProvider)
{
 var isBranch:Boolean = dataDescriptor.isBranch(item, dataProvider);
 // do something with the outcome
}

Cette approche est très puissante, mais seulement nécessaire pour les composants complexe tels que le Tree. L'utilisation de ce type de composant est moins aisée qu'une List ou un ComboBox mais le composant est découplé de la donnée dont il fait le rendu. Si un développeur veut faire le rendu d'une nouvelle classe d'objet dans un Tree, il va typiquement écrire une nouvelle implémentation de l'interface ITreeDataDescriptor (ou en utilisant l'héritage sur une classe qui implémente déjà ITreeDataDescriptor).

Cette approche est puissante et permet par exemple de corriger certains bugs de l'implémentation du Tree du Flex SDK. En effet, vous ne voulez pas re-compiler votre SDK Flex pour corriger un simple bug, comme celui du filtrage par filterFunction de données XML hiérarchiques. Dans ce cas-là, on passe au composant Tree, l'implémentation corrigée de ITreeDataDescriptor, sans toucher au code du Flex SDK ;) .

Factory Objects

Un objet Factory, en termes de développement de composant est une propriété de type IFactory, qui est utilisé pour instancier un enfant à l'exécution. Par exemple, la propriété itemRenderer de List et DataGrid ou la propriété dropDownFactory du composant ComboBox:

<mx:List dataProvider="{ items }" itemRenderer="my.package.MyItemRenderer"/>

Le composant utilise l'interface standard IFactory du SDK Flex pour créer de nouveaux objets à l'exécution:

if (itemRenderer is IDataRenderer)
{
 IDataRenderer(itemRenderer).data = item;
}

Cette approche donne un grand contrôle sur l'apparence visuelle des éléments qui composent le composant. En donnant un itemRenderer personalisé, on peut aboutir à des résultats totalement différents. Cependant, les composants utilisant des IFactory doivent définir des valeurs par défaut cohérente, pour que le composant puisse être utilisé dans des cas simple, sans setter d'itemRenderer en particulier. C'est le cas pour les composants de type ListBase comme DataGrid, qui utilise l'objet DataGridItemRenderer par défaut.

Il est important de noter que le compilateur Flex a une relation spéciale avec les propriétés de type IFactory. Quand il remarque une de ces propriétés dans un MXML, il va automatiquement générer du code pour convertir le nom de la classe en instances de ClassFactory. Cela rend le composant plus facile à utiliser, puisque les développeurs n'ont pas à faire les instanciation manuellement, mais doivent simplement spécifier un nom de classe dans le composant.

Si vous utilisez le même composant en ActionScript, vous devrez cependant faire l'instanciation avec ClassFactory vous-même.

Conclusion

Si vous devez créer des composants vraiment ré-utilisables, rappelez-vous d'une règle simple: ce composant doit pouvoir faire le rendu de n'importe quel type de donnée. Cela peut être fait en suivant les conventions posées par le SDK Flex, comme les dataField, dataFunction, data descriptor et Object Factory. Dans ce cas d'utilisation, il est important d'essayer de s'affranchir de l'utilisation d'interface qui limitent la ré-utilisation.

Rappelez-vous, le framework Adobe Flex est Open Source, vous pouvez donc aller voir comment les ingénieurs de chez Adobe ont conçu les composants que vous utilisez tous les jours de manière banale. Des composants comme la ComboBox comportent des mécanismes de synchronisation très intéressants. N'hésitez pas à aller fouiller dans les classes de Flex :)

Articles similaires

Commentaires (0) Trackbacks (0)

Aucun commentaire pour l'instant


Leave a comment

(required)

Aucun trackbacks pour l'instant