La Programmation Orientée Aspect (POA) est un paradigme qui complète la Programmation Orientée Objet (POO). Il se concentre sur l'objectif final d'une opération plutôt que sur les étapes intermédiaires. En essence, lorsqu'une méthode cible est déclenchée (par exemple, dans un contrôleur), des comportements définis par l'aspect (comme un décodage, une authentification) s'exécutent avant ou après cet appel.
Pour clarifier, comparons d'abord brièvement avec la Programmation Orientée Procédure (POP) et la POO.
Programmation Orientée Procédure (POP) : Elle se focalise sur la séquence d'étapes d'un processus. Par exemple, pour acheter des courses : sortir de chez soi, traverser des feux, marcher jusqu'au supermarché, choisir des articles et payer. Chaque étape est explicitement détaillée dans le code.
Programmation Orientée Objet (POO) : Elle encapsule des segments d'une procédure dans des objets. Chaque objet est responsable d'une tâche. Pour acheter des courses : un objet s'occupe de commander un taxi, un autre de sélectionner les articles, etc. Le programmeur orchestre ces objets.
Programmation Orientée Aspect (POA) : Elle abstrait les actions transversales (comme la marche vers le magasin) dans des aspects, gérés par un objet proxy. L'objet principal (le client) spécifie son objectif (acheter), et le proxy se charge de l'exécution complète, y compris les prérequis et postrequis. Le point de jointure est l'achat, et l'aspect inclut toutes les étapes associées.
Exemple de base avec la POO
let fonds = 100;
class Démarcheur {
static avant() {
console.log("Marche jusqu'au supermarché");
}
}
class Acheteur {
static acheter(montant) {
fonds -= montant;
console.log(`Achat effectué, débité ${montant}€`);
}
}
class Retourneur {
static après() {
console.log("Marche retour");
}
}
Démarcheur.avant();
Acheteur.acheter(55);
Retourneur.après();
Implémentation avec le paradigme POA via un Proxy
class ProxyAchats {
actionPréalable() {
console.log("Marche vers le magasin");
}
exécuterAchat(action, montant) {
this.actionPréalable();
action(montant);
this.actionPostérieure();
}
actionPostérieure() {
console.log("Retour à pied");
}
}
class Client {
constructor(proxy) {
this.proxy = proxy;
}
effectuerAchat(action, montant) {
this.proxy.exécuterAchat(action, montant);
}
}
const proxy = new ProxyAchats();
const moi = new Client(proxy);
// Définition des actions spécifiques
function acheterCourses(montant) {
fonds -= montant;
console.log(`Courses achetées, débité ${montant}€, solde: ${fonds}€`);
}
function acheterArticlesMénagers(montant) {
fonds -= montant;
console.log(`Articles ménagers achetés, débité ${montant}€, solde: ${fonds}€`);
}
moi.effectuerAchat(acheterCourses, 55);
// Résultat: Marche vers le magasin / Courses achetées, débité 55€, solde: 45€ / Retour à pied
fonds = 1000;
moi.effectuerAchat(acheterArticlesMénagers, 198);
// Résultat: Marche vers le magasin / Articles ménagers achetés, débité 198€, solde: 802€ / Retour à pied
Les méthodes actionPréalable et actionPostérieure agissent comme des filtres. Le proxy centralise la logique d'aspect, réutilisable pour différentes tâches.
Aspects plus avancés : Conseils d'aspect
On peut définir plusieurs types de conseils :
- Pré-exécution (before) : Avant la méthode cible.
- Post-exécution (after-returning) : Après l'exécution réussie.
- Gestion d'erreur (after-throwing) : En cas d'exception.
- Final (after) : Quelle que soit l'issue (exécuté en dernier).
- Autorour (around) : Enveloppe la méthode cible, contrôlant l'exécution avant et après.
Exemple avec des conseils multiples
class ProxyAchatsAvancé {
conseillerAvant() {
console.log("Chemin vers le magasin");
}
conseillerAprèsSuccès() {
console.log("Retour sain et sauf");
}
conseillerAprèsErreur() {
console.log("Annulation de la commande");
}
conseillerFinal() {
console.log("Fin de la transaction");
}
conseillerAutour(action, paramètre) {
console.log("Entrée dans le magasin");
action(paramètre);
console.log("Sortie du magasin");
}
exécuterAvecConseils(action, montant) {
try {
this.conseillerAvant();
this.conseillerAutour(action, montant);
this.conseillerAprèsSuccès();
} catch (erreur) {
this.conseillerAprèsErreur();
} finally {
this.conseillerFinal();
}
}
}
const proxyAvancé = new ProxyAchatsAvancé();
const clientAvecProxy = new Client(proxyAvancé);
clientAvecProxy.effectuerAchat(acheterCourses, 55);
/* Résultat:
Chemin vers le magasin
Entrée dans le magasin
Courses achetées, débité 55€, solde: 45€
Sortie du magasin
Retour sain et sauf
Fin de la transaction
*/
Cette approche démontre comment le POA isole les préoccupations transversales. Le choix de l'utiliser dépend du contexte métier, particulièrement lorsque plusieurs opérations partagent une logique de traitement similaire autour de leur cœur d'exécution.