Скажем, я обезьяна, исправляющая метод в классе, как я могу вызвать переопределенный метод из переопределяющего метода? Т.е. что-то вродеsuper
Например
class Foo
def bar()
"Hello"
end
end
class Foo
def bar()
super() + " World"
end
end
>> Foo.new.bar == "Hello World"
ruby
monkeypatching
Джеймс Холлингворт
источник
источник
Foo
и использованиеFoo::bar
. Таким образом, вы должны обезьяна патч метод.Ответы:
РЕДАКТИРОВАТЬ : Прошло 9 лет с тех пор, как я первоначально написал этот ответ, и это заслуживает некоторой косметической операции, чтобы сохранить его в актуальном состоянии.
Вы можете увидеть последнюю версию перед редактированием здесь .
Вы не можете позвонить перезаписанный метод по имени или ключевому слову. Это одна из многих причин, по которой следует избегать внесения исправлений в обезьяны и отдавать предпочтение наследованию, поскольку очевидно, что вы можете вызвать переопределенный метод.
Избежание мартышек
наследование
Итак, если это вообще возможно, вы должны предпочесть что-то вроде этого:
Это работает, если вы контролируете создание
Foo
объектов. Просто измените каждое место, которое создаетFoo
вместо того, чтобы создатьExtendedFoo
. Это работает даже лучше , если вы используете Dependency Injection Design Pattern , то шаблон Factory Method Design , то шаблон Abstract Factory Design или что - то вдоль этих линий, так как в этом случае, есть только место вам нужно изменить.Делегация
Если вы не контролируете создание
Foo
объектов, например, потому что они создаются структурой, которая находится вне вашего контроля (например,Рубин на рельсахнапример), тогда вы можете использовать шаблон дизайна Wrapper :По сути, на границе системы, где
Foo
объект входит в ваш код, вы заключаете его в другой объект, а затем используете этот объект вместо исходного везде в вашем коде.Это использует
Object#DelegateClass
вспомогательный метод изdelegate
библиотеки в stdlib.«Чистая» мартышка
Module#prepend
: МиксинДва вышеупомянутых метода требуют изменения системы, чтобы избежать внесения исправлений обезьянами. В этом разделе показан предпочтительный и наименее инвазивный метод исправления обезьян, если изменение системы не будет возможным.
Module#prepend
был добавлен для поддержки более или менее точно этот вариант использования.Module#prepend
делает то же самоеModule#include
, за исключением того, что он смешивается в миксине непосредственно под классом:Примечание: я также написал немного о
Module#prepend
этом вопросе: модуль Ruby предваряет против деривацииMixin Inheritance (не работает)
Я видел, как некоторые люди пытаются (и спрашивают о том, почему это не работает здесь, в StackOverflow) что-то вроде этого, то есть,
include
используя миксин вместоprepend
этого:К сожалению, это не сработает. Это хорошая идея, потому что она использует наследование, что означает, что вы можете использовать
super
. Тем не менее,Module#include
вставляет mixin над классом в иерархию наследования, что означает, чтоFooExtensions#bar
он никогда не будет вызван (и если бы он был вызван, тоsuper
он фактически не ссылался бы на него,Foo#bar
а скорее наObject#bar
который не существует), посколькуFoo#bar
всегда будет найден первым.Обертывание методом
Большой вопрос: как мы можем держаться за
bar
метод, фактически не оставляя фактического метода ? Ответ, как это часто бывает, заключается в функциональном программировании. Мы получаем метод как фактический объект и используем замыкание (то есть блок), чтобы убедиться, что мы и только мы удерживаем этот объект:Это очень чисто: так как
old_bar
это просто локальная переменная, она выйдет из области видимости в конце тела класса, и к ней невозможно получить доступ откуда угодно, даже используя отражение! И поскольку онModule#define_method
занимает блок, а блоки закрываются вокруг окружающей его лексической среды (именно поэтому мы используемdefine_method
егоdef
здесь), то он (и только он) будет по-прежнему иметь доступold_bar
даже после того, как он выйдет из области видимости.Краткое объяснение:
Здесь мы оборачиваем
bar
метод вUnboundMethod
объект метода и присваиваем его локальной переменнойold_bar
. Это означает, что теперь у нас есть способ удержатьсяbar
даже после того, как он был перезаписан.Это немного сложно. По сути, в Ruby (и почти во всех языках ОО на основе одной диспетчеризации) метод привязан к конкретному объекту-получателю, называемому
self
в Ruby. Другими словами: метод всегда знает, к какому объекту он был вызван, он знает, к какомуself
такое. Но мы взяли метод непосредственно из класса, откуда он знает, что этоself
такое?Ну, это не так, поэтому мы должны
bind
OURUnboundMethod
к объекту первого, который будет возвращатьMethod
объект , который мы можем назвать. (UnboundMethod
не могут быть вызваны, потому что они не знают, что делать, не зная ихself
.)И к чему мы
bind
это? Мы простоbind
это для себя, так он будет вести себя так же, как оригиналbar
!Наконец, нам нужно позвонить,
Method
что возвращается изbind
. В Ruby 1.9 есть некоторый новый синтаксис для этого (.()
), но если вы используете 1.8, вы можете просто использоватьcall
метод; это то, что.()
переводится в любом случае.Вот пара других вопросов, где объясняются некоторые из этих концепций:
«Грязная» мартышка
alias_method
цепьПроблема, с которой мы сталкиваемся при использовании патчей для обезьян, состоит в том, что когда мы перезаписываем метод, метод исчезает, поэтому мы больше не можем его вызывать. Итак, давайте просто сделаем резервную копию!
Проблема в том, что мы теперь загрязнили пространство имен излишним
old_bar
методом. Этот метод будет отображаться в нашей документации, он будет отображаться при завершении кода в наших IDE, он будет отображаться во время отражения. Кроме того, он все еще может быть вызван, но, вероятно, мы исправили его, потому что в первую очередь нам не нравилось его поведение, поэтому мы не хотим, чтобы другие люди вызывали его.Несмотря на то, что это имеет некоторые нежелательные свойства, оно, к сожалению, стало популярным благодаря AciveSupport's
Module#alias_method_chain
.В сторону: уточнения
Если вам нужно другое поведение только в нескольких определенных местах, а не во всей системе, вы можете использовать уточнения, чтобы ограничить патч обезьяны определенной областью. Я собираюсь продемонстрировать это здесь на
Module#prepend
примере сверху:Вы можете увидеть более сложный пример использования уточнений в этом вопросе: Как включить патч обезьяны для конкретного метода?
Заброшенные идеи
До того, как сообщество Ruby приняло решение
Module#prepend
, вокруг было много разных идей, на которые вы иногда можете встретить ссылки в более ранних обсуждениях. Все это относится к категорииModule#prepend
.Комбинаторы методов
Одной из идей была идея комбинаторов методов из CLOS. Это в основном очень облегченная версия подмножества Аспектно-ориентированного программирования.
Используя синтаксис как
Вы сможете «зацепиться» за исполнение
bar
метода.Однако не совсем понятно, если и как вы получаете доступ к
bar
возвращаемому значению в пределахbar:after
. Может быть, мы могли бы (ab) использоватьsuper
ключевое слово?замена
Комбинатор before эквивалентен
prepend
смешиванию с переопределенным методом, который вызываетsuper
в самом конце метода. Аналогично, после комбинатор эквивалентенprepend
смешиванию с переопределенным методом, который вызываетsuper
в самом начале метода.Вы также можете делать вещи до и после вызова
super
, вы можете вызыватьsuper
несколько раз, а также получать и манипулироватьsuper
возвращаемым значением, делая егоprepend
более мощным, чем комбинаторы методов.а также
old
ключевое словоЭта идея добавляет новое ключевое слово, похожее на
super
, которое позволяет вам вызывать перезаписанный метод таким же образом,super
позволяет вызывать переопределенный метод:Основная проблема в том, что он несовместим в обратном направлении: если у вас есть вызванный метод
old
, вы больше не сможете его вызывать!замена
super
в методе переопределения вprepend
ed mixin, по существу, такой же, какold
в этом предложении.redef
ключевое словоКак и выше, но вместо добавления нового ключевого слова для вызова перезаписанного метода и оставления в
def
покое, мы добавляем новое ключевое слово для переопределения методов. Это обратно совместимо, так как синтаксис в настоящее время в любом случае недопустим:Вместо добавления двух новых ключевых слов, мы также можем переопределить значение
super
insideredef
:замена
redef
Использование метода эквивалентно переопределению метода вprepend
ed mixin.super
в методе переопределения ведет себя какsuper
илиold
в этом предложении.источник
bind
одну и ту жеold_method
переменную?UnboundMethod#bind
возвращает новый, другойMethod
, поэтому я не вижу никакого конфликта, независимо от того, вызываете ли вы его дважды подряд или два раза одновременно из разных потоков.old
иredef
? Мой 2.0.0 не имеет их. Ах, трудно не пропустить Другие конкурирующие идеи, которые не попали в Ruby:Взгляните на методы псевдонимов, это своего рода переименование метода в новое имя.
Для получения дополнительной информации и отправной точки взгляните на эту статью о методах замены (особенно в первой части). Документация по Ruby API также предоставляет (менее сложный) пример.
источник
Класс, который будет выполнять переопределение, должен быть перезагружен после класса, который содержит исходный метод, поэтому
require
он должен находиться в файле, который будет выполнять переопределение.источник