Archive

Archive for août 2012

Comment générer du code avec des modèles T4

Que vous soyez un développeur .net, java, C, php,… ou quelqu’un qui fait quotidiennement du SQL , des batch, du XML, etc. Il vous est sûrement arrivé de vouloir générer vos scripts ou codes car ils se ressemblent beaucoup. Pour moi personnellement, à une époque où je faisais beaucoup de Php (du CodeIgniter :-P), je me lassais d’écrire tout le temps les codes des modèles d’accès aux données, des controllers et même des vues (qui ne font tout le temps que : lister/ajouter/modifier et supprimer des enregistrements en base de données); j’avais utilisé un outil bien connu pour la génération de code, qui s’appelle CodeSmith. Et cela faisait bien l’affaire, mais le hic, c’est que c’est un outil payant 😦

Mais si vous avez déjà Visual Studio, et même si vous voulez générer d’autres scripts/codes en dehors de C# ou VB; vous pouvez bien l’utiliser pour générer des codes. Et pour ceux qui ont déjà essayé CodeSmith, cela a une certaine ressemblance ! Il s’agit des modèles T4. Voici un how-to rapide pour ceux qui veulent s’initier. En tout cas, voici comment je l’utilise :

Premièrement, comment créer un modèle T4 ?

Après avoir créé un projet (une librairie de classe ou une application console ou même une application windows…), ajouter un nouvel élément et choisir un modèle T4 prétraité. Cela nous permet d’avoir un fichier d’extension .tt avec un fichier .cs associé qui porte le même nom.

Utilisation simple des balises T4

Comme de nombreux utilitaires de génération (entre autres CodeSmith), un modèle T4 se base sur l’utilisation de balises spécifiques.

<#@      #> => pour écrire un code en en-tête comme le type de langage à utiliser par le moteur de génération (généralement c’est du C#), définir le type de code à générer (du texte ou du C# ou du SQL ou du XML, ou tout ce qu’on veut), ou importer des assembly, référencer des namespace. Habituellement, ce que l’on va trouver dans un en-tête de template T4, c’est le genre de code qui suit :

T4

Ces lignes de code signifient que : on va générer du code avec du C#, et on va générer du texte (.txt). Par défaut; le code généré est d’extension .cs. Mais ce qui est embêtant avec Visual Studio, c’est que lorsqu’on fournit une extension .cs ou .vb au code à générer, il tente de le compiler à la volée au moment où l’on lancera la génération proprement dite. C’est utile dans certains cas, mais parfois, c’est gênant car nous n’aurons pas forcément les références aux assembly que le code généré aurait besoin en exécution. En effet, le moteur de génération peut être un projet vraiment à part… Mais de toute façon, même si on génère du .txt, au moment où l’on crachera le contenu vers un fichier, nous pouvons spécifier la vraie extension, comme le nom du fichier d’ailleurs.

Ici, en plus, et c’est un exemple réel 🙂 nous faisons référence à un assembly externe : Xxx.CodesGenerator.Library ainsi qu’aux namespaces définies juste en dessous. Cet assembly nous sert – juste en passant – de requêter la structure de notre base de données. Mais tout ça reste en dehors de cet article !

Le balise <# #> permet d’ écrire du code C# pour le moteur. Ceci nous permet d’écrire du code pour personnaliser notre génération : des clauses if, des boucles, des appels à des variables externes (on verra cela plus bas), etc. C’est équivalent à <% %> pour ceux qui connaissent du ASP.Net.

<#= #> sert à écrire du code qui va cracher quelque chose en sortie. C’est comme faire un « echo » en PHP… c’est équivalent à <%= %> lorsqu’on fait du ASP.Net.

Et c’est tout !!! Tout ce que vous écrivez en dehors de ces balises sera généré en sortie. Du moins, dans mon cas, ces trois balises me suffisent pour générer quelques choses d’utilisables.

Utiliser une variable fournie par du code externe

Dans tout le code du template, on peut récupérer une variable fourni par du code externe en passant par une propriété qui s’appelle Session. Dans l’exemple ci-dessous, nous utilisons une variable de type TableSchema (qui représente pour nous un schéma d’une table en base de données – c’est une classe que nous avons créé nous-même pour modéliser le schéma de notre base, ce la n’a rien à voir avec T4). Cela nous permet de générer un modèle POCO ou un mapping à partir d’une table donnée.

