Czy podczas łatania metody można wywołać metodę overridden z nowej implementacji?
Powiedzmy, że jestem małpą łatającą metodę w klasie, jak Mogę wywołać metodę overridden z metody overriding? Czyli coś jak super
Np.
class Foo
def bar()
"Hello"
end
end
class Foo
def bar()
super() + " World"
end
end
>> Foo.new.bar == "Hello World"
3 answers
EDIT: minęło 5 lat, odkąd napisałem tę odpowiedź, i zasługuje na jakąś operację plastyczną, aby utrzymać ją na bieżąco.
Możesz zobaczyć ostatnią wersję przed edycją tutaj .
Nie można wywołać metody nadpisanej przez nazwę lub słowo kluczowe. Jest to jeden z wielu powodów, dla których należy unikać łatania małp i preferować dziedziczenie, ponieważ oczywiście możesz nazwać overridden ]} metoda.
Unikanie Łatania Małp
Dziedziczenie
Więc, jeśli w ogóle to możliwe, powinieneś preferować coś takiego:
class Foo
def bar
'Hello'
end
end
class ExtendedFoo < Foo
def bar
super + ' World'
end
end
ExtendedFoo.new.bar # => 'Hello World'
To działa, jeśli kontrolujesz tworzenie Foo
obiektów. Po prostu zmień każde miejsce, które tworzy Foo
, aby zamiast tego utworzyć ExtendedFoo
. To działa jeszcze lepiej, jeśli użyjesz wzorzec projektowy wtrysku zależności , wzorzec projektowy metody Fabrycznej , abstrakcyjny wzorzec projektowy fabryki albo coś w tym stylu, bo w takim przypadku jest tylko miejsce, w którym trzeba się zmienić.
Delegacja
Jeśli nie kontrolujesz tworzenia obiektów, na przykład dlatego, że są one tworzone przez framework, który jest poza Twoją kontrolą (jak na przykład ruby-on-rails), możesz użyć Wrapper Design Pattern :
require 'delegate'
class Foo
def bar
'Hello'
end
end
class WrappedFoo < DelegateClass(Foo)
def initialize(wrapped_foo)
super
end
def bar
super + ' World'
end
end
foo = Foo.new # this is not actually in your code, it comes from somewhere else
wrapped_foo = WrappedFoo.new(foo) # this is under your control
wrapped_foo.bar # => 'Hello World'
Zasadniczo, na granicy systemu, gdzie Foo
obiekt wchodzi do twojego kod, owijasz go w inny obiekt, a następnie używasz tego obiektu zamiast oryginalnego wszędzie w kodzie.
To wykorzystuje Object#DelegateClass
metoda pomocnicza z delegate
Biblioteka w stdlib.
"Clean" Monkey Patching
Module#prepend
: Mixin Prepending
Dwie powyższe metody wymagają zmiany systemu, aby uniknąć łatania małp. W tej sekcji przedstawiono preferowaną I najmniej inwazyjną metodę łatanie małp, czy zmiana systemu nie wchodzi w grę.
Module#prepend
został dodany do obsługi mniej lub bardziej dokładnie tego przypadku użycia. Module#prepend
robi to samo co Module#include
, z tym, że miesza się w mixinie bezpośrednio poniżej klasy:
class Foo
def bar
'Hello'
end
end
module FooExtensions
def bar
super + ' World'
end
end
class Foo
prepend FooExtensions
end
Foo.new.bar # => 'Hello World'
Uwaga: napisałem też trochę o Module#prepend
w tym pytaniu: Ruby module prepend vs derivation
Mixin dziedziczenie (złamane)
Widziałem, jak niektórzy próbują (i pytają o to, dlaczego nie działa tutaj na StackOverflow) coś takiego, tzn. include
ing a mixin zamiast prepend
ING it:
class Foo
def bar
'Hello'
end
end
module FooExtensions
def bar
super + ' World'
end
end
class Foo
include FooExtensions
end
Niestety, to nie zadziała. To dobry pomysł, ponieważ używa dziedziczenia, co oznacza, że możesz użyć super
. Jednakże, Module#include
wstawia mixin powyżej klasy w hierarchii dziedziczenia, co oznacza, że FooExtensions#bar
nigdy nie zostanie wywołana (i jeśli zostaną wywołane, super
w rzeczywistości nie odnosi się do Foo#bar
, ale raczej do Object#bar
który nie istnieje), ponieważ Foo#bar
zawsze znajdzie się pierwszy.
Owijanie Metodą
Wielkie pytanie brzmi: jak możemy trzymać się metody bar
, Nie trzymając się faktycznej metody {108]}? Odpowiedź leży, Jak to często bywa, w programowaniu funkcyjnym. Uzyskujemy uchwyt metody jako rzeczywisty obiekt i używamy zamknięcia (tj. bloku), aby upewnić się, że my i tylko my trzymamy się tego obiekt:
class Foo
def bar
'Hello'
end
end
class Foo
old_bar = instance_method(:bar)
define_method(:bar) do
old_bar.bind(self).() + ' World'
end
end
Foo.new.bar # => 'Hello World'
Jest to bardzo czyste: ponieważ old_bar
jest tylko zmienną lokalną, będzie ona poza zasięgiem na końcu ciała klasowego, a dostęp do niej z dowolnego miejsca jest niemożliwy, nawet używając reflection! I od Module#define_method
blokuje, a bloki zamykają się wokół otaczającego ich środowiska leksykalnego (co jest dlaczego używamy define_method
zamiast def
tutaj), to (i tylko it) nadal będzie miał dostęp do old_bar
, nawet po to jest poza zasięgiem.
Krótkie wyjaśnienie:
old_bar = instance_method(:bar)
Tutaj owijamy metodę bar
w UnboundMethod
obiekt metody i przypisanie go do zmiennej lokalnej old_bar
. Oznacza to, że mamy teraz sposób, aby utrzymać bar
nawet po nadpisaniu.
old_bar.bind(self)
To trochę skomplikowane. Zasadniczo, w Rubim (i prawie wszystkich językach OO opartych na pojedynczej wysyłce), metoda jest powiązana z określonym obiektem odbiorczym, zwanym self
W Rubim. W innymi słowy: metoda zawsze wie, na jakim obiekcie została wywołana, wie, czym jest jej self
. Ale, wzięliśmy metodę bezpośrednio z klasy, skąd ona wie, co jej self
jest?
Cóż, nie ma, dlatego musimy bind
nasz UnboundMethod
do obiektu, który zwróci Method
obiekt, który możemy następnie wywołać. (UnboundMethod
s nie można nazwać, ponieważ nie wiedzą, co zrobić, nie znając ich self
.)
I do czego to robimy? My po prostu bind
to dla nas samych, w ten sposób będzie się zachowywać dokładnie jak oryginał bar
!
Na koniec musimy wywołać Method
, który jest zwracany z bind
. W Rubim 1.9 jest do tego jakaś nowa składnia (.()
), ale jeśli korzystasz z 1.8, możesz po prostu użyć call
metoda; na to .()
i tak się tłumaczy.
Oto kilka innych pytań, gdzie niektóre z tych pojęć są explained:
"Dirty" Monkey Patching
alias_method
łańcuch
Problem, jaki mamy z łataniem małp polega na tym, że gdy nadpisujemy metodę, metoda zniknie, więc nie możemy jej już nazywać. Zróbmy kopię zapasową!
class Foo
def bar
'Hello'
end
end
class Foo
alias_method :old_bar, :bar
def bar
old_bar + ' World'
end
end
Foo.new.bar # => 'Hello World'
Foo.new.old_bar # => 'Hello'
Problem polega na tym, że my zanieczyściły teraz Przestrzeń nazw zbędną metodą old_bar
. Metoda ta pojawi się w naszej dokumentacji, pojawi się w uzupełnianiu kodu w naszych Idach, pojawi się podczas refleksji. Ponadto, nadal można go nazwać, ale prawdopodobnie małpa go załatała, ponieważ nie podobało nam się jego zachowanie w pierwszej kolejności, więc możemy nie chcieć, aby inni ludzie go nazywali.
Pomimo faktu, że ma to pewne niepożądane właściwości, niestety stało się spopularyzowane poprzez AciveSupport ' S Module#alias_method_chain
.
Odp: dopracowania
Jeśli potrzebujesz innego zachowania tylko w kilku konkretnych miejscach, a nie w całym systemie, możesz użyć udoskonaleń, aby ograniczyć łatkę małpy do określonego zakresu. Zademonstruję to tutaj używając przykładu Module#prepend
z góry:
class Foo
def bar
'Hello'
end
end
module ExtendedFoo
module FooExtensions
def bar
super + ' World'
end
end
refine Foo do
prepend FooExtensions
end
end
Foo.new.bar # => 'Hello'
# We haven’t activated our Refinement yet!
using ExtendedFoo
# Activate our Refinement
Foo.new.bar # => 'Hello World'
# There it is!
Możesz zobaczyć bardziej wyrafinowany przykład użycia udoskonaleń w tym pytaniu: jak włączyć monkey patch dla konkretna metoda?
Porzucone pomysły
Zanim społeczność Ruby się osiedliła Module#prepend
, było wiele różnych pomysłów, które możesz czasami zobaczyć w starszych dyskusjach. Wszystkie z nich są subsumowane przez Module#prepend
.
Kombinatory Metod
Jednym z pomysłów był pomysł kombinatorów metod z CLOS. Jest to w zasadzie bardzo lekka wersja podzbioru programowania zorientowanego na aspekt.Używanie składni jak
class Foo
def bar:before
# will always run before bar, when bar is called
end
def bar:after
# will always run after bar, when bar is called
# may or may not be able to access and/or change bar’s return value
end
end
Byłabyś w stanie" podłączyć się " do wykonania metody bar
.
Nie jest jednak do końca jasne, czy i w jaki sposób można uzyskać dostęp do zwracanej wartości bar
Wewnątrz bar:after
. Może moglibyśmy (ab) użyć słowa kluczowego super
?
class Foo
def bar
'Hello'
end
end
class Foo
def bar:after
super + ' World'
end
end
Zamiennik
Kombinator przed jest równoważny prepend
ing mixin z nadrzędną metodą, która wywołuje super
na samym końcu metody. Podobnie kombinator po jest odpowiednikiem prepend
ing a mixin with a overriding method that calls super
at the very beginning of the method.
Możesz również wykonywać czynności przed i po wywołaniu super
, możesz wywołać super
wiele razy, a zarówno pobierać, jak i manipulować zwracaną wartością super
, czyniąc prepend
potężniejszym niż kombinatory metod.
class Foo
def bar:before
# will always run before bar, when bar is called
end
end
# is the same as
module BarBefore
def bar
# will always run before bar, when bar is called
super
end
end
class Foo
prepend BarBefore
end
I
class Foo
def bar:after
# will always run after bar, when bar is called
# may or may not be able to access and/or change bar’s return value
end
end
# is the same as
class BarAfter
def bar
original_return_value = super
# will always run after bar, when bar is called
# has access to and can change bar’s return value
end
end
class Foo
prepend BarAfter
end
old
słowo kluczowe
Ten pomysł dodaje nowe słowo kluczowe podobne do super
, które pozwala na wywołanie nadpisane metoda w ten sam sposób super
pozwala wywołać metodęnadpisane:
class Foo
def bar
'Hello'
end
end
class Foo
def bar
old + ' World'
end
end
Foo.new.bar # => 'Hello World'
Głównym problemem jest to, że jest ona niezgodna wstecz: jeśli masz metodę o nazwie old
, nie będziesz już w stanie jej wywołać!
Zamiennik
super
w metodzie nadrzędnej w prepend
ed mixin jest zasadniczo taki sam jak old
w niniejszym wniosku.
redef
słowo kluczowe
Podobne do powyższego, ale zamiast dodawać nowe słowo kluczowe dla wywołanie nadpisanej metody i pozostawienie samej def
, dodajemy nowe słowo kluczowe dla przedefiniowanie metod . Jest to kompatybilne wstecz, ponieważ składnia obecnie jest nielegalna:
class Foo
def bar
'Hello'
end
end
class Foo
redef bar
old + ' World'
end
end
Foo.new.bar # => 'Hello World'
Zamiast dodawać dwa nowe słowa kluczowe, możemy również przedefiniować znaczenie super
wewnątrz redef
:
class Foo
def bar
'Hello'
end
end
class Foo
redef bar
super + ' World'
end
end
Foo.new.bar # => 'Hello World'
Zamiennik
redef
ining metody jest równoznaczne z nadpisaniem metody w prepend
ed mixin. super
w metoda nadrzędna zachowuje się w niniejszym wniosku jak super
lub old
.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-11 07:25:02
Spójrz na metody aliasingu, jest to rodzaj zmiany nazwy metody na nową nazwę.
Aby uzyskać więcej informacji i punkt wyjścia, zajrzyj do tego artykułu zastępującego metody (zwłaszcza pierwszej części). Ruby API docs , również dostarcza (mniej rozbudowany) przykład.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-12-17 11:52:50
Klasa, która spowoduje nadpisanie musi być przeładowana po klasie, która zawiera oryginalną metodę, więc require
jest w pliku, który spowoduje nadpisanie.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-01 00:03:50