Есть две причины, по которым одна будет использовать Module#alias_method
, одна является текущей и действительной, а другая устарела и в действительности никогда не была необходима.
Первая причина заключается в том, чтоВы просто хотите иметь два метода с разными именами, которые делают одно и то же.Одной из причин этого может быть то, что есть два одинаково широко используемых термина для одного и того же действия, и вы хотите, чтобы людям было проще писать код, понятный их сообществу.(Некоторыми примерами в базовой библиотеке Ruby являются методы коллекций, имена которых знакомы людям, приходящим из функциональных языков программирования, таким как map
, reduce
, людям из семейства языков программирования Smalltalk, таким как collect
, inject
, select
и стандартные английские имена, такие как find_all
.) Другая возможность состоит в том, что вы создаете Fluent Interface и хотите, чтобы он читался более свободно, например так:
play this
and that
and something_else
В этом случае and
может быть псевдонимом для play
.
Другая, связанная с этим причина (назовем ее причиной 1.5), заключается в том, что вы хотите реализовать некоторый протокол иу вас уже есть метод, который правильно реализует семантику этого протокола, но у него неправильное имя.Давайте предположим гипотетическую структуру коллекций, которая имеет два метода map_seq
и map_nonseq
.Первый выполняет map
и гарантирует порядок побочных эффектов, тогда как второй не гарантирует порядок побочных эффектов и может даже выполнять операцию отображения асинхронно, одновременно или параллельно.Эти два метода на самом деле имеют разную семантику, но если ваша структура данных не поддается параллелизации, вы можете просто реализовать map_seq
и сделать map_nonseq
псевдонимом.
В этом случае драйвер не являетсянастолько, что вы хотите предоставить два имени для одной и той же операции, но вы хотите предоставить одну и ту же реализацию для двух имен (если это предложение имеет смысл :-D).
Вторая основная причина, почему alias_method
использовался в прошлом, имеет отношение к важной детали его семантики: когда вы переопределяете или monkey-patch один из двух методов, это повлияет только на это имя, но не на другое.В прошлом это использовалось для обтекания поведения метода, например:
class SomeClass
def some_method
"Hello World"
end
end
Это немного скучно.Мы хотим, чтобы наш метод ШУТ!Но мы не хотим просто копировать и повторно реализовывать метод, мы хотим повторно использовать его внутреннюю реализацию без необходимости знать, как он реализован внутри.И мы хотим сделать это, чтобы все клиенты этого метода имели кричащее поведение.Популярный способ сделать это был примерно такой:
class SomeClass
alias_method :__some_method_without_shouting :some_method
def __some_method_with_shouting
__some_method_without_shouting.upcase
end
alias_method :some_method :__some_method_with_shouting
end
В этом примере мы используем alias_method
, чтобы создать «резервную копию» метода, который мы используем для обезьяньего исправления, чтобы мы могли вызвать егоизнутри нашей залатанной обезьяной версии метода.(В противном случае этот метод исчезнет.) На самом деле это вариант использования, приведенный в документации alias_method
.
. Эта идиома была настолько популярна и широко использовалась, что некоторые библиотеки даже предоставили ее реализацию,например, ActiveSupport Module#alias_method_chain
.
Обратите внимание, однако, что эта идиома имеет некоторые не очень хорошие свойства.Во-первых, мы загрязняем пространство имен всеми этими _with_
и _without_
методами.Например, когда вы посмотрите на методы объекта, вы увидите все это.Другая проблема заключается в том, что люди все еще могут вызывать старый метод напрямую, но, вероятно, у нас была причина для его исправления, потому что мы не хотим старого поведения.(В противном случае мы могли бы просто создать метод с новым именем, которое вызывает старое, например, shout
.)
Всегда была лучшая альтернатива, которая не была так широко использована:
class SomeClass
some_method_without_shouting = instance_method(:some_method)
define_method(:some_method) do
some_method_without_shouting.bind(self).().upcase
end
end
Здесь мы храним старый метод в локальной переменной и используем блок для определения нового метода (через Module#define_method
).Локальная переменная выходит из области видимости в конце тела класса, поэтому к ней больше никогда не будет никакого доступа.Но блоки - это замыкания , они закрываются в окружающей их лексической среде, и, таким образом, блок, переданный в define_method
(и только этот блок), все еще имеет доступ к привязке переменной.Таким образом, старая реализация полностью скрыта и загрязнение пространства имен отсутствует.
Однако в Ruby 2.0 существует гораздо лучшее решение для обертывания этого метода: Module#prepend
.Прелесть prepend
в том, что это «просто наследование», и мы можем просто использовать super
:
module Shouter
def some_method
super.upcase
end
end
class SomeClass
prepend Shouter
end
Module#prepend
- причина, по которой Module#alias_method_chain
устарела в ActiveSupport 5.0 и удаленав 5.1, например.Все эти искажения просто больше не нужны.
Итак, подведем итог: было две основные причины использования alias_method
: буквальное создание псевдонима, то есть двух имен для одной и той же операции, и создание резервной копии.для метода упаковки.Второй больше не действителен и, возможно, возможно никогда не был.Сегодня только первая причина является допустимой причиной использования alias_method
.