Альтернативы базовым классам исправления обезьян - PullRequest
9 голосов
/ 30 марта 2009

Я все еще новичок в Ruby и в основном просто пишу свою первую микропрограмму после окончания книги Купера. Мне указали направление избегания исправлений обезьян, но проблема в том, что я не знаю, каковы альтернативы для достижения того же поведения. По сути, я хочу добавить новый метод, который доступен для каждого строкового объекта. Очевидный способ исправления обезьян:

class String
  def do_magic
    ...magic...
  end
end

Я помню, что есть способ использовать String.send. Но я не могу вспомнить, как это делается, и где я это читал. Кто-нибудь может указать какие-либо альтернативы, которые позволили бы мне сделать этот метод доступным для класса String и дочерних объектов?

Ответы [ 6 ]

15 голосов
/ 30 марта 2009

Любой другой способ сделать это был бы просто более неуклюжий синтаксис для исправления обезьян. Есть способы, включающие send и eval и все виды вещей, но почему ? Иди вперед и сделай это очевидным путем.

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

Одной из альтернатив в Ruby является то, что вы можете добавлять методы к одному объекту.

a = "Hello"
b = "Goodbye"
class <<a
  def to_slang
    "yo"
  end
end
a.to_slang # => "yo"
b.to_slang # NoMethodError: undefined method `to_slang' for "Goodbye":String
6 голосов
/ 30 марта 2009

Если вы хотите добавить новый метод, доступный для каждого строкового объекта, то сделать это так, как вы это делаете, - как это сделать.

Хорошей практикой является размещение ваших расширений для основных объектов в отдельном файле (например, string_ex.rb) или подкаталоге (например, extensions или core_ext). Таким образом, очевидно, что было расширено, и кому-то легко увидеть, как они были расширены или изменены.

Когда патч обезьяны может пойти не так, как надо, вы можете изменить существующее поведение основного объекта, из-за чего другой код ожидает неправильной работы исходной функциональности.

2 голосов
/ 30 марта 2009

Класс object определяет send, и все объекты наследуют его. Вы «отправляете» объект методом send. Параметры метода send - это имя метода, который вы хотите вызвать в качестве символа, за которым следуют любые аргументы и необязательный блок. Вы также можете использовать __send__.

>> "heya".send :reverse
=> "ayeh"

>> space = %w( moon star sun galaxy )
>> space.send(:collect) { |el| el.send :upcase! }
=> ["MOON", "STAR", "SUN", "GALAXY"]

Редактировать ..

Возможно, вы хотите использовать метод define_method:

String.class_eval {
  define_method :hello do |name|
    self.gsub(/(\w+)/,'hello') + " #{name}"
  end
}

puts "Once upon a time".hello("Dylan")
# >> hello hello hello hello Dylan

Это добавляет методы экземпляра. Чтобы добавить методы класса:

eigenclass = class << String; self; end
eigenclass.class_eval {
  define_method :hello do
    "hello"
  end
}

puts String.hello
# >> hello

Вы не можете определить методы, которые ожидают блок.

Было бы неплохо прочитать эту главу из руководства Why Poignant Guide , вы можете перейти к «Массиву Двемти», чтобы перейти к метапрограммированию.

1 голос
/ 30 марта 2009

Спасибо, ребята.

Все предложенные работы по внедрению. Что еще более важно, я научился взвешивать дело в руке и решить, является ли хорошая идея повторного открытия базовых (или библиотечных) классов.

FWIW, друг указал на реализацию send, которую я искал. Но теперь, когда я смотрю на это, он еще ближе к обезьяньей патче, чем все другие реализации:)

module M
    def do_magic
    ....
    end
end
String.send(:include, M)
0 голосов
/ 07 апреля 2016

«Обезьяна-патч», которую вы описываете, действительно может быть проблемой, если кто-то еще хочет запросить ваш код (например, как драгоценный камень). Кто сказал, что они также не захотят добавлять метод String, который называется do_magic? Один метод перезапишет другой, и это может быть сложным для отладки. Если есть вероятность, что ваш код будет с открытым исходным кодом, то лучше создать собственный класс:

class MyString < String
  def initialize(str)
    @str = str
  end
  def do_magic
    ...magic done on @str
    @str
  end
end

Теперь, если вам нужно сделать do_magic, вы можете

magic_str = MyString.new(str).do_magic
0 голосов
/ 07 ноября 2013

В качестве альтернативы присоединению функций к классам или объектам, вы всегда можете пойти функциональным путем:

class StringMagic
  def self.do(string)
     ...
  end
end

StringMagic.do("I'm a String.") # => "I'm a MAGIC String!"
...