Альтернативы базовым классам исправлений обезьян
-
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