Programmation réflexive - Reflective programming

En informatique , la programmation réflexive ou la réflexion est la capacité d'un processus à examiner, à introspecter et à modifier sa propre structure et son propre comportement.

Contexte historique

Les premiers ordinateurs étaient programmés dans leurs langages d'assemblage natifs , qui étaient intrinsèquement réflexifs, car ces architectures originales pouvaient être programmées en définissant des instructions comme des données et en utilisant un code auto-modifiant . Alors que la majeure partie de la programmation passait à des langages compilés de niveau supérieur tels que Algol , Cobol , Fortran , Pascal et C , cette capacité de réflexion a largement disparu jusqu'à ce que de nouveaux langages de programmation avec réflexion intégrée dans leurs systèmes de types apparaissent.

La thèse de doctorat de Brian Cantwell Smith en 1982 a introduit la notion de réflexion informatique dans les langages de programmation procédurale et la notion d' interpréteur méta-circulaire en tant que composant de 3-Lisp .

Les usages

Reflection aide les programmeurs à créer des bibliothèques logicielles génériques pour afficher des données, traiter différents formats de données, effectuer une sérialisation ou une désérialisation de données pour la communication, ou regrouper et dégrouper des données pour des conteneurs ou des rafales de communication.

L'utilisation efficace de la réflexion nécessite presque toujours un plan : un cadre de conception, une description d'encodage, une bibliothèque d'objets, une carte d'une base de données ou des relations d'entité.

La réflexion rend un langage plus adapté au code orienté réseau. Par exemple, il aide des langages tels que Java à bien fonctionner dans les réseaux en permettant aux bibliothèques de sérialiser, de regrouper et de faire varier les formats de données. Les langages sans réflexion tels que C doivent utiliser des compilateurs auxiliaires pour des tâches telles que la notation de syntaxe abstraite afin de produire du code pour la sérialisation et le regroupement.

La réflexion peut être utilisée pour observer et modifier l'exécution du programme au moment de l' exécution . Un composant de programme orienté réflexion peut surveiller l'exécution d'une enveloppe de code et peut se modifier en fonction d'un objectif souhaité de cette enveloppe. Ceci est généralement accompli en affectant dynamiquement le code du programme au moment de l'exécution.

Dans les langages de programmation orientés objet tels que Java , la réflexion permet l' inspection des classes, des interfaces, des champs et des méthodes au moment de l'exécution sans connaître les noms des interfaces, des champs et des méthodes au moment de la compilation. Il permet également l' instanciation de nouveaux objets et l' invocation de méthodes.

La réflexion est souvent utilisée dans le cadre des tests logiciels , comme pour la création/l'instanciation d' objets fictifs à l'exécution .

La réflexion est également une stratégie clé pour la métaprogrammation .

Dans certains langages de programmation orientés objet tels que C# et Java , la réflexion peut être utilisée pour contourner les règles d' accessibilité des membres . Pour les propriétés C#, cela peut être réalisé en écrivant directement sur le champ de sauvegarde (généralement invisible) d'une propriété non publique. Il est également possible de trouver des méthodes non publiques de classes et de types et de les invoquer manuellement. Cela fonctionne pour les fichiers internes au projet ainsi que pour les bibliothèques externes telles que les assemblys .NET et les archives Java.

Mise en œuvre

Un langage prenant en charge la réflexion fournit un certain nombre de fonctionnalités disponibles au moment de l'exécution qui seraient autrement difficiles à accomplir dans un langage de niveau inférieur. Certaines de ces fonctionnalités sont les capacités à :

  • Découvrez et modifiez les constructions du code source (telles que les blocs de code, les classes , les méthodes, les protocoles, etc.) en tant qu'objets de première classe lors de l' exécution .
  • Convertir une chaîne correspondant au nom symbolique d'une classe ou d'une fonction en une référence ou un appel de cette classe ou fonction.
  • Évaluez une chaîne comme s'il s'agissait d'une instruction de code source au moment de l'exécution.
  • Créez un nouvel interpréteur pour le bytecode du langage afin de donner une nouvelle signification ou un nouveau but à une construction de programmation.

