Powiedzmy, że jestem małpą łatającą metodę w klasie, jak mogę wywołać metodę przesłoniętą z metody przesłonięcia? Tj. Coś trochę jaksuper
Na przykład
class Foo
def bar()
"Hello"
end
end
class Foo
def bar()
super() + " World"
end
end
>> Foo.new.bar == "Hello World"
ruby
monkeypatching
James Hollingworth
źródło
źródło
Foo
i użytkowaniaFoo::bar
. Więc musisz małpować łatanie metody.Odpowiedzi:
EDYCJA : Minęło 9 lat, odkąd pierwotnie napisałem tę odpowiedź i zasługuję na chirurgię plastyczną, aby była aktualna.
Można zobaczyć ostatnią wersję przed edit tutaj .
Nie można wywołać zastąpionej metody według nazwy lub słowa kluczowego. To jeden z wielu powodów, dla których należy unikać łatania małp i zamiast tego preferować dziedziczenie, ponieważ oczywiście można wywołać metodę przesłoniętą .
Unikanie łatania małp
Dziedzictwo
Więc jeśli to w ogóle możliwe, powinieneś preferować coś takiego:
Działa to, jeśli kontrolujesz tworzenie
Foo
obiektów. Po prostu zmień każde miejsce, które tworzyFoo
zamiastExtendedFoo
. Działa to jeszcze lepiej, jeśli użyjesz wzoru projektu wtrysku zależności , tym wzór Metoda Factory Design The wzór abstrakcyjny Factory Design lub coś wzdłuż tych linii, ponieważ w tym przypadku nie jest miejsce tylko trzeba zmienić.Delegacja
Jeśli ty nie kontrolujesz tworzenia
Foo
obiektów, na przykład ponieważ są one tworzone przez strukturę, która jest poza twoją kontrolą (nprubin na szynachna przykład), możesz użyć Wzorca Projektu Opakowania :Zasadniczo na granicy systemu, w której
Foo
obiekt wchodzi do twojego kodu, zawijasz go w inny obiekt, a następnie używasz tego obiektu zamiast oryginalnego w dowolnym miejscu w kodzie.Używa to
Object#DelegateClass
metody pomocniczej zdelegate
biblioteki w stdlib.„Czysta” łatka małp
Module#prepend
: Mixin PrependingDwie powyższe metody wymagają zmiany systemu, aby uniknąć łatania małp. W tej sekcji pokazano preferowaną i najmniej inwazyjną metodę łatania małp. Zmiana systemu nie powinna być opcją.
Module#prepend
został dodany w celu obsługi mniej więcej dokładnie tego przypadku użycia.Module#prepend
robi to samoModule#include
, z tym wyjątkiem, że miesza się w mixin bezpośrednio poniżej klasy:Uwaga: napisałem też trochę o
Module#prepend
tym pytaniu: moduł Ruby prepend vs wyprowadzenieDziedziczenie Mixin (uszkodzony)
Widziałem, jak niektórzy ludzie próbują (i pytają, dlaczego to nie działa tutaj na StackOverflow) coś takiego, np.
include
Wkładając mixin zamiastprepend
go:Niestety to nie zadziała. To dobry pomysł, ponieważ wykorzystuje dziedziczenie, co oznacza, że możesz używać
super
. JednakModule#include
wstawia mixin powyżej klasy w hierarchii dziedziczenia, co oznacza, żeFooExtensions#bar
nigdy nie można nazwać (a jeśli były nazywa,super
nie odnoszą się do rzeczywistościFoo#bar
, lecz doObject#bar
których nie istnieje), ponieważFoo#bar
zawsze znajdują się w pierwszej kolejności.Metoda owijania
Najważniejsze pytanie brzmi: w jaki sposób możemy trzymać się tej
bar
metody bez faktycznego trzymania się tej metody ? Odpowiedź leży, jak to często bywa, w programowaniu funkcjonalnym. Utrzymujemy metodę jako rzeczywisty obiekt i używamy zamknięcia (tj. Bloku), aby upewnić się, że my i tylko my trzymamy się tego obiektu:Jest to bardzo czyste: ponieważ
old_bar
jest to tylko zmienna lokalna, na końcu ciała klasy wyjdzie poza zakres i nie można uzyskać do niej dostępu z dowolnego miejsca, nawet przy użyciu odbicia! A ponieważModule#define_method
bierze blok, a bloki zamykają otaczające ich środowisko leksykalne ( dlatego używamydefine_method
zamiastdef
tutaj), to (i tylko on) nadal będzie miał dostępold_bar
, nawet po tym, jak wyszedł poza zakres.Krótkie wyjaśnienie:
W tym miejscu zawijamy
bar
metodę doUnboundMethod
obiektu metody i przypisujemy do zmiennej lokalnejold_bar
. Oznacza to, że mamy teraz sposób, aby się zatrzymaćbar
nawet po jego zastąpieniu.To trochę trudne. Zasadniczo w języku Ruby (i prawie we wszystkich językach OO opartych na pojedynczej wysyłce) metoda jest powiązana z konkretnym obiektem odbiorczym, zwanym
self
w języku Ruby. Innymi słowy: metoda zawsze wie, do którego obiektu została wywołana, wie, co toself
jest. Ale wzięliśmy metodę bezpośrednio z klasy, skąd ona wie, co toself
jest?Cóż, to nie, dlatego musimy
bind
OurUnboundMethod
do pierwszego obiektu, który zwróciMethod
obiekt, który możemy wywołać. (UnboundMethod
nie można się nazywać, ponieważ nie wiedzą, co robić, nie znając ichself
).A do czego to robimy
bind
? Po prostubind
to dla siebie, w ten sposób zachowa się dokładnie tak , jak oryginałbar
!Na koniec musimy zadzwonić pod ten,
Method
który jest zwracanybind
. W Ruby 1.9 dostępna jest nowa, świetna składnia (.()
), ale jeśli korzystasz z wersji 1.8, możesz po prostu użyćcall
metody; i tak.()
zostaje przetłumaczone.Oto kilka innych pytań, w których wyjaśniono niektóre z tych pojęć:
Łata „Brudna” Małpa
alias_method
łańcuchProblem z łataniem małp polega na tym, że gdy nadpisujemy metodę, metoda znika, więc nie możemy jej już nazwać. Zróbmy więc kopię zapasową!
Problem polega na tym, że przestaliśmy zanieczyszczać przestrzeń nazw zbędnym
old_bar
metodą. Ta metoda pojawi się w naszej dokumentacji, pokaże się po zakończeniu kodu w naszych IDE, pojawi się podczas refleksji. Ponadto nadal można go nazwać, ale prawdopodobnie załataliśmy go małpą, ponieważ nie podobało nam się jego zachowanie, więc możemy nie chcieć, aby inni go nazywali.Pomimo tego, że ma to pewne niepożądane właściwości, niestety zostało spopularyzowane przez AciveSupport
Module#alias_method_chain
.Na bok: udoskonalenia
Jeśli potrzebujesz innego zachowania tylko w kilku określonych miejscach, a nie w całym systemie, możesz użyć Udoskonaleń, aby ograniczyć łatkę małpy do określonego zakresu. Pokażę to tutaj na
Module#prepend
powyższym przykładzie:Bardziej wyrafinowany przykład użycia udoskonaleń można znaleźć w tym pytaniu: Jak włączyć łatkę małpy dla określonej metody?
Porzucone pomysły
Zanim społeczność Ruby się osiedliła
Module#prepend
, krążyło wokół niej wiele różnych pomysłów, o których od czasu do czasu można się odwoływać w starszych dyskusjach. Wszystkie są uwzględnione przezModule#prepend
.Kombinatory metod
Jednym z pomysłów był pomysł metody łączenia z CLOS. Jest to w zasadzie bardzo lekka wersja podzbioru programowania zorientowanego na aspekty.
Używając podobnej składni
będziesz w stanie „zaczepić” się o wykonanie
bar
metody.Nie jest jednak do końca jasne, czy i jak uzyskać dostęp do
bar
zwracanej wartościbar:after
. Może moglibyśmy (ab) użyćsuper
słowa kluczowego?Zastąpienie
Kombinator przed równoważny jest równoważeniem
prepend
miksu z metodą nadpisującą, która wywołujesuper
na samym końcu metody. Podobnie, po kombiatororze jest równoważne zprepend
wprowadzeniem mixinu za pomocą metody nadpisującej, która wywołujesuper
na samym początku metody.Możesz także robić rzeczy przed i po wywołaniu
super
, możesz wywoływaćsuper
wiele razy, a także zarówno pobierać, jak i manipulowaćsuper
wartością zwracaną, czyniąc toprepend
silniejszym niż kombinatory metod.i
old
słowo kluczoweTen pomysł dodaje nowe słowo kluczowe podobne do
super
, które pozwala wywołać zastąpioną metodę w taki sam sposóbsuper
, jak wywołać zastąpioną metodę:Główny problem polega na tym, że jest on niezgodny wstecz: jeśli masz wywoływaną metodę
old
, nie będziesz już w stanie jej wywołać!Zastąpienie
super
w nadrzędnej metodzie wprepend
ed mixin jest zasadniczo taki sam jakold
w tej propozycji.redef
słowo kluczowePodobnie jak powyżej, ale zamiast dodawać nowe słowo kluczowe w celu wywołania zastąpionej metody i pozostawienia go w
def
spokoju, dodajemy nowe słowo kluczowe w celu przedefiniowania metod. Jest to kompatybilne wstecz, ponieważ i tak obecnie składnia jest nielegalna:Zamiast dodawać dwa nowe słowa kluczowe, moglibyśmy również przedefiniować znaczenie
super
wewnątrzredef
:Zastąpienie
redef
wprowadzenie metody jest równoważne z przesłonięciem metody wprepend
edytowanym mixinie.super
w metodzie zastępowania zachowuje się jaksuper
lubold
w tej propozycji.źródło
bind
tę samąold_method
zmienną?UnboundMethod#bind
zwróci nowy, innyMethod
, więc nie widzę żadnego konfliktu, niezależnie od tego, czy wywołujesz go dwa razy z rzędu, czy dwa razy w tym samym czasie z różnych wątków.old
iredef
? Mój 2.0.0 ich nie ma. Ach, trudno nie przeoczyć Inne konkurencyjne pomysły, które nie znalazły się w Ruby to:Spójrz na metody aliasingu, jest to rodzaj zmiany nazwy metody na nową nazwę.
Aby uzyskać więcej informacji i punkt wyjścia, zapoznaj się z tym artykułem na temat metod zastępowania (zwłaszcza w pierwszej części). Dokumenty interfejsu API Ruby również stanowią (mniej skomplikowany) przykład.
źródło
Klasa, która spowoduje zastąpienie, musi zostać ponownie załadowana po klasie zawierającej oryginalną metodę, więc
require
w pliku, który spowoduje zastąpienie.źródło