Programowanie refleksyjne - Reflective programming

W informatyce , refleksyjny programowanie lub odbicie jest zdolność procesu do zbadania, introspekcji i modyfikować swoją własną strukturę i zachowanie.

Tło historyczne

Najwcześniejsze komputery były programowane w ich rodzimych językach asemblerowych , które były z natury refleksyjne, ponieważ te oryginalne architektury mogły być programowane poprzez zdefiniowanie instrukcji jako danych i użycie samomodyfikującego się kodu . Ponieważ większość programowania przeszła do języków skompilowanych wyższego poziomu , takich jak Algol , Cobol , Fortran , Pascal i C , ta zdolność refleksyjna w dużej mierze zniknęła, dopóki nie pojawiły się nowe języki programowania z refleksją wbudowaną w ich systemy typów.

Rozprawa doktorska Briana Cantwella Smitha z 1982 r. wprowadziła pojęcie refleksji obliczeniowej w proceduralnych językach programowania oraz pojęcie metakołowego interpretera jako składnika 3-Lisp .

Zastosowania

Odbicie pomaga programistom w tworzeniu ogólnych bibliotek oprogramowania do wyświetlania danych, przetwarzania różnych formatów danych, wykonywania serializacji lub deserializacji danych w celu komunikacji lub łączenia i rozdzielania danych dla kontenerów lub impulsów komunikacji.

Efektywne wykorzystanie refleksji prawie zawsze wymaga planu: ramy projektowej, opisu kodowania, biblioteki obiektów, mapy bazy danych lub relacji encji.

Refleksja sprawia, że ​​język jest bardziej dostosowany do kodu zorientowanego na sieć. Na przykład pomaga językom takim jak Java, aby działały dobrze w sieciach, umożliwiając bibliotekom serializację, tworzenie pakietów i zmianę formatów danych. Języki bez refleksji, takie jak C, są wymagane do używania kompilatorów pomocniczych do zadań takich jak Notacja składni abstrakcyjnej w celu wytworzenia kodu do serializacji i łączenia w pakiety.

Odbicie może być używane do obserwowania i modyfikowania wykonywania programu w czasie wykonywania . Komponent programu zorientowany na refleksję może monitorować wykonanie załącznika kodu i może modyfikować się zgodnie z pożądanym celem tego załącznika. Jest to zazwyczaj realizowane przez dynamiczne przypisywanie kodu programu w czasie wykonywania.

W obiektowych językach programowania , takich jak Java , odbicie umożliwia inspekcję klas, interfejsów, pól i metod w czasie wykonywania bez znajomości nazw interfejsów, pól, metod w czasie kompilacji. Umożliwia także tworzenie instancji nowych obiektów i wywoływanie metod.

Refleksja jest często wykorzystywana jako część testowania oprogramowania , na przykład do tworzenia/instancji w środowisku wykonawczym makiet obiektów .

Refleksja jest również kluczową strategią metaprogramowania .

W niektórych zorientowanych obiektowo językach programowania, takich jak C# i Java , odbicie może służyć do ominięcia reguł dostępności elementów członkowskich . W przypadku właściwości C# można to osiągnąć, pisząc bezpośrednio na (zwykle niewidocznym) polu zapasowym właściwości niepublicznej. Możliwe jest również znalezienie niepublicznych metod klas i typów oraz ręczne ich wywoływanie. Działa to w przypadku plików wewnętrznych projektu, a także bibliotek zewnętrznych, takich jak zespoły .NET i archiwa Java.

Realizacja

Język obsługujący odbicie zapewnia szereg funkcji dostępnych w czasie wykonywania, które w innym przypadku byłyby trudne do osiągnięcia w języku niższego poziomu. Niektóre z tych funkcji to możliwości:

  • Odkrywaj i modyfikuj konstrukcje kodu źródłowego (takie jak bloki kodu, klasy , metody, protokoły itp.) jako obiekty pierwszej klasy w czasie wykonywania .
  • Konwertuj ciąg pasujący do nazwy symbolicznej klasy lub funkcji na odwołanie do tej klasy lub funkcji lub wywołanie tej klasy.
  • Oceń ciąg tak, jakby był to instrukcja kodu źródłowego w czasie wykonywania.
  • Utwórz nowy interpreter dla kodu bajtowego języka, aby nadać nowe znaczenie lub cel konstrukcji programistycznej.