Ces fonctionnalités peuvent être implémentées de différentes manières. Dans MOO , la réflexion fait naturellement partie de l'idiome de programmation quotidien. Lorsque des verbes (méthodes) sont appelés, diverses variables telles que verbe (le nom du verbe appelé) et this (l'objet sur lequel le verbe est appelé) sont renseignées pour donner le contexte de l'appel. La sécurité est généralement gérée en accédant à la pile des appelants par programme : puisque les appelants () sont une liste des méthodes par lesquelles le verbe actuel a finalement été appelé, effectuer des tests sur les appelants ()[0] (la commande invoquée par l'utilisateur d'origine) permet verbe pour se protéger contre une utilisation non autorisée.

Les langages compilés s'appuient sur leur système d'exécution pour fournir des informations sur le code source. Un exécutable Objective-C compilé , par exemple, enregistre les noms de toutes les méthodes dans un bloc de l'exécutable, fournissant une table pour les faire correspondre avec les méthodes sous-jacentes (ou les sélecteurs pour ces méthodes) compilées dans le programme. Dans un langage compilé qui prend en charge la création de fonctions à l'exécution, comme Common Lisp , l'environnement d'exécution doit inclure un compilateur ou un interpréteur.

La réflexion peut être implémentée pour les langages sans réflexion intégrée en utilisant un système de transformation de programme pour définir les modifications automatisées du code source.

Considérations de sécurité

La réflexion peut permettre à un utilisateur de créer des chemins de flux de contrôle inattendus via une application, contournant potentiellement les mesures de sécurité. Cela peut être exploité par des attaquants. Des vulnérabilités historiques dans Java causées par une réflexion non sécurisée ont permis au code récupéré à partir de machines distantes potentiellement non fiables de sortir du mécanisme de sécurité du bac à sable Java . Une étude à grande échelle de 120 vulnérabilités Java en 2013 a conclu que la réflexion non sécurisée est la vulnérabilité la plus courante dans Java, mais pas la plus exploitée.

Exemples

Les extraits de code suivants créent une instance foo de classe Foo et appellent sa méthode PrintHello . Pour chaque langage de programmation , des séquences d'appel normales et basées sur la réflexion sont affichées.

C#

Voici un exemple en C# :

// Without reflection
Foo foo = new Foo();
foo.PrintHello();

// With reflection
Object foo = Activator.CreateInstance("complete.classpath.and.Foo");
MethodInfo method = foo.GetType().GetMethod("PrintHello");
method.Invoke(foo, null);

Delphi / Objet Pascal

Cet exemple Delphi / Object Pascal suppose qu'une classe TFoo a été déclarée dans une unité appelée Unit1 :

uses RTTI, Unit1;

procedure WithoutReflection;
var
  Foo: TFoo;
begin
  Foo := TFoo.Create;
  try
    Foo.Hello;
  finally
    Foo.Free;
  end;
end;

procedure WithReflection;
var
  RttiContext: TRttiContext;
  RttiType: TRttiInstanceType;
  Foo: TObject;
begin
  RttiType := RttiContext.FindType('Unit1.TFoo') as TRttiInstanceType;
  Foo := RttiType.GetMethod('Create').Invoke(RttiType.MetaclassType, []).AsObject;
  try
    RttiType.GetMethod('Hello').Invoke(Foo, []);
  finally
    Foo.Free;
  end;
end;

CE

Voici un exemple dans eC :

// Without reflection
Foo foo { };
foo.hello();

// With reflection
Class fooClass = eSystem_FindClass(__thisModule, "Foo");
Instance foo = eInstance_New(fooClass);
Method m = eClass_FindMethod(fooClass, "hello", fooClass.module);
((void (*)())(void *)m.function)(foo);

Aller

Voici un exemple en Go :

import "reflect"

// Without reflection
f := Foo{}
f.Hello()

// With reflection
fT := reflect.TypeOf(Foo{})
fV := reflect.New(fT)

