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"
Author: Peter O., 2010-12-17

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 deftutaj), 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ć. (UnboundMethods 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 superna samym końcu metody. Podobnie kombinator po jest odpowiednikiem prepending 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 prepended 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

redefining metody jest równoznaczne z nadpisaniem metody w prepended mixin. super w metoda nadrzędna zachowuje się w niniejszym wniosku jak super lub old.

 1058
Author: Jörg W Mittag,
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.

 12
Author: Veger,
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.

 0
Author: rplaurindo,
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