Te funkcje mogą być realizowane na różne sposoby. W MOO refleksja stanowi naturalną część codziennego idiomu programowania. Kiedy wywoływane są czasowniki (metody), różne zmienne, takie jak czasownik (nazwa wywoływanego czasownika) i this (dopełnienie, w którym czasownik jest wywoływany), są wypełniane, aby podać kontekst wywołania. Bezpieczeństwo jest zwykle zarządzane poprzez programowy dostęp do stosu wywołujących: ponieważ wywołujący () to lista metod, za pomocą których został ostatecznie wywołany bieżący czasownik, wykonanie testów na wywołujących ()[0] (polecenie wywołane przez pierwotnego użytkownika) pozwala na czasownik do ochrony przed nieautoryzowanym użyciem.

Języki kompilowane polegają na swoim systemie wykonawczym w celu dostarczania informacji o kodzie źródłowym. Skompilowany plik wykonywalny Objective-C , na przykład, zapisuje nazwy wszystkich metod w bloku pliku wykonywalnego, dostarczając tabelę odpowiadającą tym metodom (lub selektorom dla tych metod) skompilowanym do programu. W skompilowanym języku obsługującym tworzenie funkcji w czasie wykonywania, takim jak Common Lisp , środowisko wykonawcze musi zawierać kompilator lub interpreter.

Odbicie można zaimplementować dla języków bez wbudowanego odbicia przy użyciu systemu transformacji programu do definiowania automatycznych zmian w kodzie źródłowym.

Względy bezpieczeństwa

Odbicie może umożliwić użytkownikowi tworzenie nieoczekiwanych ścieżek przepływu sterowania przez aplikację, potencjalnie z pominięciem środków bezpieczeństwa. Może to zostać wykorzystane przez atakujących. Historyczne luki w Javie spowodowane niebezpiecznym odbiciem umożliwiały kodowi pobranemu z potencjalnie niezaufanych zdalnych maszyn wydostanie się z mechanizmu bezpieczeństwa piaskownicy Java . Przeprowadzone w 2013 roku na dużą skalę badanie 120 luk w zabezpieczeniach Javy wykazało, że niebezpieczne odbicie jest najczęstszą luką w Javie, choć nie jest to najczęściej wykorzystywana.

Przykłady

Następujące fragmenty utworzyć przypadek foo z klasy Foo i wywołać jego sposobu PrintHello . Dla każdego języka programowania pokazane są sekwencje wywołań normalnych i opartych na odbiciach.

C#

Poniżej znajduje się przykład w 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 / Object Pascal

Ten przykład Delphi / Object Pascal zakłada, że klasa TFoo została zadeklarowana w jednostce o nazwie 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;

eK

Poniżej znajduje się przykład w 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);

Udać się

Oto przykład w 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)
}

Jawa

Poniżej znajduje się przykład w Javie :

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

Poniżej znajduje się przykład w 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

Poniżej znajduje się przykład w Julia (język programowania) :

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

Cel C

Poniżej znajduje się przykład w Objective-C , sugerujący użycie frameworka OpenStep lub Foundation Kit :

// 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

Poniżej znajduje się przykład w Perlu :

# 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

Poniżej znajduje się przykład w 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);

Pyton

Poniżej znajduje się przykład w Pythonie :

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

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

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

r

Poniżej znajduje się przykład w 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))

Rubin

Poniżej znajduje się przykład w 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

Poniżej znajduje się przykład użycia 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

Zobacz też

Bibliografia

Cytaty

Źródła

Dalsza lektura

Zewnętrzne linki