Ruby добавить метод в класс
Предположим у меня есть класс :
class Foo
end
чтобы добавить метод к этому классу, я знаю 2 варианта:
-
повторное открытие класса и реализация метода:
class Foo def bar end end
-
используя
class_eval
выполнить способ :Foo.class_eval { def bar; end}
в чем разница? Какой из них лучше?
2 ответов
на самом деле, есть несколько других способов, чтобы добавить новые методы в класс. Например, можно также определить методы в модуле и смешать модуль с исходным классом.
module ExtraMethods
def bar
end
end
Foo.class_eval { include ExtraMethods }
class Foo
include ExtraMethods
end
нет реального лучше или хуже. Два (или три) способа, которые вы упомянули, имеют разное поведение, и вы можете использовать тот или иной в зависимости от ваших потребностей (или предпочтений). В большинстве случаев, это субъективно. В других случаях, это действительно зависит от того, как ваш код структурированный.
основная объективная разница между повторным открытием класса против использования class_eval
является то, что первый также является определением класса, тогда как второй требует, чтобы исходный класс уже был определен.
на практике, открытие класса в некоторых случаях может вызвать неожиданные побочные эффекты. Предположим, вы определили Foo
в файле lib/foo.rb
, с кучей методов. Затем вы открываете Foo
на config/initializers/extra.rb
и добавить bar
метод.
на myclass.rb
вы используете Foo
, а вместо lib/foo.rb
вручную вы полагаетесь на функцию автоматической загрузки.
если extra.rb
загружается перед lib/foo.rb
, что может случиться, что Foo
класс уже определен в вашей среде, и ваш код не будет загружаться lib/foo.rb
. То, что вы есть Foo
класс, содержащий только bar
расширение, которое вы определили, а не оригинал Foo
один.
другими словами, если для чего-либо причина повторного открытия класса для добавления некоторых методов, не убедившись, что полное определение класса origina загружено первым (или после), ваш код может сломаться, если он полагается на автозапуск.
наоборот, Foo.class_eval
вызывает метод on Foo
, поэтому он ожидает, что первоначально Foo
определение будет уже присутствовать при попытке добавить новые методы. Это гарантирует, что при добавлении новых методов Foo
класс уже будет определен.
в заключение, основной разница в том, что повторное открытие класса позволяет (к лучшему или к худшему) добавлять методы в класс, который, возможно, еще не загружен, тогда как class_eval
требует, чтобы класс уже был определен.
в общем, если я не определяю подклассы с пространством имен или классы повторного открытия, которые я полностью контролирую, я предпочитаю второй подход, поскольку в больших кодовых базах он сохраняет код более доступным. Фактически, я обычно использую mixins, если я расширяю сторонние классы, чтобы я мог сохранить полная цепочка предков метода, если мне нужно переопределить существующие методы.
второй подход очень удобен, когда вам нужна какая-то динамическая вещь. Руби на самом деле имеет несколько областей:
# scope one, opened with `class` keyword
class ...
# scope two, opened with `def` keyword
def ...
end
end
С class_eval
, вы можете поделиться областей.
>> foo = 1
=> 1
>> class Foo
>> puts foo
>> def bar
>> puts foo
>> end
>> end
NameError: undefined local variable or method 'foo' for Foo:Class
from (irb):3:in <class:Foo>
from (irb):2
>> Foo
=> Foo
>> Foo.class_eval {
?> puts foo
>> define_method :bar do
>> puts foo
>> end
>> }
1
=> :bar
>> Foo.new.bar
1