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.
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à).
Quelques exemples simples d'usages de la réflexivité dynamique suivent. Ces exemples ne font qu'effleurer le sujet, sans plus.
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.
Vous trouverez des exemples d'exploration dynamique des services de classes sur les pages suivantes :
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
Quelques liens pour enrichir le propos.