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:
class Foo
def bar
'Hello'
end
end
class ExtendedFoo < Foo
def bar
super + ' World'
end
end
ExtendedFoo.new.bar # => 'Hello World'
Działa to, jeśli kontrolujesz tworzenie Foo
obiektów. Po prostu zmień każde miejsce, które tworzy Foo
zamiast ExtendedFoo
. 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 :
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, 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
Dwie 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 samo Module#include
, z tym wyjątkiem, że miesza się w mixin 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
tym pytaniu: moduł Ruby prepend vs wyprowadzenie
Dziedziczenie 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 zamiast prepend
go:
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ż wykorzystuje dziedziczenie, co oznacza, że możesz używać super
. Jednak Module#include
wstawia mixin powyżej klasy w hierarchii dziedziczenia, co oznacza, że FooExtensions#bar
nigdy nie można nazwać (a jeśli były nazywa, super
nie odnoszą się do rzeczywistości Foo#bar
, lecz do Object#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:
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 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żywamy define_method
zamiast def
tutaj), to (i tylko on) nadal będzie miał dostęp old_bar
, nawet po tym, jak wyszedł poza zakres.
Krótkie wyjaśnienie:
old_bar = instance_method(:bar)
W tym miejscu zawijamy bar
metodę do UnboundMethod
obiektu metody i przypisujemy do zmiennej lokalnej old_bar
. Oznacza to, że mamy teraz sposób, aby się zatrzymać bar
nawet po jego zastąpieniu.
old_bar.bind(self)
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 to self
jest. Ale wzięliśmy metodę bezpośrednio z klasy, skąd ona wie, co to self
jest?
Cóż, to nie, dlatego musimy bind
Our UnboundMethod
do pierwszego obiektu, który zwróci Method
obiekt, który możemy wywołać. ( UnboundMethod
nie można się nazywać, ponieważ nie wiedzą, co robić, nie znając ich self
).
A do czego to robimy bind
? Po prostu bind
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 zwracany bind
. 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
Problem 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ą!
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 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
.
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:
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!
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 przez Module#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
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
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ści bar:after
. Może moglibyśmy (ab) użyć super
słowa kluczowego?
class Foo
def bar
'Hello'
end
end
class Foo
def bar:after
super + ' World'
end
end
Zastąpienie
Kombinator przed równoważny jest równoważeniem prepend
miksu z metodą nadpisującą, która wywołuje super
na samym końcu metody. Podobnie, po kombiatororze jest równoważne z prepend
wprowadzeniem mixinu za pomocą metody nadpisującej, która wywołuje super
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 to prepend
silniejszym 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 wywołać zastąpioną metodę w taki sam sposób super
, jak wywołać zastąpioną metodę:
class Foo
def bar
'Hello'
end
end
class Foo
def bar
old + ' World'
end
end
Foo.new.bar # => 'Hello World'
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 w prepend
ed mixin jest zasadniczo taki sam jak old
w tej propozycji.
redef
słowo kluczowe
Podobnie 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:
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, moglibyśmy 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'
Zastąpienie
redef
wprowadzenie metody jest równoważne z przesłonięciem metody w prepend
edytowanym mixinie. super
w metodzie zastępowania zachowuje się jak super
lub old
w tej propozycji.