alias и alias_method под микроскопом

Часто перед программистом становится необходимость создания второго имени для метода...

Posted by Марк Мельник on December 12, 2010

Часто перед программистом становится необходимость создания второго имени для метода — псевдонима метода. Создание псевдонима может нам понадобится, например если мы хотим переопределить метод, но при этом данный метод нам так же необходим. Для этих задач Ruby предоставляет нам такие инструменты, как alias и alias_method. Псевдоним — это не просто другое имя, это полная, точная копия метода, которой дано новое имя, благодаря чему мы можем изменить один метод (оригинал) не боясь, что это как-то отразится на другом (псевдониме — копии). Давайте рассмотрим пример использования alias:

def my_method(a,b)
  puts a+b
end

alias :mymeth : my_method

my_method(2,3) #=> 5
mymeth(1,4) #=> 5

Теперь, когда мы создали копию нашего метода, мы можем переопределить метод с оригинальным названием my_method:

def my_method (a,b)
  puts a**b
end

my_method(2,3) #=> 8
mymeth(2,3) #=> 5

Люди не имеющие практики программирования могут не понимать зачем необходимо такое вот переопределение методов. Чтобы развеять их сомнения в необходимости синонимов, поделюсь лично своим примером использования. Допустим, у меня есть класс, который определяет атрибуты и методы блоков отработки залежи полезного ископаемого, объект этого класса — модель блока в котором проводятся горные работы. В этом классе содержится следующий метод:

def ore (arg=:w)
  result = ...
  case arg
    when :p;  result = ...
    when :v;  result = ...
    when :vw; result = ...
  end

  result
end

Данный метод высчитывает и возвращает некоторые параметры руды: % от общего запаса, который нам удалось изъять из блока, удельный вес добытой руды, ее объем и общий вес добытой руды. Поскольку я разделяю логику и представление, я создаю новый класс, который отвечает за представление данных, в нем метод ore уже не возвращает значение, а печатает его. Это удобнее, чем писать: puts ore :w и т.д.. Метод для печати значения выглядит следующим образом:

alias :r_ore :ore

def ore (arg=:w)
  result = "ВЕС РУДЫ: #{ore :w}"
  case arg
    when :p;  result = "ПРОЦЕНТ ИЗВЛЕЧЕННОЙ РУДЫ: #{r_ore :p}"
    when :v;  result = "ОБЪЕМ ИЗВЛЕЧЕННОЙ РУДЫ: #{r_ore :v}"
    when :vw; result = "ОБЪЕМНЫЙ ВЕС ДОБЫТОЙ РУДЫ: #{r_ore :vw}"
  end

  puts result
end

Теперь понятны выгоды использования alias?

alias_metod я никогда не пользовал и считал, что это просто синоним для alias, но когда начал писать статью, решил все таки проверить так ли это. Вот к каким выводам я пришел:

class Cls

  def initialize (a=5, b=10)
    @a = a
    @b = b
  end
  # alias
  def sum
    @a + @b
  end

  alias :old_sum :sum

  def sum
    puts @a + @b
  end

  #alias_method
  def div
    @a / @b.to_f
  end

  alias_method :old_div, :div

  def div
    puts @a / @b.to_f
  end
end

Cls.new.sum
puts Cls.new.old_sum

Cls.new.div
puts Cls.new.old_div

#Выходит, что alias и alias_method эквивалентны? - Нет!

def my_method (a = 10, b = 20)
  a + b
end

alias_method :mymeth, :my_method #=> undefined method `alias_method' for main:Object (NoMethodError)
alias mymeth my_method

puts mymeth
puts my_method

Из этого следует, что alias_method не работает вне контекста какого-нибудь класса.

В чем еще разница? — alias это ключевое слово языка Ruby, а alias_method — метод класса Module. Из этого следует, что методу alias_method имена методов следует передавать в виде строк или символов, которые разделены запятой. Ключевому слову alias можно просто передавать имена методов (в том смысле, что они могут представляться не как символы и строки, а просто вы вводите имена методов и все, даже не разделяя их запятыми).

class MyClass

end

MyClass.instance_eval {alias c class}
puts MyClass.new.c # undefined method `c' for # (NoMethodError)

MyClass.instance_eval {alias_method :cl, :class}
puts MyClass.new.cl#=> MyClass

#Оппа! alias не работает в контексте объекта, а alias_method работает!

MyClass.class_eval {alias c class}
puts MyClass.new.c #=> MyClass

MyClass.class_eval {alias_method :cls, :class}
puts MyClass.new.cls #=> MyClass

Я нашел это различие, но не могу понять какую из этого можно взять пользу. Далее я начал гуглить и нашел действительно полезный комментарий в котором было описано еще одно различие:

class Parent
  def self.redef
    alias redef_method my_method
  end

  def self.redef_m
    alias_method :redef_method, :my_method
  end

  def my_method
    puts "Parent"
  end
end

class Child1 < Parent
  def my_method
    puts "Child"
  end
  redef()
end

class Child2 < Parent   
  def my_method     
    puts "Child"   
  end   
  redef_m() 
end   

Child1.new.redef_method #=> Parent
Child2.new.redef_method #=> Child

В этом уже можно найти какой-нибудь смысл, однако я не видел, чтобы кто-то этим пользовался. Исходя из того, что я вообще мало чего видел, это нельзя считать каким-либо показателем того, что пользоваться этой особенностью нельзя.

Если вы что-то знаете о различиях в alias и alias_method отпишитесь пожалуйста в комментариях.

Ваши комментарии — лучший мотиватор для написания новых постов!