use_sessiont4

Nous verrons dans ce qui suit comment le code externe fournit la variable en question…

Lancer notre générateur à partir d’une application console

Dans le cas que je montre en exemple, nous avons juste besoin d’une application console très simple pour générer les codes et les cracher dans des fichiers. Pour cela, deux points sont à remarquer :

–          Un modèle tt est associé à un fichier cs qui porte le même nom. Ce fichier cs décrit en fait un objet que l’on peut utiliser pour générer le code proprement dit. Cet objet a une propriété Session (une variable de type dictionnaire) dans laquelle nous pouvons passer n’importe quel type d’objet. Et ce sera utilisé à l’intérieur du template lorsque ce dernier va générer le code.

–          L’objet associé à notre modèle possède aussi une méthode particulière qui s’appelle TransformText() qui permet de générer le code en interprétant les balises T4 à l’intérieur. C’est comme un moteur asp.net qui interprète les balises asp.

Voici un bout de code qui après avoir parcouru les tables en bases de données, va générer pour chaque table les modèles POCOs, les mappings etc. Remarquez la façon de passer une variable vers le template, et l’appel à la méthode TransformText(). Le reste n’est que du code qui permet de créer des fichiers et des dossiers relatifs à la génération…

Publicités

Pour les débutants : les interfaces/contrats en POO

août 23, 2012 1 commentaire

Quand j’ai commencé à programmer en Orienté-Objets, il y a quelques années de cela, j’avais du mal à comprendre l’utilité des interfaces. Pour ceux qui ne connaissent pas encore, un interface en POO est une définition d’objet sans les implémentations. Euh, c’est quoi ? un petit exemple :

Ici, on a un interface qui possède une méthode Refresh(), mais comme c’est un interface, la méthode Refresh() ne contient aucune implémentation. Et donc, on ne sait pas qu’est qu’elle va faire cette méthode… Un interface n’a donc aucune raison d’être si on ne décrit pas l’implémentation (ou les implémentations) qui va derrière. En C#, écrire qu’une classe implémente un interface c’est comme écrire un héritage, donc avec le caractère « : »

Ma première question en tant que débutant à l’époque c’est : pourquoi on perd du temps à écrire le contenu de IRefreshable, alors que cela ne servira a priori à rien car le code qui va faire quelque chose se trouve dans l’implémentation. Dans cet exemple, écrire uniquement le contenu de MyFirstRefreshable doit être suffisant, non ?

Et bien, la réponse, c’est que cela dépend !

En effet, le code utilisateur (le code qui va utiliser MyRefreshable) n’est pas sensé connaître ce que va faire la méthode Refresh(). Pour le code utilisateur, l’important est de savoir que face à un objet « IRefreshable », il a une méthode Refresh(), qui comme son nom l’indique, est sensé faire un refresh de quelque chose. Imaginons que l’assembly qui contient notre interface IRefreshable est un boitier qui communique de deux côtés : un côté avec le code utilisateur et un autre avec le code qui implémente notre interface (l’assembly qui contient MyFirstRefreshable).

– La communication avec le code utilisateur est du genre :  » voici un objet IRefreshable, et comme c’est un objet IRefreshable, il a une méthode Refresh(), alors utilises-le si tu en as besoin ! »

– La communication avec le code d’implémentation est du genre :  » si tu veux me fournir un objet IRefreshable, tu dois respecter un contrat : tout objet IRefreshable possède obligatoirement une méthode Refresh()… ».

Pas clair ? euh. Prenons un exemple plus ou moins bête :). Imaginons que l’assembly qui contient IRefreshable est un fabricant de PC (un ordi) qui sont munis de ports USB. Et bien, c’est comme si ce fabricant discute avec les fournisseurs de périphériques USB ainsi qu’aux utilisateurs de ports USB comme suit :

« Voici la forme d’un port USB, avec telle dimension. Pour échanger des données avec ce type de port , il faut respecter tel ou tel fréquence, tel ou tel amplitude de courant, etc. Donc si vous voulez être un fournisseur de port USB, il faut respecter tout ça. Et de la même manière, pour tout ce qui veut utiliser un port USB, sachez que ce type de port possède les caractéristiques suivantes : … une fois que vous connaissez ces caractéristiques, utilisez-le comme bon vous semble. »

