Ruby добавить метод в класс

Предположим у меня есть класс :

class Foo
end

чтобы добавить метод к этому классу, я знаю 2 варианта:

  1. повторное открытие класса и реализация метода:

    class Foo
      def bar
      end
    end
    
  2. используя 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