Альтернативы базовым классам исправлений обезьян

StackOverflow https://stackoverflow.com/questions/695441

  •  22-08-2019
  •  | 
  •  

Вопрос

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

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

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

Это было полезно?

Решение

Любой другой способ сделать это был бы просто более неудобным синтаксисом для исправления обезьян.Есть способы, включающие 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

Другие советы

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

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

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

А 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's Poignant Guide, вы можете перейти к «Dwemthy’s Array», чтобы перейти к метапрограммированию.

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

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

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

module M
    def do_magic
    ....
    end
end
String.send(:include, M)

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

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

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

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

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

Теперь, если вам нужно заняться магией, вы можете

magic_str = MyString.new(str).do_magic
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top