Autrement dit, ce fabricant  présente aux utilisateurs ce qu’est un port USB et ce qu’il peut faire => il leur présente un « interface » . L’utilisateur s’en fout de comment cela fonctionne derrière, mais il sait que cela fait ce qu’il veut et il l’utilise ! Et de l’autre côté, le fabricant impose aux fournisseurs de port USB de respecter tel ou tel caractéristiques pour que le port fonctionne comme les utilisateurs l’attendent => il leur présente un « contrat ». C’est en ce sens que le terme « interface »  signifie aussi « contrat » dans le monde de la POO. En fait, l’appelation « interface » ou « contrat » dépend de quel côté on regarde (du côté de ce qui utilise ou de ce qui implémente).

Et alors ? Dans quel but a-t-on besoin des interfaces ?

L’assembly ou tout simplement la couche qui expose l’interface (dans l’exemple le fabricant de PC) laisse donc libre voie aux couches ou classes externes qui veulent implémenter l’interface. En ce sens, cela permet :

1. de séparer les problématiques de définition et d’implémentation. Car la couche qui définit juste l’interface est sensée faire autre chose que décrire ce qu’un objet Ixxxx doit faire. Elle manipule par exemple ces objets pour les exposer à la couche d’utilisation…

2. d’avoir plusieurs implémentations différentes – voire des implémentations dans plusieurs couches diverses. C’est comme si on peut avoir beaucoup de fournisseurs de ports Usb, l’essentiel c’est que  » c’est un port USB », et que cela s’utilise bien avec un PC.

3. de faire abstraction des implémentations concrètes pour le code utilisateur. Quand un utilisateur branche quelque chose sur un port USB, il s’en fout pas mal de quel constructeur vient le port en question. En tout cas, c’est pas son problème de savoir comment cela fonctionne derrière.

Le framework .Net expose par exemple divers interfaces, à ne citer que les plus utilisés en pratique comme les IList, IEnumerable, IQueryable, IComparer, etc. Vous pouvez vous même définir un objet IList ou un objet IQueryable, mais à condition que vous respectez le contrat indiqué pour ces interfaces, i.e. que vous devez décrire toutes les méthodes/membres et propriétés imposés par ces interfaces. Une fois vous avez fourni des implémentations, le framework peut utiliser votre objet en tant que Ixxxx lorsqu’il a besoin, et votre objet Ixxxx est utilisable comme tel par un utilisateur de votre code. Voici un exemple beaucoup plus pratique :

Vous remarquez en tant que développeur que vous avez certains types d’objets en .Net, sur lesquels vous pouvez parcourir le contenu par un « foreach ». Si vous avez par exemple un objet List<String> (liste de string), vous pouvez faire un foreach sur cet objet pour parcourir tous les « string » qui sont à l’intérieur… Pourquoi vous pouvez faire appel à un foreach sur cet objet alors que sur d’autres objets, vous ne pouvez pas le faire. C’est parce que le type List<String> est un type qui implémente l’interface IEnumerable<String> (comme son nom l’indique, c’est un objet qu’on peut énumérer – donc qu’on peut parcourir ! Vous pouvez définir un objet IEnumerable mais ensuite vous devez décrire (implémenter) tout ce qu’un IEnumerable doit faire. Et une fois l’implémentation faite, votre objet IEnumerable peut s’utiliser avec un foreach (on peut l’énumérer). Ci-dessous, on a un exemple simple : une classe MyEnumerable qui implémente IEnumerable<String> (quelque chose dans laquelle on peut parcourir (foreach) et à chaque élément parcouru : on a un String).

Dans un code externe qui utilise notre objet MyEnumerable, on peut bien faire appel à un foreach !

Si on revient à notre petite caricature toute bête, le code qui implémente MyEnumerable est comme le fournisseur de port Usb. Il respecte le contrat en décrivant ce que fait un MyEnumerable. Tandis que la classe Utilisation (ci-dessus) est comme l’utilisateur de port Usb. Il s’en fout de comment MyEnumerable se débrouille pour qu’on puisse écrire un « foreach » avec, mais il l’utilise car c’est sensé être utilisé comme tel !