Réflexivité

La réflexivité est un mécanisme typiquement associé aux langages orientés objets, et par lequel un langage permet de représenter et d'utiliser les concepts qu'il sous-tend à même le langage. Dans la plupart des cas, on parlera de réflexivité dynamique, quoiqu'il existe aussi de la réflexivité statique.

Réflexivité dynamique (survol)

Pour prendre les exemples de Java et de C#, qui sont représentatifs, les concepts pouvant être représentés à même les objets du langage incluent :

Concept représenté En Java En C#
Classe Classe Class (en fait, techniquement, classe Class<T>) Classe Type
Instance Classe Object Classe Object
Interface Classe Type Classe Type
Constructeur Classe Constructor (ou plus précisément, classe Constructor<T>) Classe ConstructorInfo
Méthode Classe Method Classe MethodInfo
Attribut Classe Field Classe FieldInfo
Annotation Classe Annotation Classe Attribute
Assemblage / paquetage Classe Package Classe Assembly

La liste n'est pas exhaustive (loin de là).

Utilité de la réflexivité dynamique

Quelques exemples simples d'usages de la réflexivité dynamique suivent. Ces exemples ne font qu'effleurer le sujet, sans plus.

Instancier une classe par son nom

Ce qui suit est un programme C# directement inspiré d'un courriel de Pierre Prud'homme, en 2016. Dans ce programme, on trouve deux classes nommées respectivement RéflexivitéDynamique.A et RéflexivitéDynamique.B. Le programme principal saisit à la console le nom de la classe à instancier (sans le préfixe qu'est son espace nommé, ce dernier étant ajouté par le programme lui-même), puis l'instancie en appelant un constructeur acceptant deux string en paramètre (pour assurer la simplicité et la brièveté de l'exemple, les deux types exposent un tel constructeur).

using System;

namespace RéflexivitéDynamique
{
   class A
   {
      public string Valeur { get; private set; }
      public A(string s0, string s1)
      {
         Valeur = s0 + ", " + s1;
      }
      public override string ToString()
      {
         return Valeur;
      }
   }

   class B
   {
      public string Valeur { get; private set; }
      public B(string s0, string s1)
      {
         Valeur = (s0 + " et " + s1);
      }
      public override string ToString()
      {
         return Valeur;
      }
   }

   class Program
   {
      static void Main(string[] args)
      {
         Console.Write("Donnez le type voulu (la casse doit être respectée) : ");
         string nom = typeof(Program).Namespace + "." + Console.ReadLine();
         Type type = Type.GetType(nom);
         try
         {
            var obj = Activator.CreateInstance(type, "a", "b");
            Console.WriteLine("Le contenu de mon objet de type {0} est {1}", obj.GetType().Name, obj);
         }
         catch (Exception)
         {
            Console.WriteLine("Le type indiqué n'a pu être instancié");
         }
      }
   }
}

Un exemple équivalent en Java suit :

import java.io.*;
import java.lang.reflect.*;

class A {
   private String valeur;
   public String getValeur() {
      return valeur;
   }
   public A(String s0, String s1) {
      valeur = s0 + ", " + s1;
   }
   public String toString() {
      return getValeur();
   }
}

class B {
   private String valeur;
   public String getValeur() {
      return valeur;
   }
   public B(String s0, String s1) {
      valeur = s0 + " et " + s1;
   }
   public String toString() {
      return getValeur();
   }
}

public class Z {
   public static void main(String[] args) {
      System.out.print("Donnez le type voulu (la casse doit être respectée) : ");
      try {
         BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
         String nom = br.readLine();
         try {
            Class<?> type = Class.forName(nom);
            Constructor<?> ctor = type.getConstructor(new Class[]{ String.class, String.class });
            Object obj = ctor.newInstance("a", "b");
            System.out.println("Le contenu de mon objet de type " + obj.getClass() + " est " + obj);
         } catch (InstantiationException iex) {
            System.out.println("Erreur lors de l'instanciation");
         } catch (IllegalAccessException iex) {
            System.out.println("Accès illégal");
         } catch (IllegalArgumentException iex) {
            System.out.println("Paramètre illégal");
         } catch (InvocationTargetException itex) {
            System.out.println("Erreur de cible");
         } catch (NoSuchMethodException nsex) {
            System.out.println("Méthode introuvable");
         } catch (SecurityException sex) {
            System.out.println("Problème de sécurité");
         } catch (ClassNotFoundException cnex) {
            System.out.println("Le type indiqué est introuvable");
         }
      } catch (IOException ex) {
         System.out.println("Le type indiqué n'a pu être instancié");
      }
   }
}

Outre la gestion obligatoire des exceptions en Java, qui rend l'écriture un peu laborieuse dans un programme aussi dépendant de circonstances à l'exécution que celui-ci, les deux programmes se ressemblent beaucoup.

Inspecter les facilités offertes par une classe

Vous trouverez des exemples d'exploration dynamique des services de classes sur les pages suivantes :

Réflexivité statique (survol)

Par réflexivité statique, on entend raisonner sur les types et leurs services à la compilation plutôt que les instancier dynamiquement sur la base de leur nom. Ce type de réflexivité est surtout présent en C++ et s'exprime typiquement sur la base de traits.

Vous trouverez plusieurs exemples sur le sujet dans la section ../Divers--cplusplus/Metaprogrammation.html

Lectures complémentaires

Quelques liens pour enrichir le propos.


Valid XHTML 1.0 Transitional

CSS Valide !