2009-09-01 11 views
14

Czy możesz przechwycić wywołanie metody w Perlu, zrób coś z argumentami, a następnie wykonaj je?Czy wywołania metody Perl mogą zostać przechwycone?

+1

Widzę, że użyłeś tagu "aop". Dlaczego nie zapytać o konkretne techniki AOP, z których chciałbyś skorzystać? (Odpowiedzi na twoje pytanie mogą pomóc w implementacji systemu AOP, ale dlaczego tak się dzieje, gdy ktoś już to zrobił?) – jrockway

+0

Myślałem, że można to zrobić za pomocą atrybutów metod, ale teraz nie mogę znaleźć odpowiedniej strony perldoc (atrybuty perldoc nie jest stroną, o której myślałem). Pamiętam dobry przykład dotyczący zawijania różnych metod dodawania rejestrowania ... – Ether

+0

Zobacz powiązane pytanie SO, aby uzyskać dodatkowe informacje: http://stackoverflow.com/questions/635212/how-i-redefine-perl-class-methods – draegtun

Odpowiedz

14

Tak, można przechwytywać wywołania podprogramów Perl. Mam cały rozdział na ten temat w Mastering Perl. Sprawdź moduł Hook::LexWrap, który pozwala to zrobić bez przechodzenia przez wszystkie szczegóły. Metody Perla to tylko podprogramy.

Można także utworzyć podklasę i zastąpić metodę, którą chcesz przechwycić. Jest to nieco lepszy sposób, ponieważ jest to sposób, w jaki programowanie obiektowe chce, abyś to robił. Czasami jednak ludzie piszą kod, który nie pozwala ci zrobić tego poprawnie. Więcej na ten temat również w Mastering Perl.

6

To wygląda na pracę dla Moose! Łoś to system obiektowy dla Perla, który może to zrobić i wiele więcej. docs wykona o wiele lepszą pracę przy wyjaśnianiu niż ja, ale prawdopodobnie będziesz potrzebował Method Modifier, konkretnie before.

+1

Łoś jest czymś, co używasz do całej aplikacji, a nie ukierunkowanymi problemami. –

+3

Ale to zależy od problemu, a osoba pytająca nie poda żadnych szczegółów. Może to być ktoś, kto oceni podejście, które przyjmie podczas tworzenia nowego wniosku, w którym to przypadku włączanie do łosia byłoby odpowiednią i korzystną reakcją. – jsoverson

+1

Po prostu powiedzieliśmy to samo. Nie powiedziałem, że nie używam Łosia, ale nie powiedziałem, żeby go użyć. –

7

Aby krótko opisać, Perl ma możliwość modyfikowania tabeli symboli. Wywołujesz podprogram (metodę) poprzez tablicę symboli pakietu, do którego należy ta metoda. Jeśli zmodyfikujesz tabelę symboli (i nie jest to uważane za bardzo brudne), możesz zastąpić większość wywołań metod, wywołując inne określone metody. Świadczy to podejście:

# The subroutine we'll interrupt calls to 
sub call_me 
{ 
    print shift,"\n"; 
} 

# Intercepting factory 
sub aspectate 
{ 
    my $callee = shift; 
    my $value = shift; 
    return sub { $callee->($value + shift); }; 
} 
my $aspectated_call_me = aspectate \&call_me, 100; 

# Rewrite symbol table of main package (lasts to the end of the block). 
# Replace "main" with the name of the package (class) you're intercepting 
local *main::call_me = $aspectated_call_me; 

# Voila! Prints 105! 
call_me(5); 

ten pokazuje również, że gdy ktoś bierze odniesienie podprogramu i wzywa go poprzez odniesienie, nie można już wpływać na takie połączenia.

Jestem prawie pewien, że istnieją ramy do zrobienia aspektu w perlu, ale mam nadzieję, że to podejście demonstruje.

3

Tak.

potrzebne są trzy rzeczy:

argumentów do rozmowy są w @_ który jest po prostu kolejną zmienną dynamicznie scoped.

Następnie goto obsługuje argument podrzędny odniesienia, który zachowuje bieżący @_, ale wykonuje inne wywołanie funkcji (ogon).

Na koniec można użyć local do utworzenia globalnych zmiennych o zasięgu leksykalnym, a tabele symboli są pochowane w %::.

Więc masz:

co oczywiście
sub foo { 
    my($x,$y)=(@_); 
    print "$x/$y = " . ((0.0+$x)/$y)."\n"; 
} 
sub doit { 
    foo(3,4); 
} 
doit(); 

drukuje:

3/4 = 0.75 

Możemy zastąpić foo przy użyciu local i idź:

my $oldfoo = \&foo; 
local *foo = sub { (@_)=($_[1], $_[0]); goto $oldfoo; }; 
doit(); 

A teraz mamy :

4/3 = 1.33333333333333 

Jeśli chcesz zmodyfikować *foo bez używania jego nazwy, a ty nie chcesz używać eval, to można go modyfikować poprzez manipulowanie %::, na przykład:

$::{"foo"} = sub { (@_)=($_[0], 1); goto $oldfoo; }; 
doit(); 

A teraz mamy:

3/1 = 3 
5

Możesz, a Pavel opisuje dobry sposób na zrobienie tego, ale powinieneś prawdopodobnie wyjaśnić, dlaczego chcesz to zrobić w pierwszej kolejności.

Jeśli szukasz zaawansowanych sposobów przechwytywania wywołań do dowolnych podprogramów, to będzie Ci pomocny manipulator z tabelami symboli, ale jeśli chcesz dodać funkcjonalność do funkcji, być może wyeksportowanych do przestrzeni nazw, w której aktualnie pracujesz, wtedy być może trzeba będzie znać sposoby wywoływania funkcji istniejących w innych obszarach nazw.

Dane :: Dumper, na przykład, normalnie eksportuje funkcję "Dumper" do wywołującej przestrzeni nazw, ale można ją przesłonić lub wyłączyć i dostarczyć własną funkcję Dumper, która następnie wywołuje oryginał za pomocą w pełni kwalifikowanej nazwy.

np.

use Data::Dumper; 

sub Dumper { 
    warn 'Dumping variables'; 
    print Data::Dumper::Dumper(@_); 
} 

my $foo = { 
    bar => 'barval', 
}; 

Dumper($foo); 

To alternatywne rozwiązanie, które może być bardziej odpowiednie w zależności od pierwotnego problemu. Dużo radości można uzyskać podczas gry przy użyciu tablicy symboli, ale może to być przesada i może prowadzić do trudnego do utrzymania kodu, jeśli go nie potrzebujesz.

+1

W tym przypadku powinieneś podać pustą listę importu do Dumper, więc nie importujesz niczego. Również w tym przypadku kolejność ma znaczenie. Najpierw należy zaimportować, a następnie przesłonić. Wygrywa ostatnia definicja podprogramu. Na koniec, w ramach ostrzeżeń, otrzymasz komunikat "przedefiniuj". –

+0

Zdecydowanie, po prostu staram się zachować prostotę ze względu na przykład. Bycie bezpośrednim ma większą wartość (i jest szybsze dla respondenta) niż wyjaśnienie wszystkich szczegółów stycznych. Koncepcje importu i ostrzeżeń można najlepiej wyjaśnić jako pozostałe pytania. – jsoverson

Powiązane problemy