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 Fooobiektów. Po prostu zmień każde miejsce, które tworzy Foozamiast 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 Fooobiektó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 Fooobiekt 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#DelegateClassmetody 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#prependzostał dodany w celu obsługi mniej więcej dokładnie tego przypadku użycia. Module#prependrobi 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#prependtym 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. includeWkładając mixin zamiast prependgo:
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#includewstawia mixin powyżej klasy w hierarchii dziedziczenia, co oznacza, że FooExtensions#barnigdy nie można nazwać (a jeśli były nazywa, supernie odnoszą się do rzeczywistości Foo#bar, lecz do Object#barktórych nie istnieje), ponieważ Foo#barzawsze znajdują się w pierwszej kolejności.
Metoda owijania
Najważniejsze pytanie brzmi: w jaki sposób możemy trzymać się tej barmetody 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_barjest 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_methodbierze blok, a bloki zamykają otaczające ich środowisko leksykalne ( dlatego używamy define_methodzamiast deftutaj), 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 barmetodę do UnboundMethodobiektu metody i przypisujemy do zmiennej lokalnej old_bar. Oznacza to, że mamy teraz sposób, aby się zatrzymać barnawet 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 selfw języku Ruby. Innymi słowy: metoda zawsze wie, do którego obiektu została wywołana, wie, co to selfjest. Ale wzięliśmy metodę bezpośrednio z klasy, skąd ona wie, co to selfjest?
Cóż, to nie, dlatego musimy bindOur UnboundMethoddo pierwszego obiektu, który zwróci Methodobiekt, który możemy wywołać. ( UnboundMethodnie można się nazywać, ponieważ nie wiedzą, co robić, nie znając ich self).
A do czego to robimy bind? Po prostu bindto dla siebie, w ten sposób zachowa się dokładnie tak , jak oryginał bar!
Na koniec musimy zadzwonić pod ten, Methodktó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#prependpowyż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 barmetody.
Nie jest jednak do końca jasne, czy i jak uzyskać dostęp do barzwracanej wartości bar:after. Może moglibyśmy (ab) użyć supersł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 prependmiksu z metodą nadpisującą, która wywołuje superna samym końcu metody. Podobnie, po kombiatororze jest równoważne z prependwprowadzeniem mixinu za pomocą metody nadpisującej, która wywołuje superna samym początku metody.
Możesz także robić rzeczy przed i po wywołaniu super, możesz wywoływać superwiele razy, a także zarówno pobierać, jak i manipulować superwartością zwracaną, czyniąc to prependsilniejszym 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
superw nadrzędnej metodzie w prepended mixin jest zasadniczo taki sam jak oldw 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 defspokoju, 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 superwewnątrz redef:
class Foo
def bar
'Hello'
end
end
class Foo
redef bar
super + ' World'
end
end
Foo.new.bar # => 'Hello World'
Zastąpienie
redefwprowadzenie metody jest równoważne z przesłonięciem metody w prependedytowanym mixinie. superw metodzie zastępowania zachowuje się jak superlub oldw tej propozycji.