m := fV.MethodByName("Hello")
if m.IsValid() {
    m.Call(nil)
}

Java

Voici un exemple en Java :

import java.lang.reflect.Method;

// Without reflection
Foo foo = new Foo();
foo.hello();

// With reflection
try {
    Object foo = Foo.class.newInstance();

    Method m = foo.getClass().getDeclaredMethod("hello", new Class<?>[0]);
    m.invoke(foo);
} catch (ReflectiveOperationException ignored) {}

JavaScript

Voici un exemple en JavaScript :

// Without reflection
const foo = new Foo()
foo.hello()

// With reflection
const foo = Reflect.construct(Foo)
const hello = Reflect.get(foo, 'hello')
Reflect.apply(hello, foo, [])

// With eval
eval('new Foo().hello()')

Julia

Voici un exemple en Julia (langage de programmation) :

julia> struct Point
           x::Int
           y
       end

# Inspection with reflection
julia> fieldnames(Point)
(:x, :y)

julia> fieldtypes(Point)
(Int64, Any)

julia> p = Point(3,4)

# Access with reflection
julia> getfield(p, :x)
3

Objectif c

Ce qui suit est un exemple en Objective-C , impliquant que le framework OpenStep ou Foundation Kit est utilisé :

// Foo class.
@interface Foo : NSObject
- (void)hello;
@end

// Sending "hello" to a Foo instance without reflection.
Foo *obj = [[Foo alloc] init];
[obj hello];

// Sending "hello" to a Foo instance with reflection.
id obj = [[NSClassFromString(@"Foo") alloc] init];
[obj performSelector: @selector(hello)];

Perl

Voici un exemple en Perl :

# Without reflection
my $foo = Foo->new;
$foo->hello;

# or
Foo->new->hello;

# With reflection
my $class = "Foo"
my $constructor = "new";
my $method = "hello";

my $f = $class->$constructor;
$f->$method;

# or
$class->$constructor->$method;

# with eval
eval "new Foo->hello;";

PHP

Voici un exemple en PHP :

// Without reflection
$foo = new Foo();
$foo->hello();

// With reflection, using Reflections API
$reflector = new ReflectionClass('Foo');
$foo = $reflector->newInstance();
$hello = $reflector->getMethod('hello');
$hello->invoke($foo);

Python

Voici un exemple en Python :

# Without reflection
obj = Foo()
obj.hello()

# With reflection
obj = globals()["Foo"]()
getattr(obj, "hello")()

# With eval
eval("Foo().hello()")

R

Voici un exemple en R :

# Without reflection, assuming foo() returns an S3-type object that has method "hello"
obj <- foo()
hello(obj)

# With reflection
class_name <- "foo"
generic_having_foo_method <- "hello"
obj <- do.call(class_name, list())
do.call(generic_having_foo_method, alist(obj))

Rubis

Voici un exemple en Ruby :

# Without reflection
obj = Foo.new
obj.hello

# With reflection
class_name = "Foo"
method_name = :hello
obj = Object.const_get(class_name).new
obj.send method_name

# With eval
eval "Foo.new.hello"

Xojo

Voici un exemple utilisant Xojo :

' Without reflection
Dim fooInstance As New Foo
fooInstance.PrintHello

' With reflection
Dim classInfo As Introspection.Typeinfo = GetTypeInfo(Foo)
Dim constructors() As Introspection.ConstructorInfo = classInfo.GetConstructors
Dim fooInstance As Foo = constructors(0).Invoke
Dim methods() As Introspection.MethodInfo = classInfo.GetMethods
For Each m As Introspection.MethodInfo In methods
  If m.Name = "PrintHello" Then
    m.Invoke(fooInstance)
  End If
Next

Voir également

Les références

Citations

Sources

Lectures complémentaires

  • Ira R. Forman et Nate Forman, Réflexion Java en action (2005), ISBN  1-932394-18-4
  • Ira R. Forman et Scott Danforth, Mettre les métaclasses au travail (1999), ISBN  0-201-43305-2

Liens externes