Mechanizm refleksji

Mechanizm refleksji – pojęcie z dziedziny informatyki oznaczające proces, dzięki któremu program komputerowy może być modyfikowany w trakcie działania w sposób zależny od własnego kodu oraz od zachowania w trakcie wykonania. Paradygmat programowania ściśle związany z mechanizmem refleksji to programowanie refleksyjne.

Refleksja pozwala w łatwy sposób zarządzać kodem tak, jakby był danymi. Używa się jej najczęściej do zmieniania standardowego zachowania już zdefiniowanych metod lub funkcji, a także do tworzenia własnych konstrukcji semantycznych modyfikujących język. Z drugiej strony kod wykorzystujący refleksję jest mniej czytelny i nie pozwala na sprawdzenie poprawności składniowej i semantycznej w trakcie kompilacji (niewygodne śledzenie błędów).

Ciekawym przykładem mechanizmu refleksji są języki programowania, w których każdy element kodu źródłowego reprezentowany jest strukturami danych dostępnymi dla programisty. Nazywamy je językami homoikonicznymi, a kod źródłowy ich programów jest w istocie danymi. Od decyzji programisty zależy, które jego fragmenty będą wartościowane, a które traktowane jak wartości stałe niewymagające obliczeń. Proces ten może być wielokierunkowy, np. część kodu źródłowego może zostać potraktowana jak dane, przekształcona, a następnie znów wartościowana – w ten sposób działają tzw. makra składniowe w dialektach języka Lisp.

Mechanizm refleksji jest najczęściej spotykany w językach wysokiego poziomu, zwykle opartych na maszynie wirtualnej.

Przykłady

C#

Poniższy przykład demonstruje użycie refleksji w języku C Sharp używając pakietu System.Reflection

//bez refleksji
Foo foo = new Foo();
foo.hello();

//z użyciem refleksji
var type = Type.GetType("namespace.Foo");   //string powinien zawierać namespace naszej klasy
var foo = Activator.CreateInstance(type); //inicjalizacja obiektu określonego typu
MethodInfo inf = type.GetMethod("hello");
inf.Invoke(foo); // jako drugi parametr metoda Invoke przyjmuje tablicę Object[] są to parametry metody hello.

Objective-C

Poniższy przykład demonstruje użycie refleksji w języku Objective-C

// bez refleksji
Foo *foo = [[Foo alloc] init];
[foo hello];
[foo release];

// z refleksją
id foo = [[NSClassFromString(@"Foo") alloc] init];
SEL selector = NSSelectorFromString(@"hello");
[foo performSelector:selector];
[foo release];

Java

Poniższy przykład w języku Java wykorzystuje pakiet java.lang.reflect.

 // bez refleksji
 Foo foo = new Foo();
 foo.hello();

 // z refleksją
 Class cl = Class.forName("Foo");
 Method method = cl.getMethod("hello");
 method.invoke(cl.newInstance());

Oba fragmenty tworzą instancję klasy Foo, następnie wywołują metodę hello() tej klasy. Różnica polega na tym, że w pierwszym fragmencie nazwa klasy i metody są częścią kodu źródłowego, podczas gdy w drugim fragmencie możliwe jest przeniesienie ich do zmiennych, których wartość jest ustalana w czasie wykonania kodu.

Mechanizm refleksji pozwala także na zdobywanie informacji o klasach w trakcie wykonania programu. W poniższym przykładzie Klasa Main sprawdza jaki jest typ zwracany przez metody klasy Bar.

public class Bar {
    public String fun(Integer i) {
        return "0" + i + ", zglos sie!";
    }
}

import static java.lang.System.out;
import java.lang.reflect.*;

public class Main {
    public static void main(String[] args) throws Exception {
        String className = "Bar";
        Class c = Class.forName(className);
        Method[] methodArr = c.getDeclaredMethods();
        for (Method m : methodArr) {
	    out.print("Klasa " + className + " ma metode '" + m.getName() + "'");
	    out.println(" ktora zwraca wartosc typu " + m.getReturnType());
        }
    }
}

Ruby

Przykład w języku Ruby, który dodaje metodę klasową once, pozwalającą zaznaczyć, że dana funkcja składowa klasy ma być wykonywana tylko raz. Podprogram modyfikuje kod oznaczonych metod w taki sposób, że nadaje im nową nazwę. Pod starą nazwą umieszcza nową metodę, która buforuje wartość zwracaną przez pierwotnie zdefiniowaną funkcję, tym samym pozwalając się jej wykonać tylko raz.

# part of date.rb - date and time library
# Author: Tadayoshi Funaba 1998-2008

class Date
 class << self

    def once(*ids) # :nodoc: -- restricted
      for id in ids
        module_eval <<-"end;"
          alias_method :__#{id.object_id}__, :#{id.to_s}
          private :__#{id.object_id}__
          def #{id.to_s}(*args)
            @__ca__[#{id.object_id}] ||= __#{id.object_id}__(*args)
          end
        end;
      end
    end

    private :once

  end
end

Inny przykład to rozszerzenie możliwości języka o konstrukcję automatycznie kasującą zawartość wskazanych przez programistę buforów, jeśli uruchomione zostaną wyszczególnione metody. Zadaniem metaprogramu jest tu również opakowanie metod, jednak zapamiętywane są identyfikatory ich obiektów a nie identyfikatory obiektów ich symbolicznych nazw. Metaprogram zawarto w przykładowym module BufferAffects, który można pobrać z serwisu GitHub. Domieszkując ten moduł możemy korzystać z dodatkowych metod klasowych pozwalających na stosowanie w kodzie klauzul buffers_reset_method i attr_affects_buffers:

require 'bufferaffects'  # http://gist.github.com/88178

class Main

  # domieszkowanie modułu
  extend BufferAffects

  # metoda opróżniająca wykorzystywany bufor
  buffers_reset_method :reset_path_buffer

  # pola które po zmianie powinny wpływać na
  # zawartość bufora
  attr_affects_buffers :subpart

  # standardowe akcesory pól
  attr_accessor        :subpart, :otherpart

  # metoda opróżniająca bufor
  def reset_path_buffer(name)
    @path = nil
    p "uruchomiono reset dla #{name}"
  end

  # metoda z buforowanym wyjściem
  def path
    @path ||= @subpart.to_s + @otherpart.to_s
  end

end

# tworzenie nowego obiektu
obj = Main.new

# ustawianie jednego z pól
# i wyświetlanie buforowanych wynikow
obj.subpart = 'test'
p obj.path
obj.subpart = '1234'
p obj.path

Linki zewnętrzne