Présentation de la POA avec AspectC++

Come David

Cet article a pour but de vous introduire la programmation par aspects et son implémentation en C++: Aspect C++

- Titre : Présentation de la POA avec AspectC++

- Auteur : Come David

- Parution : 22 juin 2009

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2009 Côme David. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Présentation de la POA

- I-A. Les limites de la POO

- I-B. Présentation de la POA et mécanismes

- I-B-a. Mécanisme

- I-B-b. Vocabulaire

- II. Aspect C++

- II-A. Sous Windows

- II-B. Sous Debian et Ubuntu

- III. Etude détaillée de Aspect C++

- III-A. 1er Exemple

I - Présentation de la POA

I-A - Les limites de la POO

la POO a pour bur but de découper le programme en une série d'objets indépendants qui s'échangent des messages via leur interface publique. Cette indépendance peut être acquise entre les différents objets métiers mais le code sera toujours envahie par une série d'instructions transverse à son but premier comme le logging, la gestion des invariants ou encore le verrouillage dans un contexte multi-thread. Regardons un exemple pour mieux comprendre.

class Compte
{
//...
int m_solde;
public:
void depot(int valeur)
{
m_solde += valeur;
}

void retrait(int valeur)
{
if(valeur < m_solde)
m_solde -= valeur;
else
std::cerr << Impossible de retirer plus que disponible" << std::endl;
}
};

Cette classe Compte, qui gère un compte en banque, est tout ce qu'il y a de plus basique. Imaginons qu'a chaque transaction on souhaite enregistrer l'ancien solde, la transaction, et le nouveau solde. On peut faire quelque chose dans le style suivant:

class Compte
{
// ...
public:
void depot(int valeur)
{
Logger << "Votre solde" << m_solde << "avec un depot de " << valeur << "est de ";
m_solde += valeur;
Logger << m_solde;
}

void retrait(int valeur)
{
if(valeur < m_solde)
{
Logger << "Votre solde"<<m_solde<<"avec un retrait de " << valeur << "est de ";
m_solde -= valeur;
Logger << m_solde;
}
else
{
std::cerr << "Impossible de retirer plus que disponible" << std::endl;
}
}

};

Pour le code suivant compile, plusieurs solutions.
La première est que Logger est une variable globale. Certains diront que c'est une faute grave mais c'est envisageable, regardez std::cout. Le problème avec cela, c'est qu'on polue l'espace global et que des risques de collisions sont présents, même avec des namespaces. Enfin, on peut souhaiter appliquer différents traitements de logging en fonction des classes, ce qui est difficilement permis avec les variables globales.

La solution est donc que ce soit la classe qui soit le propriéataire de l'objet Logger. Mais dans ce cas, on perd l'indépendance du code métier, ce dernier est lié à un pan vertical du programme alors que dans un monde idéal, il ne devrait pas.

La POA souhaite arreter cet envahissement en placant le code transverse non plus dans le code métier mais dans des aspects.

I-B - Présentation de la POA et mécanismes

I-B-a - Mécanisme

Etant donné que la POA souhaite séparer les aspects du code, il parait normal qu'ils soient situés dans fichiers différents que ceux du code source. Mais lors du processus gloable de création du logiciel, ils doivent être intégrés au code source: c'est le rôle du tisseur. Compararons la compilation "classique" et celle avec Aspect C++

Compilation classique avec g++

Compilation avec as++

Pour les personnes familières avec Qt, on peut dire qu'Aspect C++ est similaire au Moc dans le sens où il opère une opération pré-compilation dans un langage qui est une extension du C++.

I-B-b - Vocabulaire

La POA introduit un certain nombre de nouveaux mots de vocabulaire. Regardons les en détails car il est d'une grande importance par la suite

- Aspect: L'aspect est un moyen d'étendre de façon intrusive une un programme via les classes ou les fonctions. Un aspect se comporte de façon similaire à une classe, il peut avoir des données membres (fonctions ou variables) mais il contient aussi les déclarations des advices

- Advice: un advice est une déclaration qui va servir soit à exécuter du code lorsqu'un join point dynamique est atteint soit à introduire des élements dans le(s) joint point statiques

- Joint point: un Joint point est un endroit du code où les aspects peuvent venir s'interfacer. Un joint point dynamique est l'appel ou l'execution d'une fonction quelconque ou encore la création d'un objet. Un joint point statique est

- Pointcut: un Pointcut est un prédicat pour attraper les différents joint point.

- Pointcut expression: une Pointcut expression est une expression servant à définir un pointcut. Elle est composée de match expression (pour trouver les joints points, comme des regex) ainsi que d'opérateurs permettant de cibler certaines fonction en particulier. Le tout est lié par des opérateurs logiques classiques.

- Ordre de déclaration: Si plus d'un aspect doit intervenir sur le même join point, cette instruction permet de spécifier l'ordre d'appel.

- Slice: un slice est un fragment de code qui peut être introduit dans une classe.

II - Aspect C++

II-A - Sous Windows

II-B - Sous Debian et Ubuntu

L'installation sous debian et ubuntu est la plus classique qu'il soit puisqu'il existe un paquet nommé aspectc++; Il suffit donc de faire

aptitude install aspectc++

III - Etude détaillée de Aspect C++

III-A - 1er Exemple

Reprenons le code du paragraphe d'introduction et rendons le compilable:

#include <iostream>

template <class U> class Logger{
U& Ref;
public:

Logger& operator << (U& (*pf)(U&))
{
pf(Ref);
return *this;
}

template <class T> Logger& operator << (const T& t)
{
Ref << t;
return *this;
}

Logger(U& r=std::cout) : Ref(r)
{}
};

class Compte
{
Logger<std::ofstream> m_log;
int m_solde;
// ...
public:
Compte(int solde=100) : m_log(std::cout), m_solde(solde)
{}

void depot(int valeur)
{
m_log << "Votre ancien solde est de " << m_solde << std::endl;
m_solde += valeur;
m_log << "Votre nouveau solde est de " << m_solde << std::endl;
std::cout << std::endl;
}

void retrait(int valeur)
{
if(valeur < m_solde)
{
m_log << "Votre ancien solde est de " << m_solde << std::endl;
m_solde -= valeur;
m_log << "Votre nouveau solde est de " << m_solde << std::endl;
std::cout << std::endl;
}
else
{
std::cerr << "Impossible de retirer plus que disponible" << std::endl;
}
}

};

Les aspects liés au logging sont redondants. Voici le code équivalent avec les aspects

#include <iostream>
#include "compte.h"

Compte::Compte(int solde) : /*m_log(std::cout)*/m_solde(solde)
{}

void Compte::depot(int valeur)
{
m_solde += valeur;
}

void Compte::retrait(int valeur)
{
if(valeur < m_solde)
{
m_solde -= valeur;
}
else
{
std::cerr << "Impossible de retirer plus que disponible" << std::endl;
}
}

int main(int argc, char const *argv[])
{
Compte c(200);
c.depot(10);
return 0;
}

Le header qui va avec ce code:

#ifndef COMPTE_H
#define COMPTE_H

#include <sstream>

template <class U>class Logger{
U& Ref;
public:
//pour les manipulateurs de flux, comme std::endl
Logger& operator << (U& (*pf)(U&))
{
pf(Ref);
return *this;
}

template <class T> Logger& operator << (const T& t)
{
Ref<<t;
return *this;
}

Logger(U& r=std::cout) : Ref(r)
{}
};

class Compte
{
int m_solde;
public:
Compte(int solde = 100);
void depot(int valeur);
void retrait(int valeur);
};

#endif

Et le fichier aspect correspondant

#include <iostream>
#include "compte.h"

aspect TraceCompte
{
private:

pointcut what() = "Compte";
advice what() : slice class
{
protected:
Logger<std::ostream> m_log;
};

advice execution("% ...::%(...)") && within(what()) && that(compte) : around(Compte& compte)
{
compte.m_log  << "Avant votre solde etait de " << compte.m_solde << std::endl;
tjp->proceed();
compte.m_log << "Avant nouveau solde est de " <<  compte.m_solde << std::endl;
}
};

Je ne commenterai ici que le code lié à la programmation par